├── .gitignore ├── AUTHORS ├── CHANGELOG ├── LICENSE ├── MANIFEST.in ├── README.rst ├── cura_plugins ├── stretch_plugin.py └── tempcal_plugin.py ├── docs ├── Makefile ├── conf.py ├── gcode_mod.rst ├── gcode_optimize_arcs.rst ├── gcode_stretch.rst ├── gcode_tempcal.rst ├── index.rst └── installation.rst ├── gcodeutils ├── __init__.py ├── filter │ ├── __init__.py │ ├── arc_optimizer.py │ ├── filter.py │ ├── relative_extrusion.py │ └── translate.py ├── gcode_mod.py ├── gcode_optimize_arcs.py ├── gcode_stretch.py ├── gcode_tempcal.py ├── gcoder.py ├── stretch │ ├── __init__.py │ ├── stretch.py │ └── vector3.py ├── tests │ ├── __init__.py │ ├── arc_raw_1.gcode │ ├── arc_raw_2.gcode │ ├── arc_raw_3.gcode │ ├── arc_raw_4.gcode │ ├── arc_ref_1.gcode │ ├── arc_ref_2.gcode │ ├── arc_ref_4.gcode │ ├── cura_square.gcode │ ├── empty_for_good.gcode │ ├── empty_skeinforge_format.gcode │ ├── simple1.gcode │ ├── simple1_equivalent.gcode │ ├── simple1_slightly_different.gcode │ ├── simple2.gcode │ ├── simple3-relative.gcode │ ├── simple3.gcode │ ├── skeinforge_model1_poststretch.gcode │ ├── skeinforge_model1_prestretch.gcode │ ├── skeinforge_square.gcode │ ├── slic3r_square.gcode │ ├── test_arc_optimization.py │ ├── test_equality.py │ ├── test_iterator.py │ ├── test_modification.py │ ├── test_stretch.py │ └── test_visitor.py └── visit │ ├── __init__.py │ ├── iterator.py │ ├── pause_at_layer.py │ └── visitor.py ├── models └── temperature │ ├── temperature_hollow_tower.scad │ └── temperature_hollow_tower.stl ├── pylintrc ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | dist/ 3 | build/ 4 | gcodeutils.egg-info/ 5 | *.pyc 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Olivier Jolly initial scripts (framework, stretching filter, ...) 2 | Eyck Jentzsch arc optimisation filter and CLI, bug fixes in relative extrusion 3 | Joe Friedrichsen classes in gcodeutils.visit 4 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | ## [1.3.2] - 2016-06-20 2 | ### Changed 3 | - fixed several bugs in arc computation based on real life feedback (Eyck Jentzsch) 4 | 5 | ## [1.3] - 2016-06-07 6 | ### Added 7 | - added filter and CLI to use gcode arc opcodes when possible (Eyck Jentzsch) 8 | 9 | ### Changed 10 | - fixed bug in relative extrusion filter after G92 (Eyck Jentzsch) 11 | 12 | ## Older versions 13 | ### Added 14 | - added filter and CLI to stretch gcode to compensante for hole undersizing 15 | - added filter and CLI to convert absolute extrusion to relative 16 | - added CLI to generate temperature gradient in existing gcode (for temperature calibration purpose) 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include AUTHORS 3 | include CHANGELOG 4 | 5 | # Include the test suite (FIXME: does not work yet) 6 | # recursive-include tests * 7 | 8 | include models/temperature/* 9 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | GCodeUtils 2 | ========== 3 | 4 | GCodeUtils is a set of utilities to manipulate GCode programs. 5 | It is targetting RepRap oriented GCode but isn't limited to RepRap. 6 | 7 | Currently, it is composed of one calibration generation program, a basic gcode modifier (which translates code along 8 | X/Y or convert extrusion distance to relative) and a stretcher to increase hole size of printed parts. 9 | 10 | Installation 11 | ------------ 12 | 13 | GCodeUtils is installable from PyPI with a single pip command:: 14 | 15 | pip install gcodeutils 16 | 17 | Alternatively, GCodeUtils can be run directly from sources after a git pull:: 18 | 19 | git clone https://github.com/zeograd/gcodeutils.git 20 | cd gcodeutils && python setup.py install 21 | 22 | or directly from its git repository:: 23 | 24 | pip install git+https://github.com/zeograd/gcodeutils.git 25 | 26 | Once GCodeUtils is installed, the .py files located in the cura_plugins 27 | subdirectory can be copied into the *plugins* directory of Cura to be callable 28 | from within Cura itself. 29 | 30 | Documentation 31 | ------------- 32 | 33 | Latest documentation can be found online at this address: http://gcodeutils.readthedocs.org/en/latest/ 34 | 35 | Acknowledgement 36 | --------------- 37 | 38 | GCode parsing is borrowed from GCoder as found in Printrun (https://github.com/kliment/Printrun) 39 | -------------------------------------------------------------------------------- /cura_plugins/stretch_plugin.py: -------------------------------------------------------------------------------- 1 | #Name: Stretch 2 | #Info: Alter toolpath to partially compensate hole size in the X/Y plane 3 | #Depend: GCode 4 | #Type: postprocess 5 | #Param: stretch_strength(float:1.0) Stretch factor, higher value will give bigger holes 6 | 7 | # hack for windows platform with a non system wide python so that you can 8 | # just copy gcodeutils into the plugins directory instead of installing it 9 | # no twinkles for you, ultimaker 10 | import sys 11 | sys.path.append('plugins') 12 | 13 | from gcodeutils.filter.relative_extrusion import GCodeToRelativeExtrusionFilter 14 | from gcodeutils.gcoder import GCode 15 | from gcodeutils.stretch.stretch import CuraStretchFilter 16 | 17 | with open(filename, 'r') as gcode_file: # pylint: disable=undefined-variable 18 | gcode = GCode(gcode_file) # pylint: disable=redefined-outer-name,invalid-name 19 | 20 | GCodeToRelativeExtrusionFilter().filter(gcode) 21 | CuraStretchFilter(stretch_strength=stretch_strength).filter(gcode) # pylint: disable=undefined-variable 22 | 23 | with open(filename, 'w') as gcode_file: # pylint: disable=undefined-variable 24 | gcode.write(gcode_file) 25 | -------------------------------------------------------------------------------- /cura_plugins/tempcal_plugin.py: -------------------------------------------------------------------------------- 1 | #Name: Temp tower 2 | #Info: Update temperature continuously 3 | #Depend: GCode 4 | #Type: postprocess 5 | #Param: start_temp(float:150) Temperature at bottom of calibration object 6 | #Param: end_temp(float:240) Temperature at top of calibration object 7 | #Param: min_z_change(float:3) Minimum Z for changing temperatures 8 | #Param: steps(float:10) Number of steps 9 | 10 | # hack for windows platform with a non system wide python so that you can 11 | # just copy gcodeutils into the plugins directory instead of installing it 12 | # no twinkles for you, ultimaker 13 | import sys 14 | sys.path.append('plugins') 15 | 16 | from gcodeutils.gcode_tempcal import GCodeStepTempGradient 17 | from gcodeutils.gcoder import GCode 18 | 19 | with open(filename, 'r') as gcode_file: # pylint: disable=undefined-variable 20 | gcode = GCode(gcode_file) # pylint: disable=redefined-outer-name,invalid-name 21 | 22 | with open(filename, 'w') as gcode_file: # pylint: disable=undefined-variable 23 | GCodeStepTempGradient(gcode=gcode, start_temp=start_temp, end_temp=end_temp, min_z_change=min_z_change, 24 | steps=steps).write(gcode_file) 25 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/GCodeutils.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/GCodeutils.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/GCodeutils" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GCodeutils" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # GCodeutils documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Jun 1 23:45:11 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = ['sphinx.ext.autodoc'] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix(es) of source filenames. 38 | # You can specify multiple suffix as a list of string: 39 | # source_suffix = ['.rst', '.md'] 40 | source_suffix = '.rst' 41 | 42 | # The encoding of source files. 43 | #source_encoding = 'utf-8-sig' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = u'GCodeutils' 50 | copyright = u'2015-2016, Olivier Jolly' 51 | author = u'Olivier Jolly' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = '1.3' 59 | # The full version, including alpha/beta/rc tags. 60 | release = '1.3' 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # There are two options for replacing |today|: either, you set today to some 70 | # non-false value, then it is used: 71 | #today = '' 72 | # Else, today_fmt is used as the format for a strftime call. 73 | #today_fmt = '%B %d, %Y' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | exclude_patterns = ['_build'] 78 | 79 | # The reST default role (used for this markup: `text`) to use for all 80 | # documents. 81 | #default_role = None 82 | 83 | # If true, '()' will be appended to :func: etc. cross-reference text. 84 | #add_function_parentheses = True 85 | 86 | # If true, the current module name will be prepended to all description 87 | # unit titles (such as .. function::). 88 | #add_module_names = True 89 | 90 | # If true, sectionauthor and moduleauthor directives will be shown in the 91 | # output. They are ignored by default. 92 | #show_authors = False 93 | 94 | # The name of the Pygments (syntax highlighting) style to use. 95 | pygments_style = 'sphinx' 96 | 97 | # A list of ignored prefixes for module index sorting. 98 | #modindex_common_prefix = [] 99 | 100 | # If true, keep warnings as "system message" paragraphs in the built documents. 101 | #keep_warnings = False 102 | 103 | # If true, `todo` and `todoList` produce output, else they produce nothing. 104 | todo_include_todos = False 105 | 106 | 107 | # -- Options for HTML output ---------------------------------------------- 108 | 109 | # The theme to use for HTML and HTML Help pages. See the documentation for 110 | # a list of builtin themes. 111 | html_theme = 'default' 112 | 113 | # Theme options are theme-specific and customize the look and feel of a theme 114 | # further. For a list of options available for each theme, see the 115 | # documentation. 116 | #html_theme_options = {} 117 | 118 | # Add any paths that contain custom themes here, relative to this directory. 119 | #html_theme_path = [] 120 | 121 | # The name for this set of Sphinx documents. If None, it defaults to 122 | # " v documentation". 123 | #html_title = None 124 | 125 | # A shorter title for the navigation bar. Default is the same as html_title. 126 | #html_short_title = None 127 | 128 | # The name of an image file (relative to this directory) to place at the top 129 | # of the sidebar. 130 | #html_logo = None 131 | 132 | # The name of an image file (within the static path) to use as favicon of the 133 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 134 | # pixels large. 135 | #html_favicon = None 136 | 137 | # Add any paths that contain custom static files (such as style sheets) here, 138 | # relative to this directory. They are copied after the builtin static files, 139 | # so a file named "default.css" will overwrite the builtin "default.css". 140 | # html_static_path = ['_static'] 141 | 142 | # Add any extra paths that contain custom files (such as robots.txt or 143 | # .htaccess) here, relative to this directory. These files are copied 144 | # directly to the root of the documentation. 145 | #html_extra_path = [] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 148 | # using the given strftime format. 149 | #html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | #html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | #html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names to 159 | # template names. 160 | #html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | #html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | #html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | #html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 175 | #html_show_sphinx = True 176 | 177 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 178 | #html_show_copyright = True 179 | 180 | # If true, an OpenSearch description file will be output, and all pages will 181 | # contain a tag referring to it. The value of this option must be the 182 | # base URL from which the finished HTML is served. 183 | #html_use_opensearch = '' 184 | 185 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 186 | #html_file_suffix = None 187 | 188 | # Language to be used for generating the HTML full-text search index. 189 | # Sphinx supports the following languages: 190 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 191 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 192 | #html_search_language = 'en' 193 | 194 | # A dictionary with options for the search language support, empty by default. 195 | # Now only 'ja' uses this config value 196 | #html_search_options = {'type': 'default'} 197 | 198 | # The name of a javascript file (relative to the configuration directory) that 199 | # implements a search results scorer. If empty, the default will be used. 200 | #html_search_scorer = 'scorer.js' 201 | 202 | # Output file base name for HTML help builder. 203 | htmlhelp_basename = 'GCodeutilsdoc' 204 | 205 | # -- Options for LaTeX output --------------------------------------------- 206 | 207 | latex_elements = { 208 | # The paper size ('letterpaper' or 'a4paper'). 209 | #'papersize': 'letterpaper', 210 | 211 | # The font size ('10pt', '11pt' or '12pt'). 212 | #'pointsize': '10pt', 213 | 214 | # Additional stuff for the LaTeX preamble. 215 | #'preamble': '', 216 | 217 | # Latex figure (float) alignment 218 | #'figure_align': 'htbp', 219 | } 220 | 221 | # Grouping the document tree into LaTeX files. List of tuples 222 | # (source start file, target name, title, 223 | # author, documentclass [howto, manual, or own class]). 224 | latex_documents = [ 225 | (master_doc, 'GCodeutils.tex', u'GCodeutils Documentation', 226 | u'Olivier Jolly', 'manual'), 227 | ] 228 | 229 | # The name of an image file (relative to this directory) to place at the top of 230 | # the title page. 231 | #latex_logo = None 232 | 233 | # For "manual" documents, if this is true, then toplevel headings are parts, 234 | # not chapters. 235 | #latex_use_parts = False 236 | 237 | # If true, show page references after internal links. 238 | #latex_show_pagerefs = False 239 | 240 | # If true, show URL addresses after external links. 241 | #latex_show_urls = False 242 | 243 | # Documents to append as an appendix to all manuals. 244 | #latex_appendices = [] 245 | 246 | # If false, no module index is generated. 247 | #latex_domain_indices = True 248 | 249 | 250 | # -- Options for manual page output --------------------------------------- 251 | 252 | # One entry per manual page. List of tuples 253 | # (source start file, name, description, authors, manual section). 254 | man_pages = [ 255 | (master_doc, 'gcodeutils', u'GCodeutils Documentation', 256 | [author], 1) 257 | ] 258 | 259 | # If true, show URL addresses after external links. 260 | #man_show_urls = False 261 | 262 | 263 | # -- Options for Texinfo output ------------------------------------------- 264 | 265 | # Grouping the document tree into Texinfo files. List of tuples 266 | # (source start file, target name, title, author, 267 | # dir menu entry, description, category) 268 | texinfo_documents = [ 269 | (master_doc, 'GCodeutils', u'GCodeutils Documentation', 270 | author, 'GCodeutils', 'One line description of project.', 271 | 'Miscellaneous'), 272 | ] 273 | 274 | # Documents to append as an appendix to all manuals. 275 | #texinfo_appendices = [] 276 | 277 | # If false, no module index is generated. 278 | #texinfo_domain_indices = True 279 | 280 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 281 | #texinfo_show_urls = 'footnote' 282 | 283 | # If true, do not generate a @detailmenu in the "Top" node's menu. 284 | #texinfo_no_detailmenu = False 285 | -------------------------------------------------------------------------------- /docs/gcode_mod.rst: -------------------------------------------------------------------------------- 1 | gcode_mod 2 | --------- 3 | 4 | **gcode_mod** modifies an existing GCode program to translate all moves along X and Y by some amount, 5 | or to convert all extrusion distance to relative, or to insert a pause command at a specific layer. 6 | 7 | Use case 8 | ........ 9 | 10 | This program can be used to reprint a part at another place when you only have the GCode program. 11 | If you have the original stl, slicer program, and settings and you want to print at another location 12 | on your bed, you will get better results by generating a new GCode program. 13 | 14 | This program can be used to convert absolute extrusion to relative extrusion so that further GCode 15 | processing is eased. It is used for instance in **gcode_stretch** as a preprocessor before performing 16 | toolpath changes required for stretching. 17 | 18 | This program can also be used to insert a pause command when the GCode programs begins a new layer. 19 | 20 | Usage 21 | ..... 22 | 23 | Call **gcode_mod** with the GCode either in plain text (or piped) or by giving its filename. 24 | Depending on the required modification, you have to pass at least one of (i) the -x and -y amount to 25 | translate, (ii) -e to enable relative extrusion, or (iii) -p layer to pause. 26 | 27 | **gcode_mod** attempts to handle relative and absolute moves as well as position setting (G92) but you better 28 | double check the generated GCode until more feedback have been factored into polishing the translation algorithm. 29 | 30 | :: 31 | 32 | usage: gcode_mod [-h] [-x amount] [-y amount] [-e] [-p layer] [--verbose] [--quiet] 33 | [infile] [outfile] 34 | 35 | Modify gcode program 36 | 37 | positional arguments: 38 | infile Program filename to be modified. Defaults to standard input. 39 | outfile Modified program. Defaults to standard output. 40 | 41 | optional arguments: 42 | -h, --help show this help message and exit 43 | -x amount Move all gcode program by units in the X axis. 44 | -y amount Move all gcode program by units in the Y axis. 45 | -e Convert all extrusion to relative 46 | -p layer Pause the gcode program at layer . 47 | --verbose, -v Verbose mode 48 | --quiet, -q Quiet mode 49 | 50 | -------------------------------------------------------------------------------- /docs/gcode_optimize_arcs.rst: -------------------------------------------------------------------------------- 1 | gcode_optimize_arcs 2 | --------- 3 | 4 | **gcode_optimize_arcs** modifies an existing GCode program to translate all moves along a circular are into G2/G3 5 | commands. 6 | 7 | Use case 8 | ........ 9 | 10 | As it stands now, its can be used to post-process gcode generated from STL. As STL does not have means to describe arcs 11 | the resulting gcode does not contain arcs. Especially if there are small thru-holes with a high segmentation to be 12 | printed it might kill the print as many small movements happen a the same place. 13 | 14 | Usage 15 | ..... 16 | 17 | Call **gcode_optimize_arcs** with the GCode either in plain text (or piped) or by giving its filename. 18 | 19 | **gcode_optimize_arcs** attempts to handle relative and absolute moves as well as position setting (G92) but you better 20 | double check the generated GCode until more feedback have been factored into polishing the translation algorithm. 21 | Moreover there is currently no extrusion correction so you better us a high segmentation. In OpenSCAD this is 22 | controlled by $fn so a good starting point (for small radius) is $fn=64; 23 | 24 | :: 25 | 26 | usage: gcode_optimize_arcs [-h] [--inplace] [--verbose] [--quiet] 27 | [infile] [outfile] 28 | 29 | Modify GCode program to account arcs and replace the G1 with G2/G3 30 | 31 | positional arguments: 32 | infile Program filename to be modified. Defaults to standard input. 33 | outfile Modified program. Defaults to standard output. 34 | 35 | optional arguments: 36 | -h, --help show this help message and exit 37 | --inplace, -i modify the code in-place, usefull if gcode_optimize_arcs is used as post processor in Slic3r 38 | --verbose, -v Verbose mode 39 | --quiet, -q Quiet mode 40 | 41 | .. _inner-working: 42 | 43 | Inner working 44 | ============= 45 | 46 | Arcs are detected using a moving window over the gcode commands. The minimum size of the window is 9 so that at least 8 47 | segments are needed to constitute an arc. The validity is checked using the following criterias: 48 | * all segments are printed using the same speed (F parameter in gcode) 49 | * if segments extrude they have to have the same ration between extrusion (E param) and the length of th epath 50 | * the endpoints of the segments need to be equidistant on the arc 51 | -------------------------------------------------------------------------------- /docs/gcode_stretch.rst: -------------------------------------------------------------------------------- 1 | gcode_stretch 2 | ************* 3 | 4 | **gcode_stretch** modifies existing GCode program to change the size of "printed" holes. 5 | 6 | It is a port of skeinforge stretch module, made to work with other modern libre slicers. 7 | 8 | .. warning:: 9 | This filter isn't extensively tested yet. Please apply an appropriate amount of scepticism and report both 10 | failures and successes. 11 | 12 | Use case 13 | ======== 14 | 15 | When designing technical 3D parts for printing, you often need to make sure that the hole made in your part will 16 | accomodate another part of a known size. Typically, you want to be able to fit a M8 screw or rod. 17 | 18 | Due to several reasons, the printed part may end up with a smaller size than expected. 19 | 20 | .. note:: 21 | To achieve the right round hole size, when using OpenSCAD, you should use `polyholes `_. 22 | 23 | If you have no way to improve the original design to increase the hole size (not having access to the source files, 24 | not having round holes or just feeling very lazy), **gcode_stretch** can help increase it. 25 | 26 | Usage 27 | ===== 28 | 29 | The GCode to modify must be prepared so that **gcode_stretch** knows about the theorical edge width and loop types. 30 | This step is specific to each slicer. 31 | 32 | Preparation of Slic3r GCode 33 | --------------------------- 34 | 35 | Upstream slic3r doesn't provide any distinction of external and internal perimeters in its GCode. 36 | Since the 2 July 2015, slic3r doesn't generate usable comments to distinguish properly all loops type. 37 | 38 | There is an open ticket regarding this GCode verbosity. If a solution is found, regular slic3r versions will be compatible 39 | with **gcode_stretch**. 40 | 41 | Once done, you need to set 2 settings to produce compatible GCode : 42 | 43 | * "External perimeters first" (in *Print Settings* > *Layers and perimeters* > *Advanced*) 44 | * "Verbose G-code" (in *Print Settings* > *Output options* > *Output file*) 45 | 46 | At this point, you can generate GCode and apply **gcode_stretch** manually or you should be able to use **gcode_stretch** as 47 | post processing script (settable in *Print Settings* > *Output options* > *Post-processing scripts*, but untested). 48 | 49 | Preparation of Cura GCode 50 | ------------------------- 51 | 52 | Cura generates usable GCode for stretching by default. 53 | 54 | You can either manually postprocess generated GCode or copy the gcode_stretch.py file to your Cura plugins 55 | directory to enable stretching from within Cura itself. 56 | 57 | Command line 58 | ------------ 59 | 60 | GCode can be provided in standard input and will be output in standard output by default. You can set the GCode 61 | filename on command line and set the output filename too. 62 | 63 | If you need to change the sizing, you're advised to play with the stretch_strength parameter first (it defaults to 1, 64 | increase it to increase hole size [1.1, 1.2, ...], decrease it to decrease hole size [0.9, 0.8, ...]) . Changing other 65 | parameters imply knowledge of the inner working. 66 | 67 | :: 68 | 69 | usage: gcode_stretch [-h] 70 | [--cross_limit_distance_over_edge_width CROSS_LIMIT_DISTANCE_OVER_EDGE_WIDTH] 71 | [--stretch_from_distance_over_edge_width STRETCH_FROM_DISTANCE_OVER_EDGE_WIDTH] 72 | [--loop_stretch_over_edge_width LOOP_STRETCH_OVER_EDGE_WIDTH] 73 | [--edge_inside_stretch_over_edge_width EDGE_INSIDE_STRETCH_OVER_EDGE_WIDTH] 74 | [--edge_outside_stretch_over_edge_width EDGE_OUTSIDE_STRETCH_OVER_EDGE_WIDTH] 75 | [--stretch_strength STRETCH_STRENGTH] [--verbose] 76 | [--quiet] 77 | [infile] [outfile] 78 | 79 | Modify GCode program to account for stretch and improve hole size 80 | 81 | positional arguments: 82 | infile Program filename to be modified. Defaults to standard 83 | input. 84 | outfile Modified program. Defaults to standard output. 85 | 86 | optional arguments: 87 | -h, --help show this help message and exit 88 | --cross_limit_distance_over_edge_width CROSS_LIMIT_DISTANCE_OVER_EDGE_WIDTH 89 | Distance after and before a point where moves are 90 | considered to determine the local normal. Defaults to 91 | 5.0. Set too low or too high, it might cause points no 92 | to be moved in the right direction. 93 | --stretch_from_distance_over_edge_width STRETCH_FROM_DISTANCE_OVER_EDGE_WIDTH 94 | Distance after and before a point where moves are 95 | considered to determine the local normal. Defaults to 96 | 2.0. Set too low or too high, it might cause points no 97 | to be moved in the right direction. 98 | --loop_stretch_over_edge_width LOOP_STRETCH_OVER_EDGE_WIDTH 99 | Stretching strength for "loop" (extra shells), 100 | defaults to 0.11 101 | --edge_inside_stretch_over_edge_width EDGE_INSIDE_STRETCH_OVER_EDGE_WIDTH 102 | Stretching strength for "inner perimeter", defaults to 103 | 0.32 104 | --edge_outside_stretch_over_edge_width EDGE_OUTSIDE_STRETCH_OVER_EDGE_WIDTH 105 | Stretching strength for "outer perimeter", defaults to 106 | 0.1 107 | --stretch_strength STRETCH_STRENGTH 108 | Stretching stretch factor. This is the first setting 109 | you'll want to change to modify the hole size 110 | --verbose, -v Verbose mode 111 | --quiet, -q Quiet mode 112 | 113 | 114 | 115 | .. _inner-working: 116 | 117 | Inner working 118 | ============= 119 | 120 | This filter uses metadata provided by the slicer to determine the type of path to which a point belong. 121 | Only points being part of the visible shell are affected (infill, skirt, brim and so on aren't related to hole sizes). 122 | 123 | When a loop is flagged in the GCode (external perimeter being the outer shell, external perimeter being the inner 124 | shell or internal perimeter), all points in this loop are considered for streching. 125 | 126 | For each point, the normal vector is estimated by looking at the next and previous points of this loop. Once the normal 127 | vector is found, the point is moved away proportionally to the edge width, normal strength and loop type stretching 128 | strength. 129 | Extrusion is also adapted (quite empirically at this moment) to limit overextrusion in the shell / infill boundary. -------------------------------------------------------------------------------- /docs/gcode_tempcal.rst: -------------------------------------------------------------------------------- 1 | gcode_tempcal 2 | ------------- 3 | 4 | **gcode_tempcal** modifies an existing gcode program to introduce temperature 5 | gradient along the Z axis. 6 | 7 | When finetuning the extrusion temperature for an unknown filament, it is possible 8 | to perform an unattented calibration print where the temperature changes along 9 | with Z. Once the print is finished, you can observe which height the print looks 10 | better and determines the temperature at which this part was printed. 11 | 12 | Use case 13 | ........ 14 | 15 | Finding out what is the ideal temperature to print with a new spool of filament can 16 | be achieved using the technique described on this video : https://www.youtube.com/watch?v=FSOPsRiiOZk 17 | 18 | Basically, you create a tower that you'll slice to print only the outer shell. 19 | 20 | Then you update manually the gcode program to insert temperature decrase as you print and 21 | you inspect the print quality to find out which temperature resulted in the best appearance. 22 | 23 | **gcode_tempcal** comes with a suitable cuboid model and a script to perform this temperature 24 | alteration in the gcode produced by your slicer to get you ready to print in a shorter time. 25 | 26 | You may also want to use a tower with vertical holes like this one : http://www.thingiverse.com/thing:826019 so 27 | that you'll both see perimeter appearance and bridge quality depending on the temperature. 28 | 29 | Usage 30 | ..... 31 | 32 | Follow the directions in the video https://www.youtube.com/watch?v=FSOPsRiiOZk except 33 | that you can use the cuboid located at this location https://github.com/zeograd/gcodeutils/blob/master/models/temperature/temperature_hollow_tower.stl 34 | which is already hollowed out (you may want to check that the slicer will generate at least 35 | one outer shell for the thin walls. If not you can force the perimeter width or regenerate the 36 | mode out of the self documented openSCAD model at the same location). 37 | 38 | Once sliced, instead of manually editing the gcode program, use **gcode_tempcal** to insert 39 | the temperature change instructions. 40 | 41 | .. code:: bash 42 | 43 | $ gcode_tempcal 220 210 temperate_hollow_tower.gcode temperature_grad220-210.gcode -v 44 | [ ... heights snippet removed ...] 45 | INFO:temperature gradient from 220.0°C, altitude 0.30mm to 210.0°C, altitude 99.95mm 46 | DEBUG:target temp for layer #2 (height 0.30mm) is 220.0°C 47 | DEBUG:target temp for layer #34 (height 9.65mm) is 219.0°C 48 | DEBUG:target temp for layer #64 (height 18.65mm) is 218.0°C 49 | DEBUG:target temp for layer #94 (height 27.65mm) is 217.0°C 50 | DEBUG:target temp for layer #124 (height 36.65mm) is 216.0°C 51 | DEBUG:target temp for layer #154 (height 45.65mm) is 215.0°C 52 | DEBUG:target temp for layer #185 (height 54.95mm) is 214.0°C 53 | DEBUG:target temp for layer #215 (height 63.95mm) is 213.0°C 54 | DEBUG:target temp for layer #245 (height 72.95mm) is 212.0°C 55 | DEBUG:target temp for layer #275 (height 81.95mm) is 211.0°C 56 | DEBUG:target temp for layer #305 (height 90.95mm) is 210.0°C 57 | 58 | 59 | :: 60 | 61 | usage: gcode_tempcal [-h] [--min_z_change MIN_Z_CHANGE] [--continuous] 62 | [--steps STEPS] [--verbose] [--quiet] 63 | start_temp end_temp [infile] [outfile] 64 | 65 | Add temperature gradient to gcode program 66 | 67 | positional arguments: 68 | start_temp Initial temperature (best set to the default slicing 69 | temperature). For instance, for ABS you may want 240 70 | and 200 for PLA. 71 | end_temp End temperature for the gcode program. Usually lower 72 | than the initial temperature. Make sure that your 73 | material can be still be extruded at this temperature 74 | to avoid clogging your extruder. 75 | infile Program filename to be modified. Defaults to standard 76 | input. 77 | outfile Modified program with temperature gradient. Defaults 78 | to standard output. 79 | 80 | optional arguments: 81 | -h, --help show this help message and exit 82 | --min_z_change MIN_Z_CHANGE, -z MIN_Z_CHANGE 83 | Minimum height above which temperature gradient is 84 | created. If you have a special start sequence playing 85 | with temperatures, you may want to raise this to avoid 86 | overlapping of temperature. Defaults to 0.1mm which is 87 | compatible with NopHead ooze free unattended start 88 | sequence. 89 | --verbose, -v Verbose mode. It notably outputs the mapping between 90 | temperature and height if you have troubles figuring 91 | it out. 92 | --quiet, -q Quiet mode 93 | 94 | temperature control: 95 | --continuous, -c Switch to a continuous gradient generation where 96 | temperature is recomputed for every layer. You may 97 | want this in the case of very precise and fast hotend. 98 | Defaults to discrete temperature gradient divided in X 99 | steps. 100 | --steps STEPS, -s STEPS 101 | Number of steps used to create a discrete gradient 102 | when using the default gradient generation model. 103 | Defaults to 10 steps. This setting is not used when 104 | using the continuous gradient generation model. 105 | 106 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. GCodeutils documentation master file, created by 2 | sphinx-quickstart on Mon Jun 1 23:45:11 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to GCodeutils' documentation! 7 | ===================================== 8 | 9 | GCodeUtils is a collection of scripts to manipulate GCode, usually as filters, as in the Un*x philosophy. 10 | 11 | .. toctree:: 12 | :maxdepth: 1 13 | 14 | installation 15 | gcode_tempcal 16 | gcode_mod 17 | gcode_stretch 18 | gcode_optimize_arcs 19 | 20 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ------------ 3 | 4 | GCodeUtils is installable from PyPI with a single pip command:: 5 | 6 | pip install gcodeutils 7 | 8 | Alternatively, GCodeUtils can be run directly from sources after a git pull:: 9 | 10 | git clone https://github.com/zeograd/gcodeutils.git 11 | cd gcodeutils && python setup.py install 12 | 13 | Once GCodeUtils is installed, the .py files located in the cura_plugins 14 | subdirectory can be copied into the *plugins* directory of Cura to be callable 15 | from within Cura itself. 16 | -------------------------------------------------------------------------------- /gcodeutils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeograd/gcodeutils/f5f82e6cc2d5c0e4081c06f51b75442679b8455d/gcodeutils/__init__.py -------------------------------------------------------------------------------- /gcodeutils/filter/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'olivier' 2 | -------------------------------------------------------------------------------- /gcodeutils/filter/arc_optimizer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # GCodeUtils is distributed in the hope that it will be useful, 3 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 4 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 5 | # GNU General Public License for more details. 6 | # 7 | # You should have received a copy of the GNU General Public License 8 | # along with GCodeUtils. If not, see . 9 | 10 | import logging 11 | from math import sqrt, sin 12 | import cmath 13 | from gcodeutils.filter.filter import GCodeFilter 14 | from gcodeutils.gcoder import Line, move_gcodes, unsplit 15 | 16 | __author__ = 'Eyck Jentzsch ' 17 | 18 | # constraints for the detection algorithm 19 | MIN_SEGMENTS = 8 # number is segments forming an arc 20 | MAX_RADIUS = 200 # mm, maximum radius of the detectable circle 21 | ALIGNMENT_ERROR = 0.015 # 15 µm, max. offset a point might be off of the resulting circle 22 | PHASE_ERROR = 5*cmath.pi/180 # 5° in radian, max deviation of the angle steps forming a circle 23 | EXTRUSION_ERROR = 0.15 # 15%, maximum deviation of the extrusion for the segments 24 | EPSILON = 0.000001 # epsilon for zero comapriosn of float. everything less than will be treated as zero 25 | 26 | EXTRUSION_CORRECTION_LIMIT=0.01 # 1%, if the length of all segments deviates more than this from the legnth of the arc 27 | # a correction gcode is generated in absolute coordinate mode 28 | 29 | logger = logging.getLogger('arc_optimizer') 30 | 31 | class Point(object): 32 | """ 33 | an object representing a 2-dimensional point 34 | """ 35 | 36 | __slots__ = ('x', 'y') 37 | 38 | def __init__(self, x=0.0, y=0.0): 39 | self.x = x 40 | self.y = y 41 | 42 | def __add__(self, other): 43 | return Point(self.x + other.x, self.y + other.y) 44 | 45 | def __sub__(self, other): 46 | return Point(self.x - other.x, self.y - other.y) 47 | 48 | def __str__(self): 49 | return "Point(%.3f, %.3f)" % (self.x, self.y) 50 | 51 | def amplitude(self): 52 | return sqrt(self.x * self.x + self.y * self.y) 53 | 54 | def distance_to(self, other): 55 | xdiff = self.x - other.x 56 | xdiffsq = pow(xdiff, 2) 57 | ydiff = self.y - other.y 58 | ydiffsq = pow(ydiff, 2) 59 | return sqrt(xdiffsq + ydiffsq) 60 | 61 | 62 | class Circle(object): 63 | """ 64 | an object representing a cicle arc 65 | """ 66 | 67 | __slots__ = ('radius', 'center', 'direction', 'start', 'end') 68 | 69 | def __init__(self, radius=0.0, center=Point(0, 0), direction=0.0, start=Point(0, 0), end=Point(0, 0)): 70 | self.radius = radius 71 | self.center = center 72 | self.direction = direction 73 | self.start = start 74 | self.end = end 75 | 76 | def __str__(self): 77 | circle_str="Arc(r=%.5f, center=%s from %s to %s" % (self.radius, str(self.center), str(self.start), str(self.end)) 78 | return "CCW-" + circle_str if self.direction > 0.0 else "CW-" + circle_str 79 | 80 | 81 | class GCodeArcOptimizerFilter(GCodeFilter): 82 | """filter replacing subsequent G1 moves with G2/G3 (cirle c/cw if applicable""" 83 | 84 | queue = [] 85 | valid_circle = False 86 | 87 | def __init__(self): 88 | self.queue = [] 89 | 90 | @staticmethod 91 | def phase_diff(phase1, phase2): 92 | """ 93 | calculate the difference between to angles (in radian) starting a the positive x-axis 94 | 95 | asumption: we have MIN_SEGMENTS segments per cicle so the angle cannot be larget than 2*PI/MIN_SEGMENTS 96 | if angle is positive: diff=2*PI+p1-p2 if p1<0 & p2>0 else p1-p2 97 | if angle is negative: diff= p1-p2-2*PI if p2<0 && p1>0 else p1-p2 98 | 99 | :param phase1: angle 1 100 | :param phase2: angle 2 101 | :return: the difference 102 | """ 103 | diff = phase1 - phase2 104 | if diff < -cmath.pi: 105 | diff += 2 * cmath.pi 106 | elif diff > cmath.pi: 107 | diff -= 2 * cmath.pi 108 | return diff 109 | 110 | def opcode_filter(self, opcode): 111 | """ 112 | default implementation of the opcode filter 113 | :param opcode: the gcode of the current line 114 | :return: the modified opcode 115 | """ 116 | return opcode 117 | 118 | def parse_gcode(self, gcode, opcode_filter): 119 | for layer in gcode.all_layers: 120 | self.parse_layer(layer, opcode_filter) 121 | if len(self.queue) > 0: 122 | layer += self.queue 123 | 124 | def get_circle_least_squares(self): 125 | """ 126 | get cicle based on least-square error method 127 | :return: the estimated cicle 128 | """ 129 | count = len(self.queue) 130 | xbar = sum(i.current_x for i in self.queue) / count 131 | ybar = sum(i.current_y for i in self.queue) / count 132 | suu = 0 133 | suuu = 0 134 | suvv = 0 135 | suv = 0 136 | svv = 0 137 | svvv = 0 138 | svuu = 0 139 | for line in self.queue: 140 | pt = Point(line.current_x - xbar, line.current_y - ybar) 141 | suu += pt.x * pt.x 142 | suuu += pt.x * pt.x * pt.x 143 | suvv += pt.x * pt.y * pt.y 144 | suv += pt.x * pt.y 145 | svv += pt.y * pt.y 146 | svvv += pt.y * pt.y * pt.y 147 | svuu += pt.y * pt.x * pt.x 148 | if suu < EPSILON or svv < EPSILON: 149 | return None 150 | v = (((svvv + svuu) / 2) - ((suv / 2) * ((suuu + suvv) / suu))) / (((-(suv * suv)) / suu) + svv) 151 | u = (((suuu + suvv) / 2) - (v * suv)) / suu 152 | # calculate direction 153 | center = Point(x=u + xbar, y=v + ybar) 154 | p0 = complex(self.queue[0].current_x - center.x, self.queue[0].current_y - center.y) 155 | p1 = complex(self.queue[2].current_x - center.x, self.queue[2].current_y - center.y) 156 | return Circle(radius=sqrt((u * u) + (v * v) + ((suu + svv) / count)), 157 | center=center, 158 | direction=GCodeArcOptimizerFilter.phase_diff(cmath.phase(p1), cmath.phase(p0)), 159 | start=Point(self.queue[0].current_x, self.queue[0].current_y), 160 | end=Point(self.queue[-1].current_x, self.queue[-1].current_y)) 161 | 162 | def get_circle_radius_errors(self, circle): 163 | """ 164 | calculate the errors between the radius of the estimated circle and the distance of points on the circle to 165 | the center point 166 | :param circle: the estimated circle 167 | :return: list of error values 168 | """ 169 | errors = [] 170 | for line in self.queue: 171 | length = Point(circle.center.x - line.current_x, circle.center.y - line.current_y).amplitude() 172 | errors.append(abs(length - circle.radius)) 173 | return errors 174 | 175 | def get_phase_diffs(self, circle): 176 | """ 177 | get the angle between successive points 178 | :param circle: 179 | :return: 180 | """ 181 | center = circle.center 182 | phases = [cmath.phase(complex(line.current_x - center.x, line.current_y - center.y)) for line in self.queue] 183 | phase_diffs = [GCodeArcOptimizerFilter.phase_diff(phases[idx], phases[idx - 1]) for idx in 184 | range(1, len(phases))] 185 | return phase_diffs 186 | 187 | def get_circle_angle_errors(self, circle): 188 | """ 189 | calculate the angle errors between 3 consecutive points on the circle 190 | 191 | :param circle: the estimated circle 192 | :return: the list of angle errors 193 | """ 194 | phase_diffs = self.get_phase_diffs(circle) 195 | phase_diff_avg = sum(phase_diffs) / len(phase_diffs) 196 | phase_errors = [phase_diff - phase_diff_avg for phase_diff in phase_diffs] 197 | return phase_errors 198 | 199 | def get_circle(self): 200 | """ 201 | check if we have a valid circle. There are three criterias 202 | - all points have to be on the cicle arc (with max ALIGNMENT_ERROR variation) 203 | - all points are equidistant to each other (the angle between them is of same size with max PHASE_ERROR 204 | variation) 205 | :return: a tuple consisting of the bool indication an erroneous circle and the estimated circle 206 | """ 207 | circle = self.get_circle_least_squares() 208 | if circle is None or circle.radius>MAX_RADIUS: 209 | return True, circle 210 | radius_err = self.get_circle_radius_errors(circle) 211 | if any([error > ALIGNMENT_ERROR for error in radius_err]): 212 | return True, circle 213 | angle_err = self.get_circle_angle_errors(circle) 214 | return any([error > PHASE_ERROR for error in angle_err]), circle 215 | 216 | def get_distances(self): 217 | """ 218 | calculate extrusion and distances allong a path and its ratios 219 | :return: a map containing the values 220 | """ 221 | extrusions = { 222 | 'total': { 223 | 'path': 0, 224 | 'filament': 0, 225 | }, 226 | 'avg': { 227 | 'path': 0, 228 | 'filament': 0, 229 | 'ratio': 0, 230 | }, 231 | 'filament': {}, 232 | 'path': {}, 233 | 'ratio': {} 234 | } 235 | prev = None 236 | for num, line in enumerate(self.queue): 237 | if prev is not None: 238 | path = round(Point(line.current_x, line.current_y).distance_to( 239 | Point(prev.current_x, prev.current_y)), 7) 240 | extrusion = line.e if line.relative_e else (line.current_e - prev.current_e) 241 | extrusions['total']['filament'] += extrusion 242 | extrusions['total']['path'] += path 243 | extrusions['filament'][num] = extrusion 244 | extrusions['path'][num] = path 245 | if path < EPSILON: 246 | extrusions['ratio'][num] = 0 247 | else: 248 | extrusions['ratio'][num] = extrusions['filament'][num] / path 249 | prev = line 250 | extrusions['avg']['filament'] = extrusions['total']['filament'] / len(self.queue) 251 | extrusions['avg']['path'] = extrusions['total']['path'] / len(self.queue) 252 | extrusions['avg']['ratio'] = extrusions['total']['filament'] / extrusions['total']['path'] 253 | return extrusions 254 | 255 | def queue_valid(self): 256 | """ 257 | check if entries in the queue constitute a valid circle 258 | :return: a tuple consisting of the bool indication an erroneous circle and the estimated circle 259 | """ 260 | # check from 1 as idx 0 indicates the move to the start point 261 | all_e = False if self.queue[1].e is None else True 262 | cur_f = self.queue[1].current_f 263 | cur_z = self.queue[1].current_z 264 | valid_e = all([line.e is not None for line in self.queue[1:]]) if all_e else \ 265 | not any([line.e is not None for line in self.queue[1:]]) 266 | valid_f = all([line.current_f == cur_f for line in self.queue[1:]]) 267 | valid_z = all([line.current_z == cur_z for line in self.queue[1:]]) 268 | if not (valid_e and valid_f and valid_z): 269 | return True, None 270 | if all_e: 271 | extrusions = self.get_distances() 272 | if extrusions['avg']['ratio'] < EPSILON: 273 | return True, None 274 | valid = all(abs((curext / extrusions['avg']['ratio']) - 1) < EXTRUSION_ERROR for curext in 275 | extrusions['ratio'].values()) 276 | if not valid: 277 | return True, None 278 | return self.get_circle() 279 | 280 | def to_gcode(self): 281 | """ 282 | translate a sequence of segments into a circular gcode command 283 | :return: the gcode command 284 | """ 285 | result=[self.queue[0]] 286 | last = self.queue.pop() 287 | count = len(self.queue) 288 | end_point = self.queue[-1] 289 | error, circle = self.get_circle() 290 | extrusions = self.get_distances() 291 | op1 = Line() 292 | result.append(op1) 293 | op1.command = "G3" if circle.direction >0 else "G2" # G2 is CW, G3 CCW 294 | op1.x = round(circle.end.x, 3) 295 | op1.y = round(circle.end.y, 3) 296 | op1.i = round(circle.center.x - circle.start.x, 3) 297 | op1.j = round(circle.center.y - circle.start.y, 3) 298 | if end_point.relative_e: 299 | # calculate extrusion correction 300 | # arc length b = r·alpha (alpha in radian) 301 | # chord length s = 2·r·sin(alpha/2) 302 | # resulting arc extrusion is s0*b/s 303 | op1.e = sum([ s0*(circle.radius*alpha)/(2*circle.radius*sin(alpha/2)) 304 | for s0, alpha in zip(extrusions['filament'].values(), self.get_phase_diffs(circle))]) 305 | op1.relative_e = True 306 | else: 307 | # absolute mode: fall-back to the original length 308 | phase_diffs=self.get_phase_diffs(circle) 309 | arc_lens=[ alpha*circle.radius for alpha in phase_diffs] 310 | arc_extrusion_length=sum(arc_lens)*extrusions['avg']['ratio'] 311 | op1.e=self.queue[0].current_e+arc_extrusion_length 312 | op1.relative_e=False 313 | rel = arc_extrusion_length/extrusions['total']['filament'] 314 | if (rel - 1) > EXTRUSION_CORRECTION_LIMIT: 315 | op2= Line() 316 | op2.command="G92" 317 | op2.e = op2.current_e = end_point.current_e 318 | unsplit(op2) 319 | op2.raw += "; generated as arc to path relation is %f" % rel 320 | result.append(op2) 321 | op1.f = end_point.current_f 322 | unsplit(op1) 323 | op1.raw += "; generated from %s segments" % (count - 1) 324 | self.valid_circle = False 325 | result.append(last) 326 | logger.info(" generated arc from %s segments" % (count - 1)) 327 | logger.debug("arc is "+str(circle)) 328 | return result 329 | 330 | def opcode_filter(self, opcode): 331 | """ 332 | scan the sequence of opcodes for valid cirle arcs 333 | :param opcode: the opcode 334 | :return: the resulting opcode or a list of resulting opcodes 335 | """ 336 | if opcode.command is None and len(self.queue) > 0: 337 | opcode.current_x =self.queue[-1].current_x 338 | opcode.current_y = self.queue[-1].current_y 339 | opcode.current_z = self.queue[-1].current_z 340 | opcode.current_e = self.queue[-1].current_e 341 | opcode.current_f = self.queue[-1].current_f 342 | self.queue.append(opcode) 343 | count = len(self.queue) 344 | if count > MIN_SEGMENTS: 345 | if not self.queue[-1].command in move_gcodes: 346 | if self.valid_circle: 347 | # the last element inserted invalidated the circle, so process & flush 348 | result = self.to_gcode() 349 | else: 350 | # flush the queue as we have a GCode resetting the processing 351 | result = self.queue 352 | self.queue = [] 353 | return result 354 | else: 355 | error, circle = self.queue_valid() 356 | if error: 357 | if self.valid_circle: 358 | # the last element inserted invalidated the circle 359 | result = self.to_gcode() 360 | # since last elem was a move keep it in the queue 361 | self.queue = [result.pop()] 362 | return result 363 | else: 364 | return self.queue.pop(0) 365 | else: 366 | self.valid_circle = True 367 | return [] 368 | else: 369 | if self.queue[-1].command in move_gcodes or self.queue[-1].command is None: 370 | return [] 371 | else: 372 | # flush the queue as we have a GCode resetting the processing 373 | result = self.queue 374 | self.queue = [] 375 | self.valid_circle = False 376 | return result 377 | -------------------------------------------------------------------------------- /gcodeutils/filter/filter.py: -------------------------------------------------------------------------------- 1 | __author__ = 'olivier' 2 | 3 | 4 | class GCodeFilter(object): 5 | """abstract base filter class""" 6 | 7 | def opcode_filter(self, x): 8 | raise NotImplementedError 9 | 10 | def filter(self, gcode): 11 | self.parse_gcode(gcode, self.opcode_filter) 12 | 13 | def parse_gcode(self, gcode, opcode_filter): 14 | for layer in gcode.all_layers: 15 | self.parse_layer(layer, opcode_filter) 16 | 17 | def parse_layer(self, layer, opcode_filter): 18 | dirty_layer = False 19 | new_layer = [] 20 | for opcode in layer: 21 | opcode_filter_result = opcode_filter(opcode) 22 | 23 | if opcode_filter_result is not None: 24 | dirty_layer = True 25 | try: 26 | new_layer += opcode_filter_result 27 | except TypeError: 28 | new_layer.append(opcode_filter_result) 29 | else: 30 | new_layer.append(opcode) 31 | 32 | if dirty_layer: 33 | layer[:] = new_layer 34 | -------------------------------------------------------------------------------- /gcodeutils/filter/relative_extrusion.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from gcodeutils.filter.filter import GCodeFilter 4 | from gcodeutils.gcoder import GCODE_SET_POSITION_COMMAND, GCODE_RELATIVE_POSITIONING_COMMAND, move_gcodes, split, Line, \ 5 | GCODE_ABSOLUTE_EXTRUSION_COMMAND, \ 6 | GCODE_RELATIVE_EXTRUSION_COMMAND, raw_to_line, unsplit 7 | 8 | __author__ = 'olivier' 9 | 10 | 11 | class GCodeToRelativeExtrusionFilter(GCodeFilter): 12 | def __init__(self): 13 | self.relative_extrusion = False 14 | self.current_extrusion_distance = Decimal() 15 | 16 | def opcode_filter(self, opcode): 17 | if opcode.command == GCODE_RELATIVE_EXTRUSION_COMMAND: 18 | self.relative_extrusion = True 19 | return 20 | 21 | if opcode.command == GCODE_ABSOLUTE_EXTRUSION_COMMAND: 22 | self.relative_extrusion = False 23 | return raw_to_line(GCODE_RELATIVE_EXTRUSION_COMMAND) 24 | 25 | if opcode.command == GCODE_SET_POSITION_COMMAND: 26 | 27 | # when setting position, if E is set, use it, but if there is parameter, consider E=0 28 | if opcode.e is not None: 29 | self.current_extrusion_distance = Decimal(opcode.e) 30 | elif opcode.x is None and opcode.y is None and opcode.z is None: 31 | self.current_extrusion_distance = Decimal() 32 | 33 | return 34 | 35 | if opcode.command in move_gcodes and not self.relative_extrusion and opcode.e is not None: 36 | # we're extruding while in absolute extrusion mode, reduce by the amount extruded so far 37 | # and keep track of the current e for later reuse 38 | opcode.e, self.current_extrusion_distance = ( 39 | opcode.e - float(self.current_extrusion_distance), Decimal(opcode.e)) 40 | opcode.relative_e=True 41 | unsplit(opcode) 42 | return opcode 43 | -------------------------------------------------------------------------------- /gcodeutils/filter/translate.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from gcodeutils.filter.filter import GCodeFilter 4 | from gcodeutils.gcoder import move_gcodes, split, unsplit, GCODE_ABSOLUTE_POSITIONING_COMMAND, \ 5 | GCODE_RELATIVE_POSITIONING_COMMAND, GCODE_SET_POSITION_COMMAND, Line, raw_to_line 6 | 7 | __author__ = 'olivier' 8 | 9 | 10 | class GCodeXYTranslateFilter(GCodeFilter): 11 | """filter translating moves in the X/Y plane""" 12 | 13 | def __init__(self, x=None, y=None, **kwargs): 14 | self.translate_x = x or 0. 15 | self.translate_y = y or 0. 16 | 17 | self.first_move_after_home = False 18 | 19 | self.absolute_distance_mode = None # None if when it is unknown 20 | 21 | def generate_translation(self): 22 | return raw_to_line("G0 X%.4f Y%.4f" % (self.translate_x, self.translate_y)) 23 | 24 | def opcode_filter(self, opcode): 25 | if opcode.command in move_gcodes: 26 | 27 | if self.absolute_distance_mode is None: 28 | logging.warn('Move detected without absolute or related mode selected first') 29 | return 30 | 31 | if not self.absolute_distance_mode: 32 | 33 | # we're in relative mode, if the first move after a homing, translate quickly 34 | if self.first_move_after_home: 35 | self.first_move_after_home = False 36 | return [self.generate_translation(), opcode] 37 | 38 | return 39 | 40 | # at this point, we're an absolute move, we have to "hard patch" coordinate 41 | if opcode.x is not None and self.translate_x: 42 | opcode.x += self.translate_x 43 | unsplit(opcode) 44 | 45 | if opcode.y is not None and self.translate_y: 46 | opcode.y += self.translate_y 47 | unsplit(opcode) 48 | 49 | return opcode 50 | 51 | if opcode.command == GCODE_ABSOLUTE_POSITIONING_COMMAND: 52 | self.absolute_distance_mode = True 53 | return 54 | 55 | if opcode.command == GCODE_RELATIVE_POSITIONING_COMMAND: 56 | self.absolute_distance_mode = False 57 | return 58 | 59 | if opcode.command == GCODE_SET_POSITION_COMMAND: 60 | if opcode.x is None and opcode.y is None and opcode.z is None: 61 | # no coordinate given is equivalent to all 0 62 | opcode.x = - self.translate_x 63 | opcode.y = - self.translate_y 64 | 65 | # now, there is a new reference point, that we translated so there's nothing left to do until 66 | # the end of the program 67 | self.translate_x = self.translate_y = 0 68 | 69 | unsplit(opcode) 70 | return opcode 71 | 72 | if opcode.x is not None: 73 | opcode.x -= self.translate_x 74 | self.translate_x = 0 75 | 76 | if opcode.y is not None: 77 | opcode.y -= self.translate_y 78 | self.translate_y = 0 79 | 80 | unsplit(opcode) 81 | return opcode 82 | -------------------------------------------------------------------------------- /gcodeutils/gcode_mod.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """modify gcode program""" 4 | from __future__ import print_function 5 | from __future__ import division 6 | 7 | import argparse 8 | import logging 9 | import sys 10 | from gcodeutils.filter.relative_extrusion import GCodeToRelativeExtrusionFilter 11 | 12 | from gcodeutils.filter.translate import GCodeXYTranslateFilter 13 | 14 | from gcodeutils.visit.iterator import GCodeIterator 15 | from gcodeutils.visit.pause_at_layer import PauseAtLayer 16 | 17 | __author__ = 'Olivier Jolly , Joe Friedrichsen ' 18 | 19 | from gcodeutils.gcoder import GCode 20 | 21 | 22 | def main(): 23 | """command line entry point""" 24 | parser = argparse.ArgumentParser(description='Modify gcode program') 25 | parser.add_argument('-x', type=float, metavar='amount', 26 | help='Move all gcode program by units in the X axis.') 27 | parser.add_argument('-y', type=float, metavar='amount', 28 | help='Move all gcode program by units in the Y axis.') 29 | 30 | parser.add_argument('-e', action='count', default=0, help='Convert all extrusion to relative') 31 | 32 | parser.add_argument('-p', type=int, metavar='layer', 33 | help='Pause the gcode program at layer .') 34 | 35 | parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin, 36 | help='Program filename to be modified. Defaults to standard input.') 37 | parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout, 38 | help='Modified program. Defaults to standard output.') 39 | 40 | parser.add_argument('--verbose', '-v', action='count', default=1, 41 | help='Verbose mode') 42 | parser.add_argument('--quiet', '-q', action='count', default=0, help='Quiet mode') 43 | 44 | args = parser.parse_args() 45 | 46 | # count verbose and quiet flags to determine logging level 47 | args.verbose -= args.quiet 48 | 49 | if args.verbose > 1: 50 | logging.root.setLevel(logging.DEBUG) 51 | elif args.verbose > 0: 52 | logging.root.setLevel(logging.INFO) 53 | 54 | logging.basicConfig(format="%(levelname)s:%(message)s") 55 | 56 | # read original GCode 57 | gcode = GCode(args.infile.readlines()) 58 | 59 | if args.x is not None or args.y is not None: 60 | GCodeXYTranslateFilter(**vars(args)).filter(gcode) 61 | 62 | if args.e: 63 | GCodeToRelativeExtrusionFilter().filter(gcode) 64 | 65 | iterator = GCodeIterator(gcode) 66 | if args.p is not None: 67 | visitor = PauseAtLayer([args.p]) 68 | iterator.accept(visitor) 69 | 70 | # write back modified gcode 71 | gcode.write(args.outfile) 72 | 73 | 74 | if __name__ == "__main__": 75 | main() 76 | -------------------------------------------------------------------------------- /gcodeutils/gcode_optimize_arcs.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import sys 4 | 5 | from gcodeutils.filter.relative_extrusion import GCodeToRelativeExtrusionFilter 6 | from gcodeutils.gcoder import GCode 7 | from gcodeutils.filter.arc_optimizer import GCodeArcOptimizerFilter 8 | 9 | __author__ = 'Eyck Jentzsch ' 10 | 11 | 12 | def main(): 13 | """command line entry point""" 14 | parser = argparse.ArgumentParser(description='Modify GCode program to account arcs and replace the G1 with G2/G3') 15 | 16 | parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin, 17 | help='Program filename to be modified. Defaults to standard input.') 18 | parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout, 19 | help='Modified program. Defaults to standard output.') 20 | parser.add_argument('--inplace', '-i', action='store_true', help='Modify file inplace') 21 | 22 | parser.add_argument('--verbose', '-v', action='count', default=1, help='Verbose mode') 23 | parser.add_argument('--quiet', '-q', action='count', default=0, help='Quiet mode') 24 | 25 | args = parser.parse_args() 26 | 27 | # count verbose and quiet flags to determine logging level 28 | args.verbose -= args.quiet 29 | 30 | if args.verbose > 1: 31 | logging.root.setLevel(logging.DEBUG) 32 | elif args.verbose > 0: 33 | logging.root.setLevel(logging.INFO) 34 | 35 | logging.basicConfig(format="%(levelname)s:%(message)s") 36 | 37 | # read original GCode 38 | gcode = GCode(args.infile.readlines()) # pylint: disable=redefined-outer-name 39 | 40 | # First convert to relative extrusion 41 | # GCodeToRelativeExtrusionFilter().filter(gcode) 42 | 43 | # Then perform the stretching 44 | GCodeArcOptimizerFilter().filter(gcode) 45 | 46 | # write back modified gcode 47 | gcode.write(open(args.infile.name, 'w') if args.inplace is True and args.infile != sys.stdin else args.outfile) 48 | 49 | 50 | if __name__ == "__main__": 51 | main() 52 | -------------------------------------------------------------------------------- /gcodeutils/gcode_stretch.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import sys 4 | 5 | from gcodeutils.filter.relative_extrusion import GCodeToRelativeExtrusionFilter 6 | from gcodeutils.gcoder import GCode 7 | from gcodeutils.stretch.stretch import Slic3rStretchFilter, CuraStretchFilter 8 | 9 | __author__ = 'olivier' 10 | 11 | 12 | def is_cura_gcode(gcode): # pylint: disable=redefined-outer-name 13 | """Detect cura generated gcode by looking for the profile string""" 14 | for layer in gcode.all_layers: 15 | for opcode in layer: 16 | if 'CURA_PROFILE_STRING' in opcode.raw: 17 | return True 18 | return False 19 | 20 | 21 | def main(): 22 | """command line entry point""" 23 | parser = argparse.ArgumentParser(description='Modify GCode program to account for stretch and improve hole size') 24 | 25 | parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin, 26 | help='Program filename to be modified. Defaults to standard input.') 27 | parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout, 28 | help='Modified program. Defaults to standard output.') 29 | 30 | parser.add_argument('--cross_limit_distance_over_edge_width', type=float, default=5.0, 31 | help='Distance after and before a point where moves are considered to determine the local ' 32 | 'normal. Defaults to %(default)s. Set too low or too high, it might cause points no to be ' 33 | 'moved in the right direction.') 34 | parser.add_argument('--stretch_from_distance_over_edge_width', type=float, default=2.0, 35 | help='Distance after and before a point where moves are considered to determine the local ' 36 | 'normal. Defaults to %(default)s. Set too low or too high, it might cause points no to be ' 37 | 'moved in the right direction.') 38 | 39 | parser.add_argument('--loop_stretch_over_edge_width', type=float, default=0.11, 40 | help='Stretching strength for "loop" (extra shells), defaults to %(default)s') 41 | parser.add_argument('--edge_inside_stretch_over_edge_width', type=float, default=0.32, 42 | help='Stretching strength for "inner perimeter", defaults to %(default)s') 43 | parser.add_argument('--edge_outside_stretch_over_edge_width', type=float, default=0.1, 44 | help='Stretching strength for "outer perimeter", defaults to %(default)s') 45 | 46 | parser.add_argument('--stretch_strength', type=float, default=1.0, 47 | help='Stretching stretch factor. This is the first setting you\'ll want to change to ' 48 | 'modify the hole size') 49 | 50 | parser.add_argument('--verbose', '-v', action='count', default=1, 51 | help='Verbose mode') 52 | parser.add_argument('--quiet', '-q', action='count', default=0, help='Quiet mode') 53 | 54 | args = parser.parse_args() 55 | 56 | # count verbose and quiet flags to determine logging level 57 | args.verbose -= args.quiet 58 | 59 | if args.verbose > 1: 60 | logging.root.setLevel(logging.DEBUG) 61 | elif args.verbose > 0: 62 | logging.root.setLevel(logging.INFO) 63 | 64 | logging.basicConfig(format="%(levelname)s:%(message)s") 65 | 66 | # read original GCode 67 | gcode = GCode(args.infile.readlines()) # pylint: disable=redefined-outer-name 68 | 69 | # First convert to relative extrusion 70 | GCodeToRelativeExtrusionFilter().filter(gcode) 71 | 72 | # Then perform the stretching 73 | if is_cura_gcode(gcode): 74 | CuraStretchFilter(**vars(args)).filter(gcode) 75 | else: 76 | Slic3rStretchFilter(**vars(args)).filter(gcode) 77 | 78 | # write back modified gcode 79 | gcode.write(args.outfile) 80 | 81 | 82 | if __name__ == "__main__": 83 | main() 84 | -------------------------------------------------------------------------------- /gcodeutils/gcode_tempcal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """Add temperature gradient to gcode program to create unattended temperature calibration program""" 4 | from __future__ import print_function 5 | from __future__ import division 6 | 7 | import argparse 8 | import logging 9 | import sys 10 | 11 | __author__ = 'Olivier Jolly ' 12 | 13 | from gcoder import GCode # pylint: disable=relative-import 14 | 15 | 16 | class GCodeTempGradient(object): # pylint: disable=too-many-instance-attributes 17 | """Alter gcode to inject temperature changes along Z""" 18 | 19 | ABSOLUTE_MIN_TEMPERATURE = 150 20 | ABSOLUTE_MAX_TEMPERATURE = 250 21 | 22 | def __init__(self, gcode, start_temp, end_temp, min_z_change, **kwargs): 23 | self.gcode = gcode 24 | self.start_temp = start_temp 25 | self.end_temp = end_temp 26 | self.min_z_change = min_z_change 27 | 28 | self.zmax = gcode.zmax 29 | self.zmin = None 30 | 31 | self.last_target_temperature = None 32 | self.current_z = None 33 | 34 | def generate_temperature_gcode(self, temperature): 35 | """Return a gcode line for the given temperature, under condition that the temperature 36 | is between the security absolute temperature bounds and that the temperature rounded 37 | to the nearest 0.1°C is different from the previous one (to avoid spurious gcode generation 38 | in vase mode)""" 39 | if self.ABSOLUTE_MIN_TEMPERATURE <= temperature <= self.ABSOLUTE_MAX_TEMPERATURE: 40 | # round the temperature to the nearest 0.1°C first 41 | rounded_temperature = "%.1f" % temperature 42 | 43 | return "M104 S{}".format(rounded_temperature) 44 | 45 | return "" 46 | 47 | def _parse_gcode(self): 48 | """parse gcode to detect Z bounds""" 49 | for layer_idx, layer in enumerate(self.gcode.all_layers): 50 | 51 | # don't parse layer without any instruction 52 | if layer: 53 | current_z = layer[0].current_z 54 | 55 | if current_z is not None: 56 | logging.debug("layer #%d altitude is %.2fmm", layer_idx, current_z) 57 | 58 | # Keep the lowest Z which is above the minimum height (used to keep the slicer first layers 59 | # temperature for adhesion) 60 | if self.zmin is None or ( 61 | current_z is not None and self.min_z_change < current_z < self.zmin): 62 | self.zmin = current_z 63 | 64 | # Make sure that the sliced model is high enough to be usable 65 | if self.zmin is None: 66 | raise RuntimeError("Height is too small to create temperature gradient (no layer found ?)") 67 | 68 | logging.info( 69 | "temperature gradient from %.1f°C, altitude %.2fmm to %.1f°C, altitude %.2fmm ", self.start_temp, self.zmin, 70 | self.end_temp, self.zmax) 71 | 72 | if self.zmin >= self.zmax: 73 | raise RuntimeError( 74 | "Height is too small to create temperature gradient (all operation are below {}mm ?)".format( 75 | self.min_z_change)) 76 | 77 | def write(self, output_file=sys.stdout): 78 | """Write the modified GCode""" 79 | 80 | self._parse_gcode() 81 | 82 | # spit back the original GCode with temperature GCode injected accordingly to their Z 83 | for layer_idx, layer in enumerate(self.gcode.all_layers): 84 | 85 | if layer: 86 | self.current_z = layer[0].current_z 87 | 88 | if self.current_z and self.zmin <= self.current_z <= self.zmax: 89 | 90 | raw_target_temp = self.get_temp_for_current_layer() 91 | if raw_target_temp is not None: 92 | target_temp = round(raw_target_temp, 1) 93 | 94 | # don't generate temperature change if same as last layer 95 | if target_temp != self.last_target_temperature: 96 | self.last_target_temperature = target_temp 97 | 98 | logging.debug("target temp for layer #%d (height %.2fmm) is %.1f°C", layer_idx, 99 | self.current_z, target_temp) 100 | print(self.generate_temperature_gcode(target_temp), file=output_file) 101 | 102 | for line in layer: 103 | print(line.raw, file=output_file) 104 | 105 | def get_temp_for_current_layer(self): 106 | """return the target temperature for the current Z (as found in self.current_z)""" 107 | raise NotImplementedError 108 | 109 | 110 | class GCodeContinuousTempGradient(GCodeTempGradient): 111 | """Change continuously temperature. Only makes sense with precise and fast hotends.""" 112 | 113 | def __init__(self, gcode, **kwargs): 114 | super(GCodeContinuousTempGradient, self).__init__(gcode, **kwargs) 115 | self.delta_temp_per_z = None 116 | 117 | def _parse_gcode(self): 118 | super(GCodeContinuousTempGradient, self)._parse_gcode() 119 | 120 | # precompute temperature change per Z unit 121 | self.delta_temp_per_z = (self.end_temp - self.start_temp) / (self.zmax - self.zmin) 122 | 123 | def get_temp_for_current_layer(self): 124 | return self.start_temp + self.delta_temp_per_z * (self.current_z - self.zmin) 125 | 126 | 127 | class GCodeStepTempGradient(GCodeTempGradient): 128 | """Change temperature by a given number of steps""" 129 | 130 | def __init__(self, gcode, **kwargs): 131 | super(GCodeStepTempGradient, self).__init__(gcode, **kwargs) 132 | self.steps = kwargs['steps'] 133 | 134 | self.step_end_temp = self.end_temp + (self.end_temp - self.start_temp) / self.steps 135 | self.steps += 1 136 | 137 | self.max_temp = max(self.end_temp, self.start_temp) 138 | 139 | def get_temp_for_current_layer(self): 140 | progress = float(((self.current_z - self.zmin) / (self.zmax - self.zmin)) * self.steps) / self.steps 141 | return min(self.max_temp, self.start_temp + progress * (self.step_end_temp - self.start_temp)) 142 | 143 | 144 | def main(): 145 | """command line entry point""" 146 | parser = argparse.ArgumentParser(description='Add temperature gradient to gcode program') 147 | parser.add_argument('start_temp', type=int, 148 | help='Initial temperature (best set to the default slicing temperature).' 149 | ' For instance, for ABS you may want 240 and 200 for PLA.') 150 | parser.add_argument('end_temp', type=int, 151 | help='End temperature for the gcode program. Usually lower than the initial temperature. ' 152 | 'Make sure that your material can be still be extruded at this temperature ' 153 | 'to avoid clogging your extruder.') 154 | parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin, 155 | help='Program filename to be modified. Defaults to standard input.') 156 | parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout, 157 | help='Modified program with temperature gradient. Defaults to standard output.') 158 | 159 | parser.add_argument('--min_z_change', '-z', type=float, default=0.1, 160 | help='Minimum height above which temperature gradient is created. ' 161 | 'If you have a special start sequence playing with temperatures, you may want to raise ' 162 | 'this to avoid overlapping of temperature. Defaults to %(default)smm which is compatible ' 163 | 'with NopHead ooze free unattended start sequence.') 164 | 165 | temperature_control = parser.add_argument_group('temperature control') 166 | temperature_control.add_argument('--continuous', '-c', action='store_const', const=GCodeContinuousTempGradient, 167 | dest='gcode_grad_class', default=GCodeStepTempGradient, 168 | help='Switch to a continuous gradient generation where temperature is recomputed ' 169 | 'for every layer. You may want this in the case of very precise and fast ' 170 | 'hotend. Defaults to discrete temperature gradient divided in X steps.') 171 | temperature_control.add_argument('--steps', '-s', default=10, 172 | help='Number of steps used to create a discrete gradient when using the default ' 173 | 'gradient generation model. Defaults to %(default)s steps. This setting is ' 174 | 'not used when using the continuous gradient generation model.') 175 | 176 | parser.add_argument('--verbose', '-v', action='count', default=1, 177 | help='Verbose mode. It notably outputs the mapping between temperature and height if you have ' 178 | 'troubles figuring it out.') 179 | parser.add_argument('--quiet', '-q', action='count', default=0, help='Quiet mode') 180 | 181 | args = parser.parse_args() 182 | 183 | # count verbose and quiet flags to determine logging level 184 | args.verbose -= args.quiet 185 | 186 | if args.verbose > 1: 187 | logging.root.setLevel(logging.DEBUG) 188 | elif args.verbose > 0: 189 | logging.root.setLevel(logging.INFO) 190 | 191 | logging.basicConfig(format="%(levelname)s:%(message)s") 192 | 193 | # read original GCode 194 | gcode = GCode(args.infile.readlines()) 195 | 196 | # Alter and write back modified GCode 197 | temp_gradient = args.gcode_grad_class(gcode=gcode, **vars(args)) 198 | temp_gradient.write(args.outfile) 199 | 200 | 201 | if __name__ == "__main__": 202 | main() 203 | -------------------------------------------------------------------------------- /gcodeutils/stretch/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'olivier' 2 | -------------------------------------------------------------------------------- /gcodeutils/stretch/stretch.py: -------------------------------------------------------------------------------- 1 | """ 2 | This page is in the table of contents. 3 | Stretch is very important Skeinforge plugin that allows you to partially compensate for the fact that extruded holes are 4 | smaller then they should be. It stretches the threads to partially compensate for filament shrinkage when extruded. 5 | 6 | The stretch manual page is at: 7 | http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Stretch 8 | 9 | Extruded holes are smaller than the model because while printing an arc the head is depositing filament on both sides 10 | of the arc but in the inside of the arc you actually need less material then on the outside of the arc. You can read 11 | more about this on the RepRap ArcCompensation page: 12 | http://reprap.org/bin/view/Main/ArcCompensation 13 | 14 | In general, stretch will widen holes and push corners out. In practice the filament contraction will not be identical 15 | to the algorithm, so even once the optimal parameters are determined, the stretch script will not be able to eliminate 16 | the inaccuracies caused by contraction, but it should reduce them. 17 | 18 | All the defaults assume that the thread sequence choice setting in fill is the edge being extruded first, then the 19 | loops, then the infill. If the thread sequence choice is different, the optimal thread parameters will also be 20 | different. In general, if the infill is extruded first, the infill would have to be stretched more so that even after 21 | the filament shrinkage, it would still be long enough to connect to the loop or edge. 22 | 23 | Holes should be made with the correct area for their radius. In other words, for example if your modeling program 24 | approximates a hole of radius one (area = pi) by making a square with the points at [(1,0), (0,1), (-1,0), (0,-1)] 25 | (area = 2), the radius should be increased by sqrt(pi/2). This can be done in fabmetheus xml by writing: 26 | radiusAreal='True' 27 | 28 | in the attributes of the object or any parent of that object. In other modeling programs, you'll have to this manually 29 | or make a script. If area compensation is not done, then changing the stretch parameters to over compensate for too 30 | small hole areas will lead to incorrect compensation in other shapes. 31 | 32 | ==Settings== 33 | ===Loop Stretch Over Perimeter Width=== 34 | Default is 0.1. 35 | 36 | Defines the ratio of the maximum amount the loop aka inner shell threads will be stretched compared to the edge width, 37 | in general this value should be the same as the 'Perimeter Outside Stretch Over Perimeter Width' setting. 38 | 39 | ===Path Stretch Over Perimeter Width=== 40 | Default is zero. 41 | 42 | Defines the ratio of the maximum amount the threads which are not loops, like the infill threads, will be stretched 43 | compared to the edge width. 44 | 45 | ===Perimeter=== 46 | ====Perimeter Inside Stretch Over Perimeter Width==== 47 | Default is 0.32. 48 | 49 | Defines the ratio of the maximum amount the inside edge thread will be stretched compared to the edge width, this is 50 | the most important setting in stretch. The higher the value the more it will stretch the edge and the wider holes will 51 | be. If the value is too small, the holes could be drilled out after fabrication, if the value is too high, the holes 52 | would be too wide and the part would have to junked. 53 | 54 | ====Perimeter Outside Stretch Over Perimeter Width==== 55 | Default is 0.1. 56 | 57 | Defines the ratio of the maximum amount the outside edge thread will be stretched compared to the edge width, in 58 | general this value should be around a third of the 'Perimeter Inside Stretch Over Perimeter Width' setting. 59 | 60 | ===Stretch from Distance over Perimeter Width=== 61 | Default is two. 62 | 63 | The stretch algorithm works by checking at each turning point on the extrusion path what the direction of the thread 64 | is at a distance of 'Stretch from Distance over Perimeter Width' times the edge width, on both sides, and moves the 65 | thread in the opposite direction. So it takes the current turning-point, goes 66 | "Stretch from Distance over Perimeter Width" * "Perimeter Width" ahead, reads the direction at that point. Then it 67 | goes the same distance in back in time, reads the direction at that other point. It then moves the thread in the 68 | opposite direction, away from the center of the arc formed by these 2 points+directions. 69 | 70 | The magnitude of the stretch increases with: 71 | the amount that the direction of the two threads is similar and 72 | by the '..Stretch Over Perimeter Width' ratio. 73 | 74 | ==Examples== 75 | The following examples stretch the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder 76 | which contains Screw Holder Bottom.stl and stretch.py. 77 | 78 | > python stretch.py 79 | This brings up the stretch dialog. 80 | 81 | > python stretch.py Screw Holder Bottom.stl 82 | The stretch tool is parsing the file: 83 | Screw Holder Bottom.stl 84 | .. 85 | The stretch tool has created the file: 86 | .. Screw Holder Bottom_stretch.gcode 87 | 88 | """ 89 | 90 | from __future__ import absolute_import 91 | import base64 92 | import logging 93 | import zlib 94 | 95 | import re 96 | 97 | from gcodeutils.gcoder import split, Line, parse_coordinates, unsplit, linear_move_gcodes 98 | from .vector3 import Vector3 99 | 100 | __author__ = 'Enrique Perez (perez_enrique@yahoo.com)' 101 | __date__ = '$Date: 2008/21/04 $' 102 | __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' 103 | 104 | try: 105 | xrange 106 | except NameError: 107 | xrange = range 108 | 109 | 110 | def get_location_from_line(old_location, line): 111 | """Get the location from a GCode line, carrying over existing location.""" 112 | if old_location is None: 113 | old_location = Vector3() 114 | 115 | return Vector3( 116 | line.x if line.x is not None else old_location.x, 117 | line.y if line.y is not None else old_location.y, 118 | line.z if line.z is not None else old_location.z, 119 | ) 120 | 121 | 122 | def dot_product(lhs, rhs): 123 | """Get the dot product of a pair of complexes.""" 124 | return lhs.real * rhs.real + lhs.imag * rhs.imag 125 | 126 | 127 | class LineIteratorForwardLegacy(object): 128 | """Forward line iterator class.""" 129 | 130 | logger = logging.getLogger('iterator') 131 | 132 | def __init__(self, line_index, lines): 133 | self.first_visited_index = None 134 | self.line_index = line_index 135 | self.first_visited_index = None 136 | self.lines = lines 137 | self.increment = 1 138 | self.stop_on_extrusion_off = True 139 | 140 | self.logger.debug("started iterator with line_index = %d", self.line_index) 141 | 142 | def index_setup(self): 143 | pass 144 | 145 | def reset_index_on_limit(self): 146 | """Get index just after the activate command.""" 147 | self.logger.debug("reset index forward") 148 | for lineIndex in xrange(self.line_index - 1, 0, - 1): 149 | if StretchFilter.EXTRUSION_ON_MARKER in self.lines[lineIndex].raw: 150 | return lineIndex + 1 151 | print('This should never happen in stretch, no activate command was found for this thread.') 152 | raise StopIteration("You've reached the end of the line.") 153 | 154 | def index_in_valid_range(self): 155 | return 0 <= self.line_index < len(self.lines) 156 | 157 | def get_next(self): 158 | """Get next line going backward or raise exception.""" 159 | 160 | self.index_setup() 161 | 162 | while self.index_in_valid_range(): 163 | 164 | if self.line_index == self.first_visited_index: 165 | self.logger.debug("infinite looping detected") 166 | raise StopIteration("You've reached the end of the line.") 167 | if self.first_visited_index is None: 168 | self.first_visited_index = self.line_index 169 | 170 | line = self.lines[self.line_index] 171 | if StretchFilter.EXTRUSION_OFF_MARKER in line.raw and self.stop_on_extrusion_off: 172 | self.line_index = self.reset_index_on_limit() 173 | continue 174 | 175 | self.line_index += self.increment 176 | if line.command in linear_move_gcodes and (line.x is not None or line.y is not None): 177 | self.logger.debug("found (%d) %s %s", self.increment, line.x, line.y) 178 | return line 179 | 180 | self.logger.debug("no more point in loop") 181 | raise StopIteration("You've reached the end of the line.") 182 | 183 | 184 | class LineIteratorBackwardLegacy(LineIteratorForwardLegacy): 185 | """Backward line iterator class.""" 186 | 187 | def __init__(self, line_index, lines): 188 | super(LineIteratorBackwardLegacy, self).__init__(line_index, lines) 189 | self.increment = -1 190 | 191 | def index_setup(self): 192 | if self.line_index < 1: 193 | self.line_index = self.reset_index_on_limit() 194 | 195 | def reset_index_on_limit(self): 196 | """Get index two lines before the deactivate command.""" 197 | 198 | self.logger.debug("reset index backward") 199 | 200 | for lineIndex in xrange(self.line_index + 1, len(self.lines)): 201 | if StretchFilter.EXTRUSION_OFF_MARKER in self.lines[lineIndex].raw: 202 | return lineIndex - 2 203 | print('This should never happen in stretch, no deactivate command was found for this thread.') 204 | raise StopIteration("You've reached the end of the line.") 205 | 206 | 207 | class LineIteratorForward(LineIteratorForwardLegacy): 208 | def reset_index_on_limit(self): 209 | """Get index just after the activate command.""" 210 | self.logger.debug("reset index forward (modern)") 211 | for lineIndex in xrange(self.line_index - 1, -1, - 1): 212 | if StretchFilter.LOOP_START_MARKER in self.lines[lineIndex].raw: 213 | return lineIndex + 1 214 | print('This should never happen in stretch, no activate command was found for this thread.') 215 | raise StopIteration("You've reached the end of the line.") 216 | 217 | 218 | class CuraLineIteratorForward(LineIteratorForwardLegacy): 219 | def __init__(self, line_index, lines): 220 | super(CuraLineIteratorForward, self).__init__(line_index, lines) 221 | self.stop_on_extrusion_off = False 222 | 223 | def index_setup(self): 224 | if StretchFilter.LOOP_STOP_MARKER in self.lines[self.line_index].raw: 225 | self.line_index = self.reset_index_on_limit() 226 | 227 | def reset_index_on_limit(self): 228 | """Get index just after the activate command.""" 229 | self.logger.debug("reset index forward (modern)") 230 | for lineIndex in xrange(self.line_index - 1, -1, - 1): 231 | if StretchFilter.LOOP_START_MARKER in self.lines[lineIndex].raw: 232 | return lineIndex 233 | print('This should never happen in stretch, no activate command was found for this thread.') 234 | raise StopIteration("You've reached the end of the line.") 235 | 236 | 237 | class LineIteratorBackward(LineIteratorBackwardLegacy): 238 | def index_setup(self): 239 | if self.line_index < 0: 240 | self.line_index = self.reset_index_on_limit() 241 | elif StretchFilter.LOOP_START_MARKER in self.lines[self.line_index + 1].raw: # if just before a loop start 242 | self.line_index = self.reset_index_on_limit() 243 | 244 | def reset_index_on_limit(self): 245 | """Get index two lines before the deactivate command.""" 246 | 247 | self.logger.debug("reset index backward (modern)") 248 | 249 | for lineIndex in xrange(self.line_index + 1, len(self.lines)): 250 | if StretchFilter.EXTRUSION_OFF_MARKER in self.lines[lineIndex].raw: 251 | return lineIndex - 2 252 | print('This should never happen in stretch, no deactivate command was found for this thread.') 253 | raise StopIteration("You've reached the end of the line.") 254 | 255 | 256 | class CuraLineIteratorBackward(LineIteratorBackwardLegacy): 257 | def __init__(self, line_index, lines): 258 | super(CuraLineIteratorBackward, self).__init__(line_index, lines) 259 | self.stop_on_extrusion_off = False 260 | 261 | def index_setup(self): 262 | if self.line_index < 0: 263 | self.line_index = self.reset_index_on_limit() 264 | elif StretchFilter.LOOP_START_MARKER in self.lines[self.line_index + 1].raw: # if just before a loop start 265 | self.line_index = self.reset_index_on_limit() 266 | 267 | def reset_index_on_limit(self): 268 | """Get index two lines before the deactivate command.""" 269 | 270 | self.logger.debug("reset index backward (modern)") 271 | 272 | for lineIndex in xrange(self.line_index + 1, len(self.lines)): 273 | if StretchFilter.LOOP_STOP_MARKER in self.lines[lineIndex].raw: 274 | return lineIndex - 1 275 | print('This should never happen in stretch, no deactivate command was found for this thread.') 276 | raise StopIteration("You've reached the end of the line.") 277 | 278 | 279 | class StretchRepository: 280 | """A class to handle the stretch settings.""" 281 | 282 | def __init__(self, cross_limit_distance_over_edge_width=5.0, loop_stretch_over_edge_width=0.11, 283 | edge_inside_stretch_over_edge_width=0.32, edge_outside_stretch_over_edge_width=0.1, 284 | stretch_from_distance_over_edge_width=2.0, stretch_strength=1.0, **kwargs): 285 | """Set the default settings.""" 286 | 287 | # Cross Limit Distance Over Perimeter Width (ratio) 288 | self.crossLimitDistanceOverEdgeWidth = cross_limit_distance_over_edge_width 289 | 290 | self.loopStretchOverEdgeWidth = loop_stretch_over_edge_width * stretch_strength 291 | self.edgeInsideStretchOverEdgeWidth = edge_inside_stretch_over_edge_width * stretch_strength 292 | self.edgeOutsideStretchOverEdgeWidth = edge_outside_stretch_over_edge_width * stretch_strength 293 | 294 | # Stretch From Distance Over Perimeter Width (ratio) 295 | self.stretchFromDistanceOverEdgeWidth = stretch_from_distance_over_edge_width 296 | 297 | 298 | class StretchFilter: 299 | """A class to stretch a skein of extrusions.""" 300 | 301 | EXTRUSION_ON_MARKER = 'stretch-extrusion-on' 302 | EXTRUSION_OFF_MARKER = 'stretch-extrusion-off' 303 | LOOP_START_MARKER = 'stretch-loop-start' 304 | INNER_EDGE_START_MARKER = LOOP_START_MARKER + ' stretch-inner-edge-start' 305 | OUTER_EDGE_START_MARKER = LOOP_START_MARKER + ' stretch-outer-edge-start' 306 | LOOP_STOP_MARKER = 'stretch-loop-stop' 307 | 308 | def __init__(self, **kwargs): 309 | self.edgeWidth = 0.4 310 | self.extruderActive = False 311 | self.feedRateMinute = 959.0 312 | self.isLoop = False 313 | self.oldLocation = None 314 | self.gcode = None 315 | self.current_layer = None 316 | self.line_number_in_layer = 0 317 | self.stretchRepository = StretchRepository(**kwargs) 318 | 319 | self.thread_maximum_absolute_stretch = 0 320 | 321 | self.line_forward_iterator = LineIteratorForwardLegacy 322 | self.line_backward_iterator = LineIteratorBackwardLegacy 323 | 324 | def filter(self, gcode): 325 | """Parse gcode text and store the stretch gcode.""" 326 | self.gcode = gcode 327 | 328 | self.setup_filter() 329 | 330 | for self.current_layer_index, current_layer in enumerate(self.gcode.all_layers): 331 | self.current_layer = current_layer[:] 332 | for self.line_number_in_layer, line in enumerate(self.current_layer): 333 | gcode_line = self.parse_line(line) 334 | parse_coordinates(gcode_line, split(gcode_line)) 335 | self.gcode.all_layers[self.current_layer_index][self.line_number_in_layer] = gcode_line 336 | 337 | def get_cross_limited_stretch(self, crossLimitedStretch, crossLineIterator, locationComplex): 338 | """Get cross limited relative stretch for a location.""" 339 | try: 340 | line = crossLineIterator.get_next() 341 | except StopIteration: 342 | return crossLimitedStretch 343 | pointComplex = get_location_from_line(self.oldLocation, line).dropAxis() 344 | pointMinusLocation = locationComplex - pointComplex 345 | pointMinusLocationLength = abs(pointMinusLocation) 346 | if pointMinusLocationLength <= self.crossLimitDistanceFraction: 347 | return crossLimitedStretch 348 | parallelNormal = pointMinusLocation / pointMinusLocationLength 349 | parallelStretch = dot_product(parallelNormal, crossLimitedStretch) * parallelNormal 350 | if pointMinusLocationLength > self.crossLimitDistance: 351 | return parallelStretch 352 | crossNormal = complex(parallelNormal.imag, - parallelNormal.real) 353 | crossStretch = dot_product(crossNormal, crossLimitedStretch) * crossNormal 354 | crossPortion = (self.crossLimitDistance - pointMinusLocationLength) / self.crossLimitDistanceRemainder 355 | return parallelStretch + crossStretch * crossPortion 356 | 357 | def get_relative_stretch(self, locationComplex, lineIterator): 358 | """Get relative stretch for a location.""" 359 | lastLocationComplex = locationComplex 360 | oldTotalLength = 0.0 361 | pointComplex = locationComplex 362 | totalLength = 0.0 363 | while 1: 364 | try: 365 | line = lineIterator.get_next() 366 | except StopIteration: 367 | locationMinusPoint = locationComplex - pointComplex 368 | locationMinusPointLength = abs(locationMinusPoint) 369 | if locationMinusPointLength > 0.0: 370 | return locationMinusPoint / locationMinusPointLength 371 | return complex() 372 | pointComplex = get_location_from_line(self.oldLocation, line).dropAxis() 373 | locationMinusPoint = lastLocationComplex - pointComplex 374 | locationMinusPointLength = abs(locationMinusPoint) 375 | totalLength += locationMinusPointLength 376 | 377 | logging.debug("total length: %d, stretchFromDistance: %f", totalLength, self.stretchFromDistance) 378 | 379 | if totalLength >= self.stretchFromDistance: 380 | distanceFromRatio = (self.stretchFromDistance - oldTotalLength) / locationMinusPointLength 381 | totalPoint = distanceFromRatio * pointComplex + (1.0 - distanceFromRatio) * lastLocationComplex 382 | locationMinusTotalPoint = locationComplex - totalPoint 383 | return locationMinusTotalPoint / self.stretchFromDistance 384 | lastLocationComplex = pointComplex 385 | oldTotalLength = totalLength 386 | 387 | def stretch_line(self, line): 388 | """Get stretched gcode line.""" 389 | location = get_location_from_line(self.oldLocation, line) 390 | self.feedRateMinute = line.f or self.feedRateMinute 391 | self.oldLocation = location 392 | 393 | # if thread_maximum_absolute_stretch is set (ie within a loop) and we're extruding or after to do so, 394 | # adjust the point location to account for stretching 395 | if self.thread_maximum_absolute_stretch > 0.0: 396 | return self.get_stretched_line_from_index_location(self.line_number_in_layer - 1, 397 | self.line_number_in_layer + 1, 398 | location, 399 | line) 400 | return line 401 | 402 | def get_stretched_line_from_index_location(self, indexPreviousStart, indexNextStart, location, original_line): 403 | """Get stretched gcode line from line index and location.""" 404 | crossIteratorForward = self.line_forward_iterator(indexNextStart, self.current_layer) 405 | crossIteratorBackward = self.line_backward_iterator(indexPreviousStart, self.current_layer) 406 | iteratorForward = self.line_forward_iterator(indexNextStart, self.current_layer) 407 | iteratorBackward = self.line_backward_iterator(indexPreviousStart, self.current_layer) 408 | 409 | locationComplex = location.dropAxis() 410 | 411 | logging.debug("original point to stretch: %s", locationComplex) 412 | 413 | relativeStretch = self.get_relative_stretch(locationComplex, iteratorForward) \ 414 | + self.get_relative_stretch(locationComplex, iteratorBackward) 415 | relativeStretch *= 0.8 416 | relativeStretch = self.get_cross_limited_stretch(relativeStretch, crossIteratorForward, locationComplex) 417 | relativeStretch = self.get_cross_limited_stretch(relativeStretch, crossIteratorBackward, locationComplex) 418 | 419 | relativeStretchLength = abs(relativeStretch) 420 | 421 | if relativeStretchLength > 1.0: 422 | relativeStretch /= relativeStretchLength 423 | 424 | logging.debug("relativeStretchLength: %f", relativeStretchLength) 425 | 426 | absoluteStretch = relativeStretch * self.thread_maximum_absolute_stretch 427 | stretchedPoint = location.dropAxis() + absoluteStretch 428 | 429 | result = Line() 430 | result.command = original_line.command 431 | result.x = stretchedPoint.real 432 | result.y = stretchedPoint.imag 433 | result.z = original_line.z 434 | result.f = self.feedRateMinute 435 | 436 | # TODO improve new extrusion length computation. It's clearly a very rough estimate 437 | if original_line.e is not None: 438 | result.e = original_line.e * (1 - abs(absoluteStretch)) 439 | 440 | unsplit(result) 441 | 442 | logging.debug("stretched point: %f %f", result.x, result.y) 443 | 444 | return result 445 | 446 | def is_just_before_extrusion(self): 447 | """Determine if activate command is before linear move command.""" 448 | for line in self.current_layer[self.line_number_in_layer + 1:]: 449 | if line.command in linear_move_gcodes or self.EXTRUSION_OFF_MARKER in line.raw: 450 | return False 451 | if self.EXTRUSION_ON_MARKER in line.raw: 452 | return True 453 | return False 454 | 455 | def set_edge_width(self, edge_width): 456 | # In the original code, the edge width found in the GCode was only used to recompute the 457 | # stretchFromDistance. 458 | # It does seem like either a typo or a hack around a problem I've yet to bump into. 459 | # For now, I'll apply the edge width to recompute all distance related variables 460 | self.edgeWidth = edge_width 461 | self.crossLimitDistance = self.edgeWidth * self.stretchRepository.crossLimitDistanceOverEdgeWidth 462 | 463 | self.loopMaximumAbsoluteStretch = self.edgeWidth * self.stretchRepository.loopStretchOverEdgeWidth 464 | self.edgeInsideAbsoluteStretch = self.edgeWidth * self.stretchRepository.edgeInsideStretchOverEdgeWidth 465 | self.edgeOutsideAbsoluteStretch = self.edgeWidth * self.stretchRepository.edgeOutsideStretchOverEdgeWidth 466 | 467 | self.stretchFromDistance = self.stretchRepository.stretchFromDistanceOverEdgeWidth * self.edgeWidth 468 | self.thread_maximum_absolute_stretch = 0 469 | 470 | self.crossLimitDistanceFraction = self.crossLimitDistance / 3 471 | self.crossLimitDistanceRemainder = self.crossLimitDistance - self.crossLimitDistanceFraction 472 | 473 | def parse_line(self, line): 474 | """Parse a gcode line and add it to the stretch skein.""" 475 | 476 | # check for loop markers 477 | if self.is_inner_edge_begin(line): 478 | self.isLoop = True 479 | self.thread_maximum_absolute_stretch = self.edgeInsideAbsoluteStretch 480 | elif self.is_outer_edge_begin(line): 481 | self.isLoop = True 482 | self.thread_maximum_absolute_stretch = self.edgeOutsideAbsoluteStretch 483 | elif self.is_loop_begin(line): 484 | self.isLoop = True 485 | self.thread_maximum_absolute_stretch = self.loopMaximumAbsoluteStretch 486 | elif self.is_loop_end(line): 487 | self.isLoop = False 488 | self.set_stretch_to_path() 489 | 490 | # handle move command if in loop 491 | if line.command in linear_move_gcodes and self.isLoop and (line.x is not None or line.y is not None): 492 | return self.stretch_line(line) 493 | 494 | return line 495 | 496 | def set_stretch_to_path(self): 497 | """Set the thread stretch to path stretch and is loop false.""" 498 | self.isLoop = False 499 | self.thread_maximum_absolute_stretch = 0 500 | 501 | def is_loop_begin(self, line): 502 | return self.LOOP_START_MARKER in line.raw 503 | 504 | def is_loop_end(self, line): 505 | return self.LOOP_STOP_MARKER in line.raw 506 | 507 | def is_inner_edge_begin(self, line): 508 | return self.INNER_EDGE_START_MARKER in line.raw 509 | 510 | def is_outer_edge_begin(self, line): 511 | return self.OUTER_EDGE_START_MARKER in line.raw 512 | 513 | def setup_filter(self): 514 | raise NotImplementedError 515 | 516 | 517 | class Slic3rStretchFilter(StretchFilter): 518 | UNKNOWN = 0 519 | EXTERNAL_PERIMETER = 1 520 | EXTRA_PERIMETER = 2 521 | 522 | EDGE_WIDTH_REGEXP = re.compile(r'; external perimeters extrusion width\s+=\s+([\.\d]+)mm') 523 | 524 | def __init__(self, **kwargs): 525 | StretchFilter.__init__(self, **kwargs) 526 | 527 | self.line_forward_iterator = LineIteratorForward 528 | self.line_backward_iterator = LineIteratorBackward 529 | 530 | self.next_external_perimeter_is_outer = None 531 | self.current_type_line = None 532 | 533 | def new_perimeter(self, line, external=False): 534 | 535 | if external: 536 | if self.next_external_perimeter_is_outer: 537 | logging.debug("found external perimeter outer") 538 | line.raw += " ; " + StretchFilter.OUTER_EDGE_START_MARKER 539 | self.next_external_perimeter_is_outer = False 540 | else: 541 | logging.debug("found external perimeter inner") 542 | line.raw += " ; " + StretchFilter.INNER_EDGE_START_MARKER 543 | 544 | if self.current_type_line != self.EXTERNAL_PERIMETER: 545 | logging.debug("found end of loop") 546 | line.raw += " ; " + StretchFilter.LOOP_STOP_MARKER 547 | 548 | self.current_type_line = self.EXTERNAL_PERIMETER 549 | 550 | else: 551 | logging.debug("found extra perimeter") 552 | line.raw += " ; " + StretchFilter.LOOP_START_MARKER 553 | 554 | if self.EXTERNAL_PERIMETER == self.current_type_line: 555 | logging.debug("found end of loop") 556 | line.raw += " ; " + StretchFilter.LOOP_STOP_MARKER 557 | 558 | self.current_type_line = self.EXTRA_PERIMETER 559 | 560 | def setup_filter(self): 561 | 562 | edge_width_found = False 563 | extruding = False 564 | 565 | for self.current_layer in self.gcode.all_layers: 566 | self.next_external_perimeter_is_outer = True 567 | self.current_type_line = self.UNKNOWN 568 | 569 | for line_idx, line in enumerate(self.current_layer): 570 | 571 | # checking extrusion 572 | if not extruding and line.command in linear_move_gcodes and line.e is not None: 573 | extruding = True 574 | line.raw += " ; " + StretchFilter.EXTRUSION_ON_MARKER 575 | elif extruding and line.command in linear_move_gcodes and line.e is None and self.current_type_line in ( 576 | self.EXTRA_PERIMETER, self.EXTERNAL_PERIMETER): 577 | extruding = False 578 | line.raw += " ; " + StretchFilter.EXTRUSION_OFF_MARKER 579 | 580 | # checking perimeter type 581 | if '; perimeter external' in line.raw: 582 | 583 | if self.EXTERNAL_PERIMETER != self.current_type_line: 584 | self.new_perimeter(line, True) 585 | 586 | elif '; perimeter' in line.raw: 587 | 588 | if self.EXTRA_PERIMETER != self.current_type_line: 589 | self.new_perimeter(line) 590 | 591 | elif '; move to first perimeter point' in line.raw: 592 | # search if next perimeter is external or not 593 | for loop_ahead_idx in xrange(line_idx + 1, len(self.current_layer)): 594 | if '; perimeter external' in self.current_layer[loop_ahead_idx].raw: 595 | self.new_perimeter(line, True) 596 | break 597 | elif '; perimeter' in self.current_layer[loop_ahead_idx].raw: 598 | self.new_perimeter(line) 599 | break 600 | 601 | elif 'unretract' not in line.raw: 602 | 603 | if self.current_type_line in (self.EXTRA_PERIMETER, self.EXTERNAL_PERIMETER): 604 | logging.debug("found end of loop") 605 | line.raw += " ; " + StretchFilter.LOOP_STOP_MARKER 606 | 607 | self.current_type_line = self.UNKNOWN 608 | 609 | # checking for edge width 610 | match = self.EDGE_WIDTH_REGEXP.match(line.raw) 611 | if match: 612 | edge_width_found = True 613 | self.set_edge_width(float(match.group(1))) 614 | 615 | if self.current_type_line != self.UNKNOWN: 616 | logging.warn("unfinished loop") 617 | 618 | if not edge_width_found: 619 | logging.warn("no edge width found in comments, picking a default value") 620 | self.set_edge_width(0.4) 621 | 622 | 623 | class CuraStretchFilter(StretchFilter): 624 | UNKNOWN = 0 625 | EXTERNAL_PERIMETER = 1 626 | EXTRA_PERIMETER = 2 627 | 628 | CURA_PROFILE_REGEXP = re.compile(r';CURA_PROFILE_STRING:(.*)$') 629 | 630 | def __init__(self, **kwargs): 631 | StretchFilter.__init__(self, **kwargs) 632 | 633 | self.line_forward_iterator = CuraLineIteratorForward 634 | self.line_backward_iterator = CuraLineIteratorBackward 635 | 636 | def new_perimeter(self, line, external=False, outer=False): 637 | 638 | if external: 639 | if outer: 640 | logging.debug("found external perimeter outer") 641 | line.raw += " ; " + StretchFilter.OUTER_EDGE_START_MARKER 642 | else: 643 | logging.debug("found external perimeter inner") 644 | line.raw += " ; " + StretchFilter.INNER_EDGE_START_MARKER 645 | 646 | if self.current_type_line != self.UNKNOWN: 647 | logging.debug("found end of loop") 648 | line.raw += " ; " + StretchFilter.LOOP_STOP_MARKER 649 | 650 | self.current_type_line = self.EXTERNAL_PERIMETER 651 | 652 | else: 653 | logging.debug("found extra perimeter") 654 | line.raw += " ; " + StretchFilter.LOOP_START_MARKER 655 | 656 | if self.current_type_line != self.UNKNOWN: 657 | logging.debug("found end of loop") 658 | line.raw += " ; " + StretchFilter.LOOP_STOP_MARKER 659 | 660 | self.current_type_line = self.EXTRA_PERIMETER 661 | 662 | def setup_filter(self): 663 | 664 | edge_width_found = False 665 | extruding = False 666 | 667 | for self.current_layer in self.gcode.all_layers: 668 | self.current_type_line = self.UNKNOWN 669 | next_line_marker = None 670 | 671 | for line_idx, line in enumerate(self.current_layer): 672 | 673 | # checking extrusion 674 | if not extruding and line.command in linear_move_gcodes and line.e is not None: 675 | extruding = True 676 | line.raw += " ; " + StretchFilter.EXTRUSION_ON_MARKER 677 | elif extruding and line.command in linear_move_gcodes and line.e is None and self.current_type_line in ( 678 | self.EXTRA_PERIMETER, self.EXTERNAL_PERIMETER): 679 | extruding = False 680 | line.raw += " ; " + StretchFilter.EXTRUSION_OFF_MARKER 681 | 682 | if next_line_marker is not None: 683 | self.new_perimeter(line, *next_line_marker) 684 | next_line_marker = None 685 | 686 | # checking perimeter type 687 | if 'TYPE:WALL-OUTER' in line.raw: 688 | self.stop_loop(line) 689 | next_line_marker = (True, True) 690 | 691 | elif 'TYPE:WALL-INNER' in line.raw: 692 | self.stop_loop(line) 693 | next_line_marker = (True, False) 694 | 695 | elif 'TYPE:SKIN' in line.raw: 696 | self.stop_loop(line) 697 | next_line_marker = (False, False) 698 | 699 | elif 'TYPE:FILL' in line.raw: 700 | self.stop_loop(line) 701 | 702 | # end loop if we reach the end of the current layer 703 | if line_idx == len(self.current_layer) - 1: 704 | self.stop_loop(line) 705 | 706 | # checking for edge width 707 | match = self.CURA_PROFILE_REGEXP.match(line.raw) 708 | if match: 709 | edge_width_found = self.parse_cura_profile(match.group(1)) 710 | 711 | if not edge_width_found: 712 | logging.warn("no edge width found in comments, picking a default value") 713 | self.set_edge_width(0.4) 714 | 715 | def parse_cura_profile(self, cura_profile): 716 | profileOpts, alt = zlib.decompress(base64.b64decode(cura_profile)).split('\f', 1) 717 | for option in profileOpts.split('\b'): 718 | if len(option) > 0: 719 | key, value = option.split('=', 1) 720 | logging.debug("found cura option %s = %s", key, value) 721 | 722 | if key == 'nozzle_size': 723 | self.set_edge_width(float(value)) 724 | return True 725 | 726 | return False 727 | 728 | def stop_loop(self, line): 729 | if self.current_type_line != self.UNKNOWN: 730 | logging.debug("found end of loop") 731 | line.raw += " ; " + StretchFilter.LOOP_STOP_MARKER 732 | self.current_type_line = self.UNKNOWN 733 | 734 | 735 | class SkeinforgeStretchFilter(StretchFilter): 736 | EDGE_WIDTH_REGEXP = re.compile(r'\( ([\.\d]+)') 737 | 738 | def parse_initialisation_line(self, line): 739 | # self.distanceFeedRate.search_decimal_places_carried(line.raw) 740 | if line.raw == '()': 741 | return True 742 | match = self.EDGE_WIDTH_REGEXP.match(line.raw) 743 | if match: 744 | self.set_edge_width(float(match.group(1))) 745 | return False 746 | 747 | def setup_filter(self): 748 | for self.current_layer in self.gcode.all_layers: 749 | for line in self.current_layer: 750 | self.parse_initialisation_line(line) 751 | if line.command == 'M101': 752 | line.raw += '; ' + self.EXTRUSION_ON_MARKER 753 | elif line.command == 'M103': 754 | line.raw += '; ' + self.EXTRUSION_OFF_MARKER 755 | elif line.raw.startswith("("): 756 | line.raw += '; ' + self.LOOP_START_MARKER 757 | elif line.raw.startswith("(") and not line.raw.startswith("( outer"): 758 | line.raw += '; ' + self.INNER_EDGE_START_MARKER 759 | elif line.raw.startswith("( outer"): 760 | line.raw += '; ' + self.OUTER_EDGE_START_MARKER 761 | elif line.raw.startswith("()") or line.raw.startswith("()"): 762 | line.raw += '; ' + self.LOOP_STOP_MARKER 763 | -------------------------------------------------------------------------------- /gcodeutils/stretch/vector3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Vector3 is a three dimensional vector class. 3 | 4 | Below are examples of Vector3 use. 5 | 6 | >>> from vector3 import Vector3 7 | >>> origin = Vector3() 8 | >>> origin 9 | 0.0, 0.0, 0.0 10 | >>> pythagoras = Vector3( 3, 4, 0 ) 11 | >>> pythagoras 12 | 3.0, 4.0, 0.0 13 | >>> pythagoras.magnitude() 14 | 5.0 15 | >>> pythagoras.magnitudeSquared() 16 | 25 17 | >>> triplePythagoras = pythagoras * 3.0 18 | >>> triplePythagoras 19 | 9.0, 12.0, 0.0 20 | >>> plane = pythagoras.dropAxis() 21 | >>> plane 22 | (3+4j) 23 | """ 24 | 25 | from __future__ import absolute_import 26 | 27 | try: 28 | import psyco 29 | 30 | psyco.full() 31 | except: 32 | pass 33 | 34 | import math 35 | import operator 36 | 37 | __author__ = 'Enrique Perez (perez_enrique@yahoo.com)' 38 | __credits__ = 'Nophead \nArt of Illusion ' 39 | __date__ = '$Date: 2008/21/04 $' 40 | __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' 41 | 42 | 43 | class Vector3: 44 | 'A three dimensional vector class.' 45 | __slots__ = ['x', 'y', 'z'] 46 | 47 | def __init__(self, x=0.0, y=0.0, z=0.0): 48 | self.x = x 49 | self.y = y 50 | self.z = z 51 | 52 | def __abs__(self): 53 | 'Get the magnitude of the Vector3.' 54 | return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z) 55 | 56 | magnitude = __abs__ 57 | 58 | def __add__(self, other): 59 | 'Get the sum of this Vector3 and other one.' 60 | return Vector3(self.x + other.x, self.y + other.y, self.z + other.z) 61 | 62 | def __copy__(self): 63 | 'Get the copy of this Vector3.' 64 | return Vector3(self.x, self.y, self.z) 65 | 66 | __pos__ = __copy__ 67 | 68 | copy = __copy__ 69 | 70 | def __div__(self, other): 71 | 'Get a new Vector3 by dividing each component of this one.' 72 | return Vector3(self.x / other, self.y / other, self.z / other) 73 | 74 | def __eq__(self, other): 75 | 'Determine whether this vector is identical to other one.' 76 | if other == None: 77 | return False 78 | if other.__class__ != self.__class__: 79 | return False 80 | return self.x == other.x and self.y == other.y and self.z == other.z 81 | 82 | def __floordiv__(self, other): 83 | 'Get a new Vector3 by floor dividing each component of this one.' 84 | return Vector3(self.x // other, self.y // other, self.z // other) 85 | 86 | def __hash__(self): 87 | 'Determine whether this vector is identical to other one.' 88 | return self.__repr__().__hash__() 89 | 90 | def __iadd__(self, other): 91 | 'Add other Vector3 to this one.' 92 | self.x += other.x 93 | self.y += other.y 94 | self.z += other.z 95 | return self 96 | 97 | def __idiv__(self, other): 98 | 'Divide each component of this Vector3.' 99 | self.x /= other 100 | self.y /= other 101 | self.z /= other 102 | return self 103 | 104 | def __ifloordiv__(self, other): 105 | 'Floor divide each component of this Vector3.' 106 | self.x //= other 107 | self.y //= other 108 | self.z //= other 109 | return self 110 | 111 | def __imul__(self, other): 112 | 'Multiply each component of this Vector3.' 113 | self.x *= other 114 | self.y *= other 115 | self.z *= other 116 | return self 117 | 118 | def __isub__(self, other): 119 | 'Subtract other Vector3 from this one.' 120 | self.x -= other.x 121 | self.y -= other.y 122 | self.z -= other.z 123 | return self 124 | 125 | def __itruediv__(self, other): 126 | 'True divide each component of this Vector3.' 127 | self.x = operator.truediv(self.x, other) 128 | self.y = operator.truediv(self.y, other) 129 | self.z = operator.truediv(self.z, other) 130 | return self 131 | 132 | def __mul__(self, other): 133 | 'Get a new Vector3 by multiplying each component of this one.' 134 | return Vector3(self.x * other, self.y * other, self.z * other) 135 | 136 | def __ne__(self, other): 137 | 'Determine whether this vector is not identical to other one.' 138 | return not self.__eq__(other) 139 | 140 | def __neg__(self): 141 | return Vector3(- self.x, - self.y, - self.z) 142 | 143 | def __nonzero__(self): 144 | return self.x != 0 or self.y != 0 or self.z != 0 145 | 146 | def __rdiv__(self, other): 147 | 'Get a new Vector3 by dividing each component of this one.' 148 | return Vector3(other / self.x, other / self.y, other / self.z) 149 | 150 | def __repr__(self): 151 | 'Get the string representation of this Vector3.' 152 | return '(%s, %s, %s)' % (self.x, self.y, self.z) 153 | 154 | def __rfloordiv__(self, other): 155 | 'Get a new Vector3 by floor dividing each component of this one.' 156 | return Vector3(other // self.x, other // self.y, other // self.z) 157 | 158 | def __rmul__(self, other): 159 | 'Get a new Vector3 by multiplying each component of this one.' 160 | return Vector3(self.x * other, self.y * other, self.z * other) 161 | 162 | def __rtruediv__(self, other): 163 | 'Get a new Vector3 by true dividing each component of this one.' 164 | return Vector3(operator.truediv(other, self.x), operator.truediv(other, self.y), 165 | operator.truediv(other, self.z)) 166 | 167 | def __sub__(self, other): 168 | 'Get the difference between the Vector3 and other one.' 169 | return Vector3(self.x - other.x, self.y - other.y, self.z - other.z) 170 | 171 | def __truediv__(self, other): 172 | 'Get a new Vector3 by true dividing each component of this one.' 173 | return Vector3(operator.truediv(self.x, other), operator.truediv(self.y, other), 174 | operator.truediv(self.z, other)) 175 | 176 | def _getAccessibleAttribute(self, attributeName): 177 | 'Get the accessible attribute.' 178 | if attributeName in globalGetAccessibleAttributeSet: 179 | return getattr(self, attributeName, None) 180 | return None 181 | 182 | def _setAccessibleAttribute(self, attributeName, value): 183 | 'Set the accessible attribute.' 184 | if attributeName in globalSetAccessibleAttributeSet: 185 | setattr(self, attributeName, value) 186 | 187 | def cross(self, other): 188 | 'Calculate the cross product of this vector with other one.' 189 | return Vector3(self.y * other.z - self.z * other.y, -self.x * other.z + self.z * other.x, 190 | self.x * other.y - self.y * other.x) 191 | 192 | def distance(self, other): 193 | 'Get the Euclidean distance between this vector and other one.' 194 | return math.sqrt(self.distanceSquared(other)) 195 | 196 | def distanceSquared(self, other): 197 | 'Get the square of the Euclidean distance between this vector and other one.' 198 | separationX = self.x - other.x 199 | separationY = self.y - other.y 200 | separationZ = self.z - other.z 201 | return separationX * separationX + separationY * separationY + separationZ * separationZ 202 | 203 | def dot(self, other): 204 | 'Calculate the dot product of this vector with other one.' 205 | return self.x * other.x + self.y * other.y + self.z * other.z 206 | 207 | def dropAxis(self, which=2): 208 | 'Get a complex by removing one axis of the vector3.' 209 | if which == 0: 210 | return complex(self.y, self.z) 211 | if which == 1: 212 | return complex(self.x, self.z) 213 | if which == 2: 214 | return complex(self.x, self.y) 215 | 216 | def getFloatList(self): 217 | 'Get the vector as a list of floats.' 218 | return [float(self.x), float(self.y), float(self.z)] 219 | 220 | def getIsDefault(self): 221 | 'Determine if this is the zero vector.' 222 | if self.x != 0.0: 223 | return False 224 | if self.y != 0.0: 225 | return False 226 | return self.z == 0.0 227 | 228 | def getNormalized(self): 229 | 'Get the normalized Vector3.' 230 | magnitude = abs(self) 231 | if magnitude == 0.0: 232 | return self.copy() 233 | return self / magnitude 234 | 235 | def magnitudeSquared(self): 236 | 'Get the square of the magnitude of the Vector3.' 237 | return self.x * self.x + self.y * self.y + self.z * self.z 238 | 239 | def maximize(self, other): 240 | 'Maximize the Vector3.' 241 | self.x = max(other.x, self.x) 242 | self.y = max(other.y, self.y) 243 | self.z = max(other.z, self.z) 244 | 245 | def minimize(self, other): 246 | 'Minimize the Vector3.' 247 | self.x = min(other.x, self.x) 248 | self.y = min(other.y, self.y) 249 | self.z = min(other.z, self.z) 250 | 251 | def normalize(self): 252 | 'Scale each component of this Vector3 so that it has a magnitude of 1. If this Vector3 has a magnitude of 0, this method has no effect.' 253 | magnitude = abs(self) 254 | if magnitude != 0.0: 255 | self /= magnitude 256 | 257 | def reflect(self, normal): 258 | 'Reflect the Vector3 across the normal, which is assumed to be normalized.' 259 | distance = 2 * (self.x * normal.x + self.y * normal.y + self.z * normal.z) 260 | return Vector3(self.x - distance * normal.x, self.y - distance * normal.y, self.z - distance * normal.z) 261 | 262 | def setToVector3(self, other): 263 | 'Set this Vector3 to be identical to other one.' 264 | self.x = other.x 265 | self.y = other.y 266 | self.z = other.z 267 | 268 | def setToXYZ(self, x, y, z): 269 | 'Set the x, y, and z components of this Vector3.' 270 | self.x = x 271 | self.y = y 272 | self.z = z 273 | 274 | 275 | globalGetAccessibleAttributeSet = 'x y z'.split() 276 | globalSetAccessibleAttributeSet = globalGetAccessibleAttributeSet 277 | 278 | """ 279 | class Vector3: 280 | __slots__ = ['x', 'y', 'z'] 281 | 282 | 283 | copy = __copy__ 284 | 285 | def __eq__(self, other): 286 | if isinstance(other, Vector3): 287 | return self.x == other.x and \ 288 | self.y == other.y and \ 289 | self.z == other.z 290 | else: 291 | assert hasattr(other, '__len__') and len(other) == 3 292 | return self.x == other[0] and \ 293 | self.y == other[1] and \ 294 | self.z == other[2] 295 | 296 | def __getattr__(self, name): 297 | try: 298 | return tuple([(self.x, self.y, self.z)['xyz'.index(c)] \ 299 | for c in name]) 300 | except ValueError: 301 | raise AttributeError, name 302 | 303 | def __getitem__(self, key): 304 | return (self.x, self.y, self.z)[key] 305 | 306 | def __iter__(self): 307 | return iter((self.x, self.y, self.z)) 308 | 309 | def __len__(self): 310 | return 3 311 | 312 | def __repr__(self): 313 | return 'Vector3(%.2f, %.2f, %.2f)' % (self.x, 314 | self.y, 315 | self.z) 316 | 317 | if _enable_swizzle_set: 318 | # This has detrimental performance on ordinary setattr as well 319 | # if enabled 320 | def __setattr__(self, name, value): 321 | if len(name) == 1: 322 | object.__setattr__(self, name, value) 323 | else: 324 | try: 325 | l = [self.x, self.y, self.z] 326 | for c, v in map(None, name, value): 327 | l['xyz'.index(c)] = v 328 | self.x, self.y, self.z = l 329 | except ValueError: 330 | raise AttributeError, name 331 | 332 | def __setitem__(self, key, value): 333 | l = [self.x, self.y, self.z] 334 | l[key] = value 335 | self.x, self.y, self.z = l 336 | 337 | """ 338 | -------------------------------------------------------------------------------- /gcodeutils/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from gcodeutils.gcoder import GCode 4 | 5 | __author__ = 'olivier' 6 | 7 | 8 | def open_gcode_file(filename): 9 | with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)) as gcode: 10 | return GCode(gcode.readlines()) 11 | 12 | 13 | def gcode_eq(lhs, rhs): 14 | if not lhs == rhs: 15 | raise AssertionError(lhs.diff(rhs)) 16 | -------------------------------------------------------------------------------- /gcodeutils/tests/arc_raw_1.gcode: -------------------------------------------------------------------------------- 1 | M107 2 | M190 S125 ; set bed temperature 3 | G28 ; home all axes 4 | G1 Z5 F5000 ; lift nozzle 5 | M109 S270 ; wait for extruder temp to be reached 6 | M3001 ; Aktivate Z-Compensation 7 | M3004 S0 ; + n-steps bed down // - n-steps bed up! 8 | G90 ; use absolute coordinates 9 | M82 ; use absolute distances for extrusion 10 | G92 E0 ; start line 11 | G1 F300 E-0.5 12 | G1 X230 Y25 Z0.35 F5000 13 | G1 F800 E8 14 | G1 X20 E25 F1000 15 | M201 X1000 Y1000 Z1000 16 | M202 X3000 Y3000 Z1000 17 | G21 ; set units to millimeters 18 | G90 ; use absolute coordinates 19 | M82 ; use absolute distances for extrusion 20 | G92 E0 21 | G1 E-1.00000 F600.00000 22 | G92 E0 23 | G1 Z0.350 F6000.000 24 | G1 X20 Y20 Z0.0000 E0 25 | G1 X80 Y20 Z0.0000 E60 26 | G1 X80 Y60 Z0.0000 E100 27 | G1 X20 Y60 Z0.0000 E160 28 | G1 X20 Y20 Z0.0000 E200 29 | G92 E0 30 | ; begin of test data to be converted into arc 31 | M83 32 | G0 X100.0000 Y20.0000 Z0 first layer 33 | G1 X100.0000 Y60.0000 E1.111111 34 | G1 X99.6157 Y63.9018 E0.108908 35 | G1 X98.4776 Y67.6537 E0.108908 36 | G1 X96.6294 Y71.1114 E0.108908 37 | G1 X94.1421 Y74.1421 E0.108908 38 | G1 X91.1114 Y76.6294 E0.108908 39 | G1 X87.6537 Y78.4776 E0.108908 40 | G1 X83.9018 Y79.6157 E0.108908 41 | G1 X80.0000 Y80.0000 E0.108908 42 | G1 X20.0000 Y80.0000 E1.666667 43 | G1 X16.0982 Y79.6157 E0.108908 44 | G1 X12.3463 Y78.4776 E0.108908 45 | G1 X8.8886 Y76.6294 E0.108908 46 | G1 X5.8579 Y74.1421 E0.108908 47 | G1 X3.3706 Y71.1114 E0.108908 48 | G1 X1.5224 Y67.6537 E0.108908 49 | G1 X0.3843 Y63.9018 E0.108908 50 | G1 X0.0000 Y60.0000 E0.108908 51 | G1 X0.0000 Y20.0000 E1.111111 52 | G1 X0.3843 Y16.0982 E0.108908 53 | G1 X1.5224 Y12.3463 E0.108908 54 | G1 X3.3706 Y8.8886 E0.108908 55 | G1 X5.8579 Y5.8579 E0.108908 56 | G1 X8.8886 Y3.3706 E0.108908 57 | G1 X12.3463 Y1.5224 E0.108908 58 | G1 X16.0982 Y0.3843 E0.108908 59 | G1 X20.0000 Y0.0000 E0.108908 60 | G1 X80.0000 Y0.0000 E1.666667 61 | G1 X83.9018 Y0.3843 E0.108908 62 | G1 X87.6537 Y1.5224 E0.108908 63 | G1 X91.1114 Y3.3706 E0.108908 64 | G1 X94.1421 Y5.8579 E0.108908 65 | G1 X96.6294 Y8.8886 E0.108908 66 | G1 X98.4776 Y12.3463 E0.108908 67 | G1 X99.6157 Y16.0982 E0.108908 68 | G1 X100.0000 Y20.0000 E0.108908 69 | G0 Z1.0000 ; next layer 70 | G1 X99.6157 Y16.0982 E0.108908 71 | G1 X98.4776 Y12.3463 E0.108908 72 | G1 X96.6294 Y8.8886 E0.108908 73 | G1 X94.1421 Y5.8579 E0.108908 74 | G1 X91.1114 Y3.3706 E0.108908 75 | G1 X87.6537 Y1.5224 E0.108908 76 | G1 X83.9018 Y0.3843 E0.108908 77 | G1 X80.0000 Y0.0000 E0.108908 78 | G1 X20.0000 Y0.0000 E1.666667 79 | G1 X16.0982 Y0.3843 E0.108908 80 | G1 X12.3463 Y1.5224 E0.108908 81 | G1 X8.8886 Y3.3706 E0.108908 82 | G1 X5.8579 Y5.8579 E0.108908 83 | G1 X3.3706 Y8.8886 E0.108908 84 | G1 X1.5224 Y12.3463 E0.108908 85 | G1 X0.3843 Y16.0982 E0.108908 86 | G1 X0.0000 Y20.0000 E0.108908 87 | G1 X0.0000 Y60.0000 E1.111111 88 | G1 X0.3843 Y63.9018 E0.108908 89 | G1 X1.5224 Y67.6537 E0.108908 90 | G1 X3.3706 Y71.1114 E0.108908 91 | G1 X5.8579 Y74.1421 E0.108908 92 | G1 X8.8886 Y76.6294 E0.108908 93 | G1 X12.3463 Y78.4776 E0.108908 94 | G1 X16.0982 Y79.6157 E0.108908 95 | G1 X20.0000 Y80.0000 E0.108908 96 | G1 X80.0000 Y80.0000 E1.666667 97 | G1 X83.9018 Y79.6157 E0.108908 98 | G1 X87.6537 Y78.4776 E0.108908 99 | G1 X91.1114 Y76.6294 E0.108908 100 | G1 X94.1421 Y74.1421 E0.108908 101 | G1 X96.6294 Y71.1114 E0.108908 102 | G1 X98.4776 Y67.6537 E0.108908 103 | G1 X99.6157 Y63.9018 E0.108908 104 | G1 X100.0000 Y60.0000 E0.108908 105 | G1 X100.0000 Y20.0000 E1.1111 106 | G0 X0 Y0 Z2 107 | M82 ; switch back to absolute 108 | G92 E0 109 | ; some real world data to be reduced to 1 arc from 64 segments 110 | G1 X59.420 Y190.462 F6000.000 111 | G1 X59.663 Y190.337 F6000.000 112 | G1 X59.663 Y84.663 E15.29297 F1080.000 113 | G1 X185.337 Y84.663 E18.43871 114 | G1 X185.337 Y190.337 E21.08384 115 | G1 X59.738 Y190.337 E24.22770 116 | G1 X67.082 Y94.742 F6000.000 117 | G1 X66.669 Y94.681 E24.23817 F900.000 118 | G1 X66.263 Y94.579 E24.24864 119 | G1 X65.868 Y94.438 E24.25912 120 | G1 X65.490 Y94.259 E24.26960 121 | G1 X65.132 Y94.044 E24.28006 122 | G1 X64.795 Y93.795 E24.29054 123 | G1 X64.486 Y93.514 E24.30100 124 | G1 X64.205 Y93.204 E24.31147 125 | G1 X63.956 Y92.868 E24.32195 126 | G1 X63.740 Y92.510 E24.33242 127 | G1 X63.561 Y92.131 E24.34290 128 | G1 X63.420 Y91.737 E24.35337 129 | G1 X63.319 Y91.332 E24.36383 130 | G1 X63.257 Y90.918 E24.37430 131 | G1 X63.237 Y90.500 E24.38478 132 | G1 X63.257 Y90.082 E24.39525 133 | G1 X63.319 Y89.668 E24.40572 134 | G1 X63.420 Y89.265 E24.41613 135 | G1 X63.561 Y88.869 E24.42666 136 | G1 X63.740 Y88.490 E24.43713 137 | G1 X63.956 Y88.132 E24.44761 138 | G1 X64.205 Y87.796 E24.45808 139 | G1 X64.486 Y87.486 E24.46855 140 | G1 X64.795 Y87.205 E24.47901 141 | G1 X65.132 Y86.956 E24.48950 142 | G1 X65.490 Y86.741 E24.49995 143 | G1 X65.868 Y86.562 E24.51043 144 | G1 X66.263 Y86.421 E24.52091 145 | G1 X66.669 Y86.319 E24.53139 146 | G1 X67.082 Y86.258 E24.54185 147 | G1 X67.429 Y86.241 E24.55055 F900.000 148 | G1 X67.916 Y86.257 E24.56273 149 | G1 X68.331 Y86.319 E24.57325 150 | G1 X68.737 Y86.421 E24.58372 151 | G1 X69.132 Y86.562 E24.59420 152 | G1 X69.510 Y86.741 E24.60468 153 | G1 X69.868 Y86.956 E24.61513 154 | G1 X70.205 Y87.205 E24.62562 155 | G1 X70.514 Y87.486 E24.63608 156 | G1 X70.795 Y87.796 E24.64655 157 | G1 X71.044 Y88.132 E24.65702 158 | G1 X71.260 Y88.490 E24.66750 159 | G1 X71.439 Y88.869 E24.67797 160 | G1 X71.580 Y89.263 E24.68845 161 | G1 X71.681 Y89.668 E24.69891 162 | G1 X71.743 Y90.082 E24.70938 163 | G1 X71.763 Y90.500 E24.71986 164 | G1 X71.743 Y90.918 E24.73033 165 | G1 X71.681 Y91.332 E24.74080 166 | G1 X71.580 Y91.737 E24.75126 167 | G1 X71.439 Y92.131 E24.76174 168 | G1 X71.260 Y92.510 E24.77221 169 | G1 X71.044 Y92.868 E24.78269 170 | G1 X70.795 Y93.204 E24.79316 171 | G1 X70.514 Y93.514 E24.80363 172 | G1 X70.205 Y93.795 E24.81409 173 | G1 X69.868 Y94.044 E24.82458 174 | G1 X69.510 Y94.259 E24.83503 175 | G1 X69.132 Y94.438 E24.84551 176 | G1 X68.737 Y94.579 E24.85599 177 | G1 X68.331 Y94.681 E24.86647 178 | G1 X67.918 Y94.742 E24.87693 179 | G1 X67.500 Y94.763 E24.88740 180 | G1 X67.157 Y94.746 E24.89599 181 | G1 X66.833 Y95.078 F6000.000 182 | G1 X67.060 Y95.192 F6000.000 183 | G92 E0 184 | M104 S0 185 | M140 S0 186 | G91 187 | G1 E-5 F300 188 | M400 189 | M3079 190 | M400 191 | M84 192 | M201 X1000 Y1000 Z1000 193 | M202 X1000 Y1000 Z1000 194 | -------------------------------------------------------------------------------- /gcodeutils/tests/arc_raw_2.gcode: -------------------------------------------------------------------------------- 1 | G92 E0 2 | G1 X175.799 Y90.500 F6000.000 3 | G1 E2.00000 F900.00000 4 | G1 X175.831 Y90.168 E2.00797 F1500.000 5 | G1 X175.929 Y89.849 E2.01593 6 | G1 X176.085 Y89.555 E2.02388 7 | G1 X176.297 Y89.297 E2.03184 8 | G1 X176.554 Y89.085 E2.03980 9 | G1 X176.849 Y88.928 E2.04776 10 | G1 X177.168 Y88.831 E2.05573 11 | G1 X177.375 Y88.811 E2.06070 12 | G1 X177.500 Y88.799 E2.06369 F1500.000 13 | G1 X177.832 Y88.831 E2.07165 14 | G1 X178.151 Y88.928 E2.07962 15 | G1 X178.446 Y89.085 E2.08758 16 | G1 X178.703 Y89.297 E2.09554 17 | G1 X178.915 Y89.555 E2.10350 18 | G1 X179.071 Y89.849 E2.11145 19 | G1 X179.169 Y90.168 E2.11941 20 | G1 X179.201 Y90.500 E2.12738 21 | G1 X179.169 Y90.832 E2.13535 22 | G1 X179.071 Y91.151 E2.14330 23 | G1 X178.915 Y91.445 E2.15126 24 | G1 X178.703 Y91.703 E2.15922 25 | G1 X178.446 Y91.915 E2.16718 26 | G1 X178.151 Y92.072 E2.17514 27 | G1 X177.832 Y92.169 E2.18311 28 | G1 X177.500 Y92.201 E2.19107 29 | G1 X177.168 Y92.169 E2.19902 30 | G1 X176.849 Y92.072 E2.20699 31 | G1 X176.554 Y91.915 E2.21496 32 | G1 X176.297 Y91.703 E2.22292 33 | G1 X176.085 Y91.445 E2.23088 34 | G1 X175.929 Y91.151 E2.23883 35 | G1 X175.831 Y90.832 E2.24679 36 | G1 X175.806 Y90.575 E2.25297 37 | G1 X175.515 Y90.324 F6000.000 38 | G1 X174.712 Y90.608 F6000.000 39 | G1 X174.701 Y90.500 E2.25555 F1500.000 40 | G1 X174.756 Y89.954 E2.26865 41 | G1 X174.915 Y89.429 E2.28174 42 | G1 X175.173 Y88.945 E2.29483 43 | G1 X175.521 Y88.521 E2.30793 44 | G1 X175.945 Y88.173 E2.32103 45 | G1 X176.429 Y87.914 E2.33413 46 | G1 X176.954 Y87.755 E2.34721 47 | G1 X177.500 Y87.701 E2.36031 48 | G1 X177.625 Y87.714 E2.36330 49 | G1 X178.046 Y87.755 E2.37341 F1500.000 50 | G1 X178.571 Y87.914 E2.38649 51 | G1 X179.055 Y88.173 E2.39959 52 | G1 X179.479 Y88.521 E2.41269 53 | G1 X179.827 Y88.945 E2.42579 54 | G1 X180.085 Y89.429 E2.43887 55 | G1 X180.244 Y89.954 E2.45197 56 | G1 X180.299 Y90.500 E2.46507 57 | G1 X180.244 Y91.046 E2.47816 58 | G1 X180.085 Y91.571 E2.49126 59 | G1 X179.827 Y92.055 E2.50434 60 | G1 X179.479 Y92.479 E2.51744 61 | G1 X179.055 Y92.827 E2.53054 62 | G1 X178.571 Y93.086 E2.54364 63 | G1 X178.046 Y93.245 E2.55672 64 | G1 X177.500 Y93.299 E2.56982 65 | G1 X176.954 Y93.245 E2.58292 66 | G1 X176.429 Y93.086 E2.59600 67 | G1 X175.945 Y92.827 E2.60910 68 | G1 X175.521 Y92.479 E2.62220 69 | G1 X175.173 Y92.055 E2.63530 70 | G1 X174.915 Y91.571 E2.64839 71 | G1 X174.756 Y91.046 E2.66148 72 | G1 X174.720 Y90.682 E2.67020 73 | G1 X174.800 Y90.545 F6000.000 74 | G1 X175.255 Y90.554 F6000.000 75 | G1 X175.253 Y90.473 E2.67432 F600.000 76 | G1 X175.294 Y90.061 E2.69525 F600.000 77 | G1 X175.422 Y89.639 E2.71759 78 | G1 X175.630 Y89.250 E2.73987 79 | G1 X175.909 Y88.910 E2.76217 80 | G1 X176.250 Y88.630 E2.78451 81 | G1 X176.640 Y88.422 E2.80684 82 | G1 X177.061 Y88.294 E2.82913 83 | G1 X177.500 Y88.251 E2.85145 84 | G1 X177.939 Y88.294 E2.87377 85 | G1 X178.360 Y88.422 E2.89606 86 | G1 X178.750 Y88.630 E2.91839 87 | G1 X179.091 Y88.910 E2.94073 88 | G1 X179.370 Y89.250 E2.96304 89 | G1 X179.578 Y89.639 E2.98531 90 | G1 X179.706 Y90.061 E3.00765 91 | G1 X179.749 Y90.500 E3.02997 92 | G1 X179.706 Y90.939 E3.05228 93 | G1 X179.578 Y91.361 E3.07462 94 | G1 X179.370 Y91.750 E3.09690 95 | G1 X179.091 Y92.090 E3.11921 96 | G1 X178.750 Y92.370 E3.14154 97 | G1 X178.360 Y92.578 E3.16387 98 | G1 X177.939 Y92.706 E3.18616 99 | G1 X177.500 Y92.749 E3.20848 100 | G1 X177.061 Y92.706 E3.23080 101 | G1 X176.640 Y92.578 E3.25310 102 | G1 X176.250 Y92.370 E3.27542 103 | G1 X175.909 Y92.090 E3.29776 104 | G1 X175.630 Y91.750 E3.32007 105 | G1 X175.422 Y91.361 E3.34234 106 | G1 X175.294 Y90.939 E3.36468 107 | G1 X175.263 Y90.629 E3.38046 108 | G1 E1.38046 F900.00000 109 | G92 E0 110 | G1 X177.500 Y72.655 E4117.71966 111 | G0 F9000 X177.500 Y72.152 112 | ;TYPE:WALL-OUTER 113 | G1 F3000 X177.822 Y72.120 E4117.72687 114 | G1 X178.132 Y72.026 E4117.73409 115 | G1 X178.418 Y71.873 E4117.74132 116 | G1 X178.672 Y71.661 E4117.74869 117 | G1 X178.874 Y71.417 E4117.75575 118 | G1 X179.026 Y71.124 E4117.76310 119 | G1 X179.119 Y70.824 E4117.77010 120 | G1 X179.151 Y70.499 E4117.77738 121 | G1 X179.117 Y70.169 E4117.78477 122 | G1 X179.024 Y69.868 E4117.79179 123 | G1 X178.868 Y69.577 E4117.79915 124 | G1 X178.667 Y69.333 E4117.80619 125 | G1 X178.417 Y69.127 E4117.81341 126 | G1 X178.132 Y68.975 E4117.82061 127 | G1 X177.822 Y68.881 E4117.82782 128 | G1 X177.500 Y68.849 E4117.83503 129 | G1 X177.177 Y68.881 E4117.84227 130 | G1 X176.867 Y68.975 E4117.84948 131 | G1 X176.581 Y69.128 E4117.85671 132 | G1 X176.327 Y69.340 E4117.86408 133 | G1 X176.125 Y69.584 E4117.87114 134 | G1 X175.973 Y69.877 E4117.87850 135 | G1 X175.880 Y70.177 E4117.88549 136 | G1 X175.848 Y70.500 E4117.89273 137 | G1 X175.882 Y70.832 E4117.90016 138 | G1 X175.975 Y71.133 E4117.90718 139 | G1 X176.131 Y71.424 E4117.91454 140 | G1 X176.332 Y71.668 E4117.92158 141 | G1 X176.582 Y71.874 E4117.92880 142 | G1 X176.867 Y72.026 E4117.93600 143 | G1 X177.177 Y72.120 E4117.94322 144 | G1 X177.500 Y72.152 E4117.95045 145 | G0 F9000 X177.498 Y73.349 -------------------------------------------------------------------------------- /gcodeutils/tests/arc_raw_3.gcode: -------------------------------------------------------------------------------- 1 | G1 X59.377 Y171.493 E4293.68752 2 | G0 F9000 X58.594 Y170.861 3 | G1 F3000 X58.609 Y170.492 E4293.69320 4 | G1 X58.600 Y169.794 E4293.70362 5 | G1 X58.600 Y169.087 E4293.71461 6 | G1 X58.600 Y168.380 E4293.72561 7 | G1 X58.600 Y167.673 E4293.73661 8 | G1 X58.600 Y166.966 E4293.74760 9 | G1 X58.600 Y166.259 E4293.75860 10 | G1 X58.600 Y165.551 E4293.76958 11 | G1 X58.600 Y164.844 E4293.78057 12 | G1 X58.600 Y164.137 E4293.79157 13 | G1 X58.600 Y163.430 E4293.80256 14 | G1 X58.600 Y162.723 E4293.81356 15 | G1 X58.600 Y162.016 E4293.82456 16 | G1 X58.600 Y161.309 E4293.83555 17 | G1 X58.600 Y160.602 E4293.84655 18 | G1 X58.600 Y159.895 E4293.85754 19 | G1 X58.600 Y159.188 E4293.86854 20 | G1 X58.600 Y158.480 E4293.87952 21 | G1 X58.600 Y157.773 E4293.89051 22 | G1 X58.600 Y157.066 E4293.90151 23 | G1 X58.600 Y156.359 E4293.91251 24 | G1 X58.600 Y155.652 E4293.92350 25 | G1 X58.600 Y154.945 E4293.93450 26 | G1 X58.600 Y154.238 E4293.94549 27 | G1 X58.600 Y153.531 E4293.95649 28 | G1 X58.600 Y152.824 E4293.96748 29 | G1 X58.600 Y152.116 E4293.97846 30 | G1 X58.600 Y151.409 E4293.98946 31 | G1 X58.600 Y150.702 E4294.00045 32 | G1 X58.600 Y149.995 E4294.01145 33 | G1 X58.600 Y149.288 E4294.02245 34 | G1 X58.600 Y148.581 E4294.03344 35 | G1 X58.600 Y147.874 E4294.04444 36 | G1 X58.600 Y147.167 E4294.05543 37 | G1 X58.600 Y146.460 E4294.06643 38 | G1 X58.600 Y145.752 E4294.07744 39 | G1 X58.600 Y145.045 E4294.08844 40 | G1 X58.600 Y144.338 E4294.09943 41 | G1 X58.600 Y143.631 E4294.11043 42 | G1 X58.600 Y142.924 E4294.12142 43 | G1 X58.600 Y142.217 E4294.13242 44 | G1 X58.600 Y141.510 E4294.14341 45 | G1 X58.600 Y140.803 E4294.15441 46 | G1 X58.600 Y140.096 E4294.16541 47 | G1 X58.600 Y139.389 E4294.17640 48 | G1 X58.600 Y138.681 E4294.18738 49 | G1 X58.600 Y137.974 E4294.19838 50 | G1 X58.600 Y137.267 E4294.20937 51 | G1 X58.600 Y136.560 E4294.22037 52 | G1 X58.600 Y135.853 E4294.23136 53 | G1 X58.600 Y135.146 E4294.24236 54 | G1 X58.600 Y134.439 E4294.25336 55 | G1 X58.600 Y133.732 E4294.26435 56 | G1 X58.600 Y133.025 E4294.27535 57 | G1 X58.600 Y132.317 E4294.28633 58 | G1 X58.600 Y131.610 E4294.29732 59 | G1 X58.600 Y130.903 E4294.30832 60 | G1 X58.600 Y130.196 E4294.31931 61 | G1 X58.600 Y129.489 E4294.33031 62 | G1 X58.600 Y128.782 E4294.34131 63 | G1 X58.600 Y128.075 E4294.35230 64 | G1 X58.600 Y127.368 E4294.36330 65 | G1 X58.600 Y126.661 E4294.37429 66 | G1 X58.600 Y125.954 E4294.38529 67 | G1 X58.600 Y125.246 E4294.39627 68 | G1 X58.600 Y124.539 E4294.40726 69 | G1 X58.600 Y123.832 E4294.41826 70 | G1 X58.600 Y123.125 E4294.42925 71 | G1 X58.600 Y122.418 E4294.44025 72 | G1 X58.600 Y121.711 E4294.45125 73 | G1 X58.600 Y121.004 E4294.46224 74 | G1 X58.600 Y120.297 E4294.47324 75 | G1 X58.600 Y119.590 E4294.48423 76 | G1 X58.600 Y118.882 E4294.49521 77 | G1 X58.600 Y118.175 E4294.50621 78 | G1 X58.600 Y117.468 E4294.51720 79 | G1 X58.600 Y116.761 E4294.52820 80 | G1 X58.600 Y116.054 E4294.53920 81 | G1 X58.600 Y115.347 E4294.55019 82 | G1 X58.600 Y114.640 E4294.56119 83 | G1 X58.600 Y113.933 E4294.57218 84 | G1 X58.600 Y113.226 E4294.58318 85 | G1 X58.600 Y112.518 E4294.59416 86 | G1 X58.600 Y111.811 E4294.60515 87 | G1 X58.600 Y111.104 E4294.61615 88 | G1 X58.600 Y110.397 E4294.62715 89 | G1 X58.600 Y109.690 E4294.63814 90 | G1 X58.600 Y108.983 E4294.64914 91 | G1 X58.600 Y108.276 E4294.66013 92 | G1 X58.600 Y107.569 E4294.67113 93 | G1 X58.600 Y106.862 E4294.68212 94 | G1 X58.600 Y106.155 E4294.69312 95 | G1 X58.600 Y105.447 E4294.70410 96 | G1 X58.600 Y104.740 E4294.71510 97 | G1 X58.600 Y104.033 E4294.72609 98 | G1 X58.600 Y103.326 E4294.73709 99 | G1 X58.600 Y102.619 E4294.74808 100 | G1 X58.600 Y101.912 E4294.75908 101 | G1 X58.600 Y101.205 E4294.77007 102 | G1 X58.600 Y100.498 E4294.78107 103 | G1 X58.600 Y99.791 E4294.79207 104 | G1 X58.600 Y99.083 E4294.80304 105 | G1 X58.600 Y98.376 E4294.81404 106 | G1 X58.600 Y97.669 E4294.82504 107 | G1 X58.600 Y96.962 E4294.83603 108 | G1 X58.600 Y96.255 E4294.84703 109 | G1 X58.600 Y95.548 E4294.85802 110 | G1 X58.600 Y94.841 E4294.86902 111 | G1 X58.600 Y94.134 E4294.88001 112 | G1 X58.600 Y93.427 E4294.89101 113 | G1 X58.600 Y92.719 E4294.90199 114 | G1 X58.600 Y92.012 E4294.91299 115 | G1 X58.600 Y91.305 E4294.92398 116 | G1 X58.600 Y90.598 E4294.93498 117 | G1 X58.600 Y89.891 E4294.94597 118 | G1 X58.600 Y89.184 E4294.95697 119 | G1 X58.600 Y88.477 E4294.96796 120 | G1 X58.600 Y87.770 E4294.97896 121 | G1 X58.600 Y87.063 E4294.98996 122 | G1 X58.600 Y86.356 E4295.00095 123 | G1 X58.600 Y85.648 E4295.01193 124 | G1 X58.600 Y84.941 E4295.02293 125 | G1 X58.600 Y84.234 E4295.03392 126 | G1 X58.600 Y83.527 E4295.04492 127 | G1 X58.600 Y82.820 E4295.05591 128 | G1 X58.600 Y82.113 E4295.06691 129 | G1 X58.600 Y81.406 E4295.07791 130 | G1 X58.600 Y80.699 E4295.08890 131 | G1 X58.600 Y79.992 E4295.09990 132 | G1 X58.600 Y79.284 E4295.11088 133 | G1 X58.600 Y78.577 E4295.12187 134 | G1 X58.600 Y77.870 E4295.13287 135 | G1 X58.600 Y77.163 E4295.14386 136 | G1 X58.600 Y76.456 E4295.15486 137 | G1 X58.600 Y75.749 E4295.16586 138 | G1 X58.600 Y75.042 E4295.17685 139 | G1 X58.600 Y74.335 E4295.18785 140 | G1 X58.600 Y73.628 E4295.19884 141 | G1 X58.600 Y72.920 E4295.20985 142 | G1 X58.600 Y72.213 E4295.22085 143 | G1 X58.600 Y71.506 E4295.23184 144 | G1 X58.600 Y70.799 E4295.24284 145 | G1 X58.600 Y70.092 E4295.25384 146 | G1 X58.600 Y69.385 E4295.26483 147 | G1 X58.600 Y68.678 E4295.27583 148 | G1 X58.600 Y67.971 E4295.28682 149 | G1 X58.600 Y67.264 E4295.29782 150 | G1 X58.600 Y66.557 E4295.30881 151 | G1 X58.600 Y65.849 E4295.31979 152 | G1 X58.600 Y65.142 E4295.33079 153 | G1 X58.775 Y64.614 E4295.34318 154 | G0 F9000 X59.614 Y63.775 155 | G92 E0 -------------------------------------------------------------------------------- /gcodeutils/tests/arc_raw_4.gcode: -------------------------------------------------------------------------------- 1 | G92 E0 2 | G1 Z0.350 F6000.000 3 | G1 X55.420 Y79.825 F6000.000 4 | G1 E2.00000 F900.00000 5 | G1 X56.834 Y78.880 E2.04407 F1800.000 6 | G1 X57.611 Y78.558 E2.06588 7 | G1 X59.072 Y78.267 E2.10448 8 | G1 X59.490 Y78.226 E2.11537 9 | G1 X185.510 Y78.226 E5.38141 10 | G1 X185.928 Y78.267 E5.39229 11 | G1 X187.388 Y78.558 E5.43089 F1800.000 12 | G1 X188.166 Y78.880 E5.45270 13 | G1 X189.580 Y79.824 E5.49678 14 | G1 X190.175 Y80.420 E5.51860 15 | G1 X191.121 Y81.834 E5.56270 16 | G1 X191.443 Y82.613 E5.58454 17 | G1 X191.733 Y84.074 E5.62315 18 | G1 X191.774 Y84.491 E5.63400 19 | G1 X191.774 Y190.509 E8.38167 20 | G1 X191.733 Y190.926 E8.39253 21 | G1 X191.443 Y192.387 E8.43111 22 | G1 X191.121 Y193.166 E8.45296 23 | G1 X190.176 Y194.580 E8.49704 24 | G1 X189.580 Y195.176 E8.51890 25 | G1 X188.166 Y196.121 E8.56298 26 | G1 X187.386 Y196.443 E8.58484 27 | G1 X185.926 Y196.733 E8.62342 28 | G1 X185.509 Y196.774 E8.63427 29 | G1 X59.491 Y196.774 E11.90029 30 | G1 X59.074 Y196.733 E11.91113 31 | G1 X57.613 Y196.443 E11.94974 32 | G1 X56.834 Y196.121 E11.97159 33 | G1 X55.419 Y195.175 E12.01569 34 | G1 X54.824 Y194.580 E12.03751 35 | G1 X53.880 Y193.166 E12.08158 36 | G1 X53.558 Y192.388 E12.10339 37 | G1 X53.267 Y190.928 E12.14199 38 | G1 X53.226 Y190.510 E12.15287 39 | G1 X53.226 Y84.490 E14.90058 40 | G1 X53.267 Y84.072 E14.91146 41 | G1 X53.558 Y82.611 E14.95006 42 | G1 X53.880 Y81.834 E14.97188 43 | G1 X54.825 Y80.420 E15.01595 44 | G1 X55.367 Y79.878 E15.03582 45 | G1 X55.685 Y80.196 F6000.000 46 | G1 X57.047 Y79.279 E15.07837 F1800.000 -------------------------------------------------------------------------------- /gcodeutils/tests/arc_ref_1.gcode: -------------------------------------------------------------------------------- 1 | M107 2 | M190 S125 ; set bed temperature 3 | G28 ; home all axes 4 | G1 Z5 F5000 ; lift nozzle 5 | M109 S270 ; wait for extruder temp to be reached 6 | M3001 ; Aktivate Z-Compensation 7 | M3004 S0 ; + n-steps bed down // - n-steps bed up! 8 | G90 ; use absolute coordinates 9 | M82 ; use absolute distances for extrusion 10 | G92 E0 ; start line 11 | G1 F300 E-0.5 12 | G1 X230 Y25 Z0.35 F5000 13 | G1 F800 E8 14 | G1 X20 E25 F1000 15 | M201 X1000 Y1000 Z1000 16 | M202 X3000 Y3000 Z1000 17 | G21 ; set units to millimeters 18 | G90 ; use absolute coordinates 19 | M82 ; use absolute distances for extrusion 20 | G92 E0 21 | G1 E-1.00000 F600.00000 22 | G92 E0 23 | G1 Z0.350 F6000.000 24 | G1 X20 Y20 Z0.0000 E0 25 | G1 X80 Y20 Z0.0000 E60 26 | G1 X80 Y60 Z0.0000 E100 27 | G1 X20 Y60 Z0.0000 E160 28 | G1 X20 Y20 Z0.0000 E200 29 | G92 E0 30 | ; begin of test data to be converted into arc 31 | M83 32 | G0 X100.000 Y20.000 Z0 first layer 33 | G1 X100.000 Y60.000 E1.111111 34 | G3 X80.000 Y80.000 E0.87267 F6000.000 I-20.000 J-0.000; generated from 8 segments 35 | G1 X20.000 Y80.000 E1.666667 36 | G3 X0.000 Y60.000 E0.87267 F6000.000 I0.000 J-20.000; generated from 8 segments 37 | G1 X0.000 Y20.000 E1.111111 38 | G3 X20.000 Y0.000 E0.87267 F6000.000 I20.000 J0.000; generated from 8 segments 39 | G1 X80.000 Y0.000 E1.666667 40 | G3 X100.000 Y20.000 E0.87267 F6000.000 I-0.000 J20.000; generated from 8 segments 41 | G0 Z1.000 ; next layer 42 | G2 X80.000 Y0.000 E0.87267 F6000.000 I-20.000 J0.000; generated from 8 segments 43 | G1 X20.000 Y0.000 E1.666667 44 | G2 X0.000 Y20.000 E0.87267 F6000.000 I0.000 J20.000; generated from 8 segments 45 | G1 X0.000 Y60.000 E1.111111 46 | G2 X20.000 Y80.000 E0.87267 F6000.000 I20.000 J-0.000; generated from 8 segments 47 | G1 X80.000 Y80.000 E1.666667 48 | G2 X100.000 Y60.000 E0.87267 F6000.000 I-0.000 J-20.000; generated from 8 segments 49 | G1 X100.000 Y20.000 E1.1111 50 | G0 X0 Y0 Z2 51 | M82 ; switch back to absolute 52 | G92 E0 53 | ; some real world data to be reduced to 1 arc from 64 segments 54 | G1 X59.420 Y190.462 F6000.000 55 | G1 X59.663 Y190.337 F6000.000 56 | G1 X59.663 Y84.663 E15.29297 F1080.000 57 | G1 X185.337 Y84.663 E18.43871 58 | G1 X185.337 Y190.337 E21.08384 59 | G1 X59.738 Y190.337 E24.22770 60 | G1 X67.082 Y94.742 F6000.000 61 | G3 X67.157 Y94.746 E24.89625 F900.000 I0.4180 J-4.2420; generated from 64 segments 62 | G1 X66.833 Y95.078 F6000.000 63 | G1 X67.060 Y95.192 F6000.000 64 | G92 E0 65 | M104 S0 66 | M140 S0 67 | G91 68 | G1 E-5 F300 69 | M400 70 | M3079 71 | M400 72 | M84 73 | M201 X1000 Y1000 Z1000 74 | M202 X1000 Y1000 Z1000 75 | -------------------------------------------------------------------------------- /gcodeutils/tests/arc_ref_2.gcode: -------------------------------------------------------------------------------- 1 | G92 E0 2 | G1 X175.799 Y90.500 F6000.000 3 | G1 E2.00000 F900.00000 4 | G3 X175.806 Y90.575 E2.25332 F1500.000 I1.701 J0.000; generated from 33 segments 5 | G1 X175.515 Y90.324 F6000.000 6 | G1 X174.712 Y90.608 F6000.000 7 | G3 X174.720 Y90.682 E2.67075 F1500.000 I2.789 J-0.108; generated from 34 segments 8 | G1 X174.800 Y90.545 F6000.000 9 | G1 X175.255 Y90.554 F6000.000 10 | G3 X175.263 Y90.629 E3.38150 F600.000 I2.246 J-0.054; generated from 33 segments 11 | G1 E1.38046 F900.00000 12 | G92 E0 13 | G1 X177.500 Y72.655 E4117.71966 14 | G0 F9000 X177.500 Y72.152 15 | ;TYPE:WALL-OUTER 16 | G2 X177.500 Y72.152 E4117.48850 F3000.000 I-0.000 J-1.651; generated from 32 segments 17 | G0 F9000 X177.498 Y73.349 18 | -------------------------------------------------------------------------------- /gcodeutils/tests/arc_ref_4.gcode: -------------------------------------------------------------------------------- 1 | G92 E0 2 | G1 Z0.350 F6000.000 3 | G1 X55.420 Y79.825 F6000.000 4 | G1 E2.00000 F900.00000 5 | G1 X56.834 Y78.880 E2.04407 F1800.000 6 | G1 X57.611 Y78.558 E2.06588 7 | G1 X59.072 Y78.267 E2.10448 8 | G1 X59.490 Y78.226 E2.11537 9 | G1 X185.510 Y78.226 E5.38141 10 | G3 X191.733 Y84.074 E5.62364 F1800.000 I-0.150 J6.409; generated from 8 segments 11 | G1 X191.774 Y84.491 E5.63400 12 | G1 X191.774 Y190.509 E8.38167 13 | G3 X185.926 Y196.733 E8.62391 F1800.000 I-6.407 J-0.148; generated from 8 segments 14 | G1 X185.509 Y196.774 E8.63427 15 | G1 X59.491 Y196.774 E11.90029 16 | G3 X53.267 Y190.928 E12.14248 F1800.000 I0.148 J-6.408; generated from 8 segments 17 | G1 X53.226 Y190.510 E12.15287 18 | G1 X53.226 Y84.490 E14.90058 19 | G1 X53.267 Y84.072 E14.91146 20 | G1 X53.558 Y82.611 E14.95006 21 | G1 X53.880 Y81.834 E14.97188 22 | G1 X54.825 Y80.420 E15.01595 23 | G1 X55.367 Y79.878 E15.03582 24 | G1 X55.685 Y80.196 F6000.000 25 | G1 X57.047 Y79.279 E15.07837 F1800.000 26 | -------------------------------------------------------------------------------- /gcodeutils/tests/cura_square.gcode: -------------------------------------------------------------------------------- 1 | G0 F9000 X196.800 Y91.800 Z0.200 2 | ;TYPE:SKIRT 3 | G1 F1200 X303.200 Y91.800 E3.68480 4 | G1 F2400 E10.23919 5 | G0 F9000 X200.600 Y95.600 6 | ;TYPE:WALL-OUTER 7 | G1 X0 Y1 E1 F960.0 8 | G1 X1 Y0 E1 F960.0 9 | G1 X0 Y-1 E1 F960.0 10 | G1 X-1 Y0 E1 F960.0 11 | ;TYPE:FILL 12 | G1 X2 13 | G1 Z20 14 | -------------------------------------------------------------------------------- /gcodeutils/tests/empty_for_good.gcode: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeograd/gcodeutils/f5f82e6cc2d5c0e4081c06f51b75442679b8455d/gcodeutils/tests/empty_for_good.gcode -------------------------------------------------------------------------------- /gcodeutils/tests/empty_skeinforge_format.gcode: -------------------------------------------------------------------------------- 1 | ( skeinforge gcode ) 2 | ( 12.03.14 ) 3 | ( 15.05.26|11:58 ) 4 | () -------------------------------------------------------------------------------- /gcodeutils/tests/simple1.gcode: -------------------------------------------------------------------------------- 1 | G90 2 | G0 X0 3 | G1 Y1 4 | G2 Z2 5 | G3 E3 6 | -------------------------------------------------------------------------------- /gcodeutils/tests/simple1_equivalent.gcode: -------------------------------------------------------------------------------- 1 | G90 2 | G0 X0.00 3 | G1 Y1.0000001 4 | G2 Z2. 5 | G3 E3.0 6 | -------------------------------------------------------------------------------- /gcodeutils/tests/simple1_slightly_different.gcode: -------------------------------------------------------------------------------- 1 | G90 2 | G0 X0.01 3 | G1 Y1 4 | G2 Z2 5 | G3 E3 6 | -------------------------------------------------------------------------------- /gcodeutils/tests/simple2.gcode: -------------------------------------------------------------------------------- 1 | G90 2 | G0 X1 3 | G1 Y3 4 | G2 Z2 5 | G3 E3 6 | -------------------------------------------------------------------------------- /gcodeutils/tests/simple3-relative.gcode: -------------------------------------------------------------------------------- 1 | M83 2 | G0 X0 E1 3 | G1 Y1 E1 4 | G2 Z2 E1 5 | G3 X4 E1 6 | -------------------------------------------------------------------------------- /gcodeutils/tests/simple3.gcode: -------------------------------------------------------------------------------- 1 | M82 2 | G0 X0 E1 3 | G1 Y1 E2 4 | G2 Z2 E3 5 | G3 X4 E4 6 | -------------------------------------------------------------------------------- /gcodeutils/tests/skeinforge_square.gcode: -------------------------------------------------------------------------------- 1 | ( 0.72 ) 2 | ( outer) 3 | G1 X-1 Y0 Z0.6 F960.0 4 | M101 5 | G1 X0 Y1 Z0.6 F960.0 6 | G1 X1 Y0 Z0.6 F960.0 7 | G1 X0 Y-1 Z0.6 F960.0 8 | G1 X-1 Y0 Z0.6 F960.0 9 | M103 10 | () 11 | -------------------------------------------------------------------------------- /gcodeutils/tests/slic3r_square.gcode: -------------------------------------------------------------------------------- 1 | ; external perimeters extrusion width = 0.72mm 2 | G1 X-1 Y0 Z0.6 F960.0 ; move to first perimeter point 3 | G1 E1 ; unretract 4 | G1 X0 Y1 E1 Z0.6 F960.0 ; perimeter external 5 | G1 X1 Y0 E1 Z0.6 F960.0 ; perimeter external 6 | G1 X0 Y-1 E1 Z0.6 F960.0 ; perimeter external 7 | G1 X-1 Y0 E1 Z0.6 F960.0 ; perimeter external 8 | G1 X2 9 | G1 Z20 10 | -------------------------------------------------------------------------------- /gcodeutils/tests/test_arc_optimization.py: -------------------------------------------------------------------------------- 1 | # GCodeUtils is distributed in the hope that it will be useful, 2 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 3 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 4 | # GNU General Public License for more details. 5 | # 6 | # You should have received a copy of the GNU General Public License 7 | # along with GCodeUtils. If not, see . 8 | 9 | import logging 10 | 11 | from gcodeutils.filter.arc_optimizer import GCodeArcOptimizerFilter 12 | from gcodeutils.gcoder import PyLine 13 | from gcodeutils.tests import open_gcode_file, gcode_eq 14 | 15 | __author__ = 'Eyck Jentzsch ' 16 | 17 | 18 | def eq_epsilon_context(object): 19 | def __call__(self, func): 20 | def inner(): 21 | # write to disc truncates to 4 digits after comma so accuraccy needs to be adapted for testing 22 | old_epsilon = PyLine.EQ_EPSILON 23 | PyLine.EQ_EPSILON = 1e-4 24 | 25 | try: 26 | return func() 27 | finally: 28 | PyLine.EQ_EPSILON = old_epsilon 29 | 30 | return inner 31 | 32 | @eq_epsilon_context 33 | def test_arc_optimization_1(): 34 | gcode = open_gcode_file('arc_raw_1.gcode') 35 | gcode_ref = open_gcode_file('arc_ref_1.gcode') 36 | logging.basicConfig(level=logging.DEBUG) 37 | GCodeArcOptimizerFilter().filter(gcode) 38 | gcode_eq(gcode_ref, gcode) 39 | 40 | @eq_epsilon_context 41 | def test_arc_optimization_2(): 42 | gcode = open_gcode_file('arc_raw_2.gcode') 43 | gcode_ref = open_gcode_file('arc_ref_2.gcode') 44 | logging.basicConfig(level=logging.DEBUG) 45 | GCodeArcOptimizerFilter().filter(gcode) 46 | gcode_eq(gcode_ref, gcode) 47 | 48 | @eq_epsilon_context 49 | def test_arc_optimization_3(): 50 | gcode = open_gcode_file('arc_raw_3.gcode') 51 | gcode_ref = open_gcode_file('arc_raw_3.gcode') 52 | logging.basicConfig(level=logging.DEBUG) 53 | GCodeArcOptimizerFilter().filter(gcode) 54 | gcode_eq(gcode_ref, gcode) 55 | 56 | @eq_epsilon_context 57 | def test_arc_optimization_4(): 58 | gcode = open_gcode_file('arc_raw_4.gcode') 59 | gcode_ref = open_gcode_file('arc_ref_4.gcode') 60 | logging.basicConfig(level=logging.DEBUG) 61 | GCodeArcOptimizerFilter().filter(gcode) 62 | gcode_eq(gcode_ref, gcode) 63 | 64 | 65 | if __name__ == "__main__": 66 | test_arc_optimization_1() 67 | test_arc_optimization_2() 68 | test_arc_optimization_3() 69 | test_arc_optimization_4() 70 | -------------------------------------------------------------------------------- /gcodeutils/tests/test_equality.py: -------------------------------------------------------------------------------- 1 | from nose.tools import assert_not_equal 2 | 3 | from gcodeutils.tests import open_gcode_file, gcode_eq 4 | 5 | __author__ = 'olivier' 6 | 7 | 8 | def test_identity_equality(): 9 | gcode_eq(open_gcode_file('simple1.gcode'), open_gcode_file('simple1.gcode')) 10 | 11 | 12 | def test_trivial_difference(): 13 | assert_not_equal(open_gcode_file('simple1.gcode'), open_gcode_file('simple2.gcode')) 14 | 15 | 16 | def test_ignore_non_command(): 17 | gcode_eq(open_gcode_file('empty_for_good.gcode'), open_gcode_file('empty_skeinforge_format.gcode')) 18 | 19 | 20 | def test_precision(): 21 | """makes sure that gcode are equals if the numeric values are similar enough 22 | and different is the numeric values are far enough""" 23 | gcode_eq(open_gcode_file('simple1.gcode'), open_gcode_file('simple1_equivalent.gcode')) 24 | assert_not_equal(open_gcode_file('simple1.gcode'), open_gcode_file('simple1_slightly_different.gcode')) 25 | -------------------------------------------------------------------------------- /gcodeutils/tests/test_iterator.py: -------------------------------------------------------------------------------- 1 | from nose.tools import eq_ 2 | 3 | from gcodeutils.gcoder import GCode 4 | from gcodeutils.stretch.stretch import LineIteratorForwardLegacy, StretchFilter 5 | 6 | __author__ = 'olivier' 7 | 8 | try: 9 | xrange 10 | except NameError: 11 | xrange = range 12 | 13 | layer1 = ";" + StretchFilter.EXTRUSION_ON_MARKER + """ 14 | G1 X1 15 | G1 X2 16 | G1 X3 17 | G1 X4 18 | G1 X5 19 | """ + ";" + StretchFilter.EXTRUSION_OFF_MARKER 20 | 21 | 22 | def cmp_ite(iterator, oracle): 23 | for expected_x in oracle: 24 | eq_(expected_x, iterator.get_next().x) 25 | try: 26 | iterator.get_next() 27 | raise AssertionError("iterator should have stopped") 28 | except StopIteration: 29 | pass 30 | 31 | 32 | def test_trivial_iteration(): 33 | gcode = GCode(layer1.split("\n")) 34 | 35 | cmp_ite(LineIteratorForwardLegacy(0, gcode.all_layers[0]), xrange(1, 6)) 36 | 37 | for start_index in xrange(1, 6): 38 | cmp_ite(LineIteratorForwardLegacy(start_index, gcode.all_layers[0]), xrange(start_index, 6)) 39 | -------------------------------------------------------------------------------- /gcodeutils/tests/test_modification.py: -------------------------------------------------------------------------------- 1 | from gcodeutils.filter.relative_extrusion import GCodeToRelativeExtrusionFilter 2 | from gcodeutils.gcode_mod import GCodeXYTranslateFilter 3 | from gcodeutils.tests import open_gcode_file, gcode_eq 4 | 5 | __author__ = 'olivier' 6 | 7 | 8 | def test_nop_move(): 9 | gcode = open_gcode_file('simple1.gcode') 10 | gcode_oracle = open_gcode_file('simple1.gcode') 11 | 12 | GCodeXYTranslateFilter(x=0, y=0).filter(gcode) 13 | 14 | gcode_eq(gcode_oracle, gcode) 15 | 16 | 17 | def test_trivial_move(): 18 | gcode = open_gcode_file('simple1.gcode') 19 | gcode_oracle = open_gcode_file('simple2.gcode') 20 | 21 | GCodeXYTranslateFilter(x=1, y=2).filter(gcode) 22 | 23 | gcode_eq(gcode_oracle, gcode) 24 | 25 | 26 | def test_nop_relative_extrusion(): 27 | gcode = open_gcode_file('simple1.gcode') 28 | gcode_oracle = open_gcode_file('simple1.gcode') 29 | 30 | GCodeToRelativeExtrusionFilter().filter(gcode) 31 | 32 | gcode_eq(gcode_oracle, gcode) 33 | 34 | 35 | def test_trivial_relative_extrusion(): 36 | gcode = open_gcode_file('simple3.gcode') 37 | gcode_oracle = open_gcode_file('simple3-relative.gcode') 38 | 39 | GCodeToRelativeExtrusionFilter().filter(gcode) 40 | 41 | gcode_eq(gcode_oracle, gcode) 42 | -------------------------------------------------------------------------------- /gcodeutils/tests/test_stretch.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from gcodeutils.stretch.stretch import SkeinforgeStretchFilter, Slic3rStretchFilter, CuraStretchFilter 4 | from gcodeutils.tests import open_gcode_file, gcode_eq 5 | 6 | __author__ = 'olivier' 7 | 8 | 9 | def test_skeinforge_formatted_stretch(): 10 | gcode_oracle = open_gcode_file('skeinforge_model1_poststretch.gcode') 11 | gcode = open_gcode_file('skeinforge_model1_prestretch.gcode') 12 | 13 | SkeinforgeStretchFilter().filter(gcode) 14 | 15 | gcode_eq(gcode_oracle, gcode) 16 | 17 | 18 | def test_square_stretch_skeinforge(): 19 | simple_square_gcode = open_gcode_file('skeinforge_square.gcode') 20 | 21 | logging.basicConfig(level=logging.DEBUG) 22 | SkeinforgeStretchFilter().filter(simple_square_gcode) 23 | simple_square_gcode.write() 24 | 25 | 26 | def test_square_stretch_slic3r(): 27 | simple_square_gcode = open_gcode_file('slic3r_square.gcode') 28 | 29 | logging.basicConfig(level=logging.DEBUG) 30 | Slic3rStretchFilter().filter(simple_square_gcode) 31 | simple_square_gcode.write() 32 | 33 | 34 | def test_square_stretch_cura(): 35 | simple_square_gcode = open_gcode_file('cura_square.gcode') 36 | 37 | logging.basicConfig(level=logging.DEBUG) 38 | CuraStretchFilter().filter(simple_square_gcode) 39 | simple_square_gcode.write() 40 | -------------------------------------------------------------------------------- /gcodeutils/tests/test_visitor.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Tests for the visit submodule 4 | """ 5 | 6 | from gcodeutils.tests import open_gcode_file 7 | 8 | from gcodeutils.visit.iterator import GCodeIterator 9 | import gcodeutils.visit.pause_at_layer as pal 10 | 11 | from nose.tools import assert_equal 12 | 13 | __author__ = "wireddown" 14 | 15 | 16 | def test_pause_at_layer(): 17 | original_gcode = open_gcode_file("arc_raw_1.gcode") 18 | paused_gcode = open_gcode_file("arc_raw_1.gcode") 19 | 20 | iterator = GCodeIterator(paused_gcode, digits_of_precision=3) 21 | pause_at_layer = pal.PauseAtLayer(pause_layer_list=[1]) 22 | iterator.accept(pause_at_layer) 23 | 24 | result = original_gcode.diff(paused_gcode) 25 | expected_result = pal.PAUSE_COMMAND 26 | has_pause_command = True if expected_result in result else False 27 | message = "Expected '%s' in 'vs' part of diff message; whole message is '%s'" % (expected_result, result) 28 | assert_equal(has_pause_command, True, message) 29 | -------------------------------------------------------------------------------- /gcodeutils/visit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeograd/gcodeutils/f5f82e6cc2d5c0e4081c06f51b75442679b8455d/gcodeutils/visit/__init__.py -------------------------------------------------------------------------------- /gcodeutils/visit/iterator.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """An iterator for GCoder objects that accepts a GCodeVisitor 4 | """ 5 | 6 | import logging 7 | 8 | __author__ = "wireddown" 9 | 10 | LOGGER_NAME = "iterator" 11 | PRINTED_LAYER_KIND = "PRINTED" 12 | PARSED_LAYER_KIND = "parsed" 13 | 14 | 15 | class GCodeIteratorInformation(object): 16 | """This class is a simple POD that represents the state of the iterator 17 | """ 18 | 19 | def __init__(self, gcode, layer_number, layer_index, line_number, is_printed): 20 | self.gcode = gcode 21 | self.layer_number = layer_number 22 | self.layer_index = layer_index 23 | self.line_number = line_number 24 | self.is_printed = is_printed 25 | 26 | 27 | # Best write-up I could find 28 | # https://guillaume.segu.in/blog/code/487/optimizing-memory-usage-in-python-a-case-study/ 29 | class GCodeIterator(object): 30 | """This class iterates over a GCoder object and accepts a GCodeVisitor 31 | """ 32 | 33 | def __init__(self, gcode, digits_of_precision=2): 34 | self.__gcode = gcode 35 | self.__digits_of_precision = digits_of_precision 36 | self.__logger = logging.getLogger(LOGGER_NAME) 37 | self.__all_zs = sorted( 38 | [round(z, self.__digits_of_precision) for z in self.__gcode.all_zs] 39 | ) 40 | self.__logger.debug(" all_zs: %s", self.__all_zs) 41 | self.__logger.debug(" %s layers: %s", PRINTED_LAYER_KIND, len(self.__all_zs)) 42 | 43 | def accept(self, visitor): 44 | """Walk the GCode structure and visit each layer and each line 45 | """ 46 | parsed_layer_number = 0 47 | parsed_line_number = 0 48 | 49 | for parsed_layer in self.__gcode.all_layers: 50 | layer_index = self.__gcode.all_layers.index(parsed_layer) 51 | try: 52 | layer_z = round(parsed_layer.z, self.__digits_of_precision) 53 | layer_number = self.__all_zs.index(layer_z) 54 | is_printed = True 55 | except: 56 | layer_number = parsed_layer_number 57 | is_printed = False 58 | 59 | layer_kind = PRINTED_LAYER_KIND if is_printed else PARSED_LAYER_KIND 60 | self.__logger.debug( 61 | " visiting %-7s layer %+4s...", layer_kind, layer_number 62 | ) 63 | info = GCodeIteratorInformation( 64 | self.__gcode, layer_number, layer_index, parsed_line_number, is_printed 65 | ) 66 | visitor.will_visit_layer(parsed_layer, info) 67 | 68 | for line in parsed_layer: 69 | self.__logger.debug(" visiting line %s...", parsed_line_number) 70 | self.__logger.debug(line.raw) 71 | info = GCodeIteratorInformation( 72 | self.__gcode, layer_number, layer_index, parsed_line_number, is_printed 73 | ) 74 | visitor.visit_line(line, info) 75 | 76 | parsed_line_number += 1 77 | self.__logger.debug(" finished line") 78 | 79 | # "-1" because we haven't advanced to the next line /just/ yet 80 | info = GCodeIteratorInformation( 81 | self.__gcode, layer_number, layer_index, parsed_line_number - 1, is_printed 82 | ) 83 | visitor.did_visit_layer(parsed_layer, info) 84 | 85 | parsed_layer_number += 1 86 | self.__logger.debug(" finished layer") 87 | -------------------------------------------------------------------------------- /gcodeutils/visit/pause_at_layer.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """A visitor that pauses the print at a layer 4 | """ 5 | 6 | from gcodeutils.visit.visitor import GCodeVisitor 7 | 8 | __author__ = "wireddown" 9 | 10 | PAUSE_COMMAND = "M226" 11 | 12 | 13 | class PauseAtLayer(GCodeVisitor): 14 | """This class inserts a pause command at the start of each layer specified 15 | """ 16 | 17 | def __init__(self, pause_layer_list): 18 | self.__pause_layer_list = pause_layer_list 19 | 20 | def did_visit_layer(self, layer_as_pyline_list, gcode_iterator_info): 21 | """Inserts a pause command if the layer matches the list 22 | """ 23 | is_printed = gcode_iterator_info.is_printed 24 | is_pause_layer = gcode_iterator_info.layer_number in self.__pause_layer_list 25 | should_pause = is_printed and is_pause_layer 26 | if should_pause: 27 | gcode = gcode_iterator_info.gcode 28 | layer_index = gcode_iterator_info.layer_index 29 | commands_to_prepend = [PAUSE_COMMAND] 30 | gcode.prepend_to_layer(commands_to_prepend, layer_index) 31 | -------------------------------------------------------------------------------- /gcodeutils/visit/visitor.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """A base visitor for use with GCodeIterator objects 4 | """ 5 | 6 | __author__ = "wireddown" 7 | 8 | 9 | class GCodeVisitor(object): 10 | """The base class for visiting GCoder objects 11 | """ 12 | 13 | def will_visit_layer(self, layer_as_pyline_list, gcode_iterator_info): 14 | """Called before iteration begins on a layer 15 | """ 16 | pass 17 | 18 | def visit_line(self, pyline, gcode_iterator_info): 19 | """Called when iteration occurs on a line 20 | """ 21 | pass 22 | 23 | def did_visit_layer(self, layer_as_pyline_list, gcode_iterator_info): 24 | """Called after iteration completes on a layer 25 | """ 26 | pass 27 | -------------------------------------------------------------------------------- /models/temperature/temperature_hollow_tower.scad: -------------------------------------------------------------------------------- 1 | module hollow_tower(towerHeight=100, towerWidth=20, wallWidth=0.3, baseHeight=0.3) { 2 | translate([0, 0, towerHeight/2]) difference() { 3 | cube([towerWidth, towerWidth, towerHeight], center=true); 4 | translate([0, 0, baseHeight]) 5 | cube([towerWidth-2*wallWidth, towerWidth-2*wallWidth, towerHeight], center=true); 6 | } 7 | } 8 | 9 | hollow_tower(); 10 | -------------------------------------------------------------------------------- /models/temperature/temperature_hollow_tower.stl: -------------------------------------------------------------------------------- 1 | solid OpenSCAD_Model 2 | facet normal -1 0 0 3 | outer loop 4 | vertex -10 -10 0 5 | vertex -10 10 100 6 | vertex -10 10 0 7 | endloop 8 | endfacet 9 | facet normal -1 -0 0 10 | outer loop 11 | vertex -10 10 100 12 | vertex -10 -10 0 13 | vertex -10 -10 100 14 | endloop 15 | endfacet 16 | facet normal 0 0 1 17 | outer loop 18 | vertex 10 10 100 19 | vertex 9.7 9.7 100 20 | vertex 10 -10 100 21 | endloop 22 | endfacet 23 | facet normal 0 0 1 24 | outer loop 25 | vertex 10 10 100 26 | vertex -9.7 9.7 100 27 | vertex 9.7 9.7 100 28 | endloop 29 | endfacet 30 | facet normal 0 0 1 31 | outer loop 32 | vertex -9.7 9.7 100 33 | vertex -10 10 100 34 | vertex -9.7 -9.7 100 35 | endloop 36 | endfacet 37 | facet normal -0 0 1 38 | outer loop 39 | vertex -10 10 100 40 | vertex -9.7 9.7 100 41 | vertex 10 10 100 42 | endloop 43 | endfacet 44 | facet normal -0 0 1 45 | outer loop 46 | vertex 9.7 -9.7 100 47 | vertex 10 -10 100 48 | vertex 9.7 9.7 100 49 | endloop 50 | endfacet 51 | facet normal -0 0 1 52 | outer loop 53 | vertex -9.7 -9.7 100 54 | vertex 10 -10 100 55 | vertex 9.7 -9.7 100 56 | endloop 57 | endfacet 58 | facet normal 0 0 1 59 | outer loop 60 | vertex -9.7 -9.7 100 61 | vertex -10 -10 100 62 | vertex 10 -10 100 63 | endloop 64 | endfacet 65 | facet normal 0 0 1 66 | outer loop 67 | vertex -10 -10 100 68 | vertex -9.7 -9.7 100 69 | vertex -10 10 100 70 | endloop 71 | endfacet 72 | facet normal 1 -0 0 73 | outer loop 74 | vertex 10 -10 100 75 | vertex 10 10 0 76 | vertex 10 10 100 77 | endloop 78 | endfacet 79 | facet normal 1 0 0 80 | outer loop 81 | vertex 10 10 0 82 | vertex 10 -10 100 83 | vertex 10 -10 0 84 | endloop 85 | endfacet 86 | facet normal 0 1 -0 87 | outer loop 88 | vertex 10 10 0 89 | vertex -10 10 100 90 | vertex 10 10 100 91 | endloop 92 | endfacet 93 | facet normal 0 1 0 94 | outer loop 95 | vertex -10 10 100 96 | vertex 10 10 0 97 | vertex -10 10 0 98 | endloop 99 | endfacet 100 | facet normal 0 0 -1 101 | outer loop 102 | vertex -10 -10 0 103 | vertex 10 10 0 104 | vertex 10 -10 0 105 | endloop 106 | endfacet 107 | facet normal -0 0 -1 108 | outer loop 109 | vertex 10 10 0 110 | vertex -10 -10 0 111 | vertex -10 10 0 112 | endloop 113 | endfacet 114 | facet normal 0 -1 0 115 | outer loop 116 | vertex -10 -10 0 117 | vertex 10 -10 100 118 | vertex -10 -10 100 119 | endloop 120 | endfacet 121 | facet normal 0 -1 -0 122 | outer loop 123 | vertex 10 -10 100 124 | vertex -10 -10 0 125 | vertex 10 -10 0 126 | endloop 127 | endfacet 128 | facet normal 1 -0 0 129 | outer loop 130 | vertex -9.7 -9.7 100 131 | vertex -9.7 9.7 0.3 132 | vertex -9.7 9.7 100 133 | endloop 134 | endfacet 135 | facet normal 1 0 0 136 | outer loop 137 | vertex -9.7 9.7 0.3 138 | vertex -9.7 -9.7 100 139 | vertex -9.7 -9.7 0.3 140 | endloop 141 | endfacet 142 | facet normal -1 0 0 143 | outer loop 144 | vertex 9.7 -9.7 0.3 145 | vertex 9.7 9.7 100 146 | vertex 9.7 9.7 0.3 147 | endloop 148 | endfacet 149 | facet normal -1 -0 0 150 | outer loop 151 | vertex 9.7 9.7 100 152 | vertex 9.7 -9.7 0.3 153 | vertex 9.7 -9.7 100 154 | endloop 155 | endfacet 156 | facet normal 0 -1 0 157 | outer loop 158 | vertex -9.7 9.7 0.3 159 | vertex 9.7 9.7 100 160 | vertex -9.7 9.7 100 161 | endloop 162 | endfacet 163 | facet normal 0 -1 -0 164 | outer loop 165 | vertex 9.7 9.7 100 166 | vertex -9.7 9.7 0.3 167 | vertex 9.7 9.7 0.3 168 | endloop 169 | endfacet 170 | facet normal -0 0 1 171 | outer loop 172 | vertex -9.7 9.7 0.3 173 | vertex 9.7 -9.7 0.3 174 | vertex 9.7 9.7 0.3 175 | endloop 176 | endfacet 177 | facet normal 0 0 1 178 | outer loop 179 | vertex 9.7 -9.7 0.3 180 | vertex -9.7 9.7 0.3 181 | vertex -9.7 -9.7 0.3 182 | endloop 183 | endfacet 184 | facet normal 0 1 -0 185 | outer loop 186 | vertex 9.7 -9.7 0.3 187 | vertex -9.7 -9.7 100 188 | vertex 9.7 -9.7 100 189 | endloop 190 | endfacet 191 | facet normal 0 1 0 192 | outer loop 193 | vertex -9.7 -9.7 100 194 | vertex 9.7 -9.7 0.3 195 | vertex -9.7 -9.7 0.3 196 | endloop 197 | endfacet 198 | endsolid OpenSCAD_Model 199 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=tests 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | 25 | [MESSAGES CONTROL] 26 | 27 | # Enable the message, report, category or checker with the given id(s). You can 28 | # either give multiple identifier separated by comma (,) or put this option 29 | # multiple time. See also the "--disable" option for examples. 30 | #enable= 31 | 32 | # Disable the message, report, category or checker with the given id(s). You 33 | # can either give multiple identifiers separated by comma (,) or put this 34 | # option multiple times (only on the command line, not in the configuration 35 | # file where it should appear only once).You can also use "--disable=all" to 36 | # disable everything first and then reenable specific checks. For example, if 37 | # you want to run only the similarities checker, you can use "--disable=all 38 | # --enable=similarities". If you want to run only the classes checker, but have 39 | # no Warning level messages displayed, use"--disable=all --enable=classes 40 | # --disable=W" 41 | disable=R0904,C1001,W0232,R0903,I0011,I0012,W0704,W0142,W0212,W0613,W0702,R0201,R0901 42 | 43 | 44 | [REPORTS] 45 | 46 | # Set the output format. Available formats are text, parseable, colorized, msvs 47 | # (visual studio) and html. You can also give a reporter class, eg 48 | # mypackage.mymodule.MyReporterClass. 49 | output-format=text 50 | 51 | # Put messages in a separate file for each module / package specified on the 52 | # command line instead of printing them on stdout. Reports (if any) will be 53 | # written in a file name "pylint_global.[txt|html]". 54 | files-output=no 55 | 56 | # Tells whether to display a full report or only the messages 57 | reports=yes 58 | 59 | # Python expression which should return a note less than 10 (10 is the highest 60 | # note). You have access to the variables errors warning, statement which 61 | # respectively contain the number of errors / warnings messages and the total 62 | # number of statements analyzed. This is used by the global evaluation report 63 | # (RP0004). 64 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 65 | 66 | # Add a comment according to your evaluation note. This is used by the global 67 | # evaluation report (RP0004). 68 | comment=no 69 | 70 | # Template used to display messages. This is a python new-style format string 71 | # used to format the message information. See doc for all details 72 | #msg-template= 73 | 74 | 75 | [FORMAT] 76 | 77 | # Maximum number of characters on a single line. 78 | max-line-length=120 79 | 80 | # Regexp for a line that is allowed to be longer than the limit. 81 | ignore-long-lines=^\s*(# )??$ 82 | 83 | # Allow the body of an if to be on the same line as the test if there is no 84 | # else. 85 | single-line-if-stmt=no 86 | 87 | # List of optional constructs for which whitespace checking is disabled 88 | no-space-check=trailing-comma,dict-separator 89 | 90 | # Maximum number of lines in a module 91 | max-module-lines=1000 92 | 93 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 94 | # tab). 95 | indent-string=' ' 96 | 97 | # Number of spaces of indent required inside a hanging or continued line. 98 | indent-after-paren=4 99 | 100 | 101 | [TYPECHECK] 102 | 103 | # Tells whether missing members accessed in mixin class should be ignored. A 104 | # mixin class is detected if its name ends with "mixin" (case insensitive). 105 | ignore-mixin-members=yes 106 | 107 | # List of module names for which member attributes should not be checked 108 | # (useful for modules/projects where namespaces are manipulated during runtime 109 | # and thus extisting member attributes cannot be deduced by static analysis 110 | ignored-modules= 111 | 112 | # List of classes names for which member attributes should not be checked 113 | # (useful for classes with attributes dynamically set). 114 | ignored-classes=SQLObject 115 | 116 | # When zope mode is activated, add a predefined set of Zope acquired attributes 117 | # to generated-members. 118 | zope=no 119 | 120 | # List of members which are set dynamically and missed by pylint inference 121 | # system, and so shouldn't trigger E0201 when accessed. Python regular 122 | # expressions are accepted. 123 | generated-members=REQUEST,acl_users,aq_parent 124 | 125 | 126 | [BASIC] 127 | 128 | # Required attributes for module, separated by a comma 129 | required-attributes= 130 | 131 | # List of builtins function names that should not be used, separated by a comma 132 | bad-functions=map,filter,apply,input,file 133 | 134 | # Good variable names which should always be accepted, separated by a comma 135 | good-names=i,j,k,ex,Run,_,qs 136 | 137 | # Bad variable names which should always be refused, separated by a comma 138 | bad-names=foo,bar,baz,toto,tutu,tata 139 | 140 | # Colon-delimited sets of names that determine each other's naming style when 141 | # the name regexes allow several styles. 142 | name-group= 143 | 144 | # Include a hint for the correct naming format with invalid-name 145 | include-naming-hint=no 146 | 147 | # Regular expression matching correct function names 148 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 149 | 150 | # Naming hint for function names 151 | function-name-hint=[a-z_][a-z0-9_]{2,30}$ 152 | 153 | # Regular expression matching correct variable names 154 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 155 | 156 | # Naming hint for variable names 157 | variable-name-hint=[a-z_][a-z0-9_]{2,30}$ 158 | 159 | # Regular expression matching correct constant names 160 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 161 | 162 | # Naming hint for constant names 163 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 164 | 165 | # Regular expression matching correct attribute names 166 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 167 | 168 | # Naming hint for attribute names 169 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$ 170 | 171 | # Regular expression matching correct argument names 172 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 173 | 174 | # Naming hint for argument names 175 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$ 176 | 177 | # Regular expression matching correct class attribute names 178 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 179 | 180 | # Naming hint for class attribute names 181 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 182 | 183 | # Regular expression matching correct inline iteration names 184 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 185 | 186 | # Naming hint for inline iteration names 187 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ 188 | 189 | # Regular expression matching correct class names 190 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 191 | 192 | # Naming hint for class names 193 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 194 | 195 | # Regular expression matching correct module names 196 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 197 | 198 | # Naming hint for module names 199 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 200 | 201 | # Regular expression matching correct method names 202 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 203 | 204 | # Naming hint for method names 205 | method-name-hint=[a-z_][a-z0-9_]{2,30}$ 206 | 207 | # Regular expression which should only match function or class names that do 208 | # not require a docstring. 209 | no-docstring-rgx=__.*__ 210 | 211 | # Minimum line length for functions/classes that require docstrings, shorter 212 | # ones are exempt. 213 | docstring-min-length=-1 214 | 215 | 216 | [LOGGING] 217 | 218 | # Logging modules to check that the string format arguments are in logging 219 | # function parameter format 220 | logging-modules=logging 221 | 222 | 223 | [VARIABLES] 224 | 225 | # Tells whether we should check for unused import in __init__ files. 226 | init-import=no 227 | 228 | # A regular expression matching the name of dummy variables (i.e. expectedly 229 | # not used). 230 | dummy-variables-rgx=_$|dummy 231 | 232 | # List of additional names supposed to be defined in builtins. Remember that 233 | # you should avoid to define new builtins when possible. 234 | additional-builtins= 235 | 236 | 237 | [SIMILARITIES] 238 | 239 | # Minimum lines number of a similarity. 240 | min-similarity-lines=4 241 | 242 | # Ignore comments when computing similarities. 243 | ignore-comments=yes 244 | 245 | # Ignore docstrings when computing similarities. 246 | ignore-docstrings=yes 247 | 248 | # Ignore imports when computing similarities. 249 | ignore-imports=no 250 | 251 | 252 | [MISCELLANEOUS] 253 | 254 | # List of note tags to take in consideration, separated by a comma. 255 | notes=FIXME,XXX,TODO 256 | 257 | 258 | [CLASSES] 259 | 260 | # List of interface methods to ignore, separated by a comma. This is used for 261 | # instance to not check methods defines in Zope's Interface base class. 262 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 263 | 264 | # List of method names used to declare (i.e. assign) instance attributes. 265 | defining-attr-methods=__init__,__new__,setUp 266 | 267 | # List of valid names for the first argument in a class method. 268 | valid-classmethod-first-arg=cls 269 | 270 | # List of valid names for the first argument in a metaclass class method. 271 | valid-metaclass-classmethod-first-arg=mcs 272 | 273 | 274 | [DESIGN] 275 | 276 | # Maximum number of arguments for function / method 277 | max-args=5 278 | 279 | # Argument names that match this expression will be ignored. Default to name 280 | # with leading underscore 281 | ignored-argument-names=_.* 282 | 283 | # Maximum number of locals for function / method body 284 | max-locals=15 285 | 286 | # Maximum number of return / yield for function / method body 287 | max-returns=6 288 | 289 | # Maximum number of branch for function / method body 290 | max-branches=12 291 | 292 | # Maximum number of statements in function / method body 293 | max-statements=50 294 | 295 | # Maximum number of parents for a class (see R0901). 296 | max-parents=10 297 | 298 | # Maximum number of attributes for a class (see R0902). 299 | max-attributes=7 300 | 301 | # Minimum number of public methods for a class (see R0903). 302 | min-public-methods=2 303 | 304 | # Maximum number of public methods for a class (see R0904). 305 | max-public-methods=20 306 | 307 | 308 | [IMPORTS] 309 | 310 | # Deprecated modules which should not be used, separated by a comma 311 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 312 | 313 | # Create a graph of every (i.e. internal and external) dependencies in the 314 | # given file (report RP0402 must not be disabled) 315 | import-graph= 316 | 317 | # Create a graph of external dependencies in the given file (report RP0402 must 318 | # not be disabled) 319 | ext-import-graph= 320 | 321 | # Create a graph of internal dependencies in the given file (report RP0402 must 322 | # not be disabled) 323 | int-import-graph= 324 | 325 | 326 | [EXCEPTIONS] 327 | 328 | # Exceptions that will emit a warning when being caught. Defaults to 329 | # "Exception" 330 | overgeneral-exceptions=Exception 331 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3 | # 3. If at all possible, it is good practice to do this. If you cannot, you 4 | # will need to generate wheels for each Python version that you support. 5 | universal=1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """A setuptools based setup module. 2 | 3 | See: 4 | https://packaging.python.org/en/latest/distributing.html 5 | https://github.com/pypa/sampleproject 6 | """ 7 | 8 | # Always prefer setuptools over distutils 9 | from setuptools import setup, find_packages 10 | # To use a consistent encoding 11 | from codecs import open 12 | from os import path 13 | 14 | here = path.abspath(path.dirname(__file__)) 15 | 16 | # Get the long description from the relevant file 17 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 18 | long_description = f.read() 19 | 20 | setup( 21 | name='gcodeutils', 22 | 23 | # Versions should comply with PEP440. For a discussion on single-sourcing 24 | # the version across setup.py and the project code, see 25 | # https://packaging.python.org/en/latest/single_source_version.html 26 | version='1.3.2', 27 | 28 | description='Reprap oriented gcode utilities', 29 | long_description=long_description, 30 | 31 | # The project's main homepage. 32 | url='https://github.com/zeograd/gcodeutils', 33 | 34 | # Author details 35 | author='Olivier Jolly', 36 | author_email='olivier@pcedev.com', 37 | 38 | # Choose your license 39 | license='GPLv2+', 40 | 41 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 42 | classifiers=[ 43 | # How mature is this project? Common values are 44 | # 3 - Alpha 45 | # 4 - Beta 46 | # 5 - Production/Stable 47 | 'Development Status :: 5 - Production/Stable', 48 | 49 | 'Environment :: Console', 50 | 51 | # Indicate who your project is intended for 52 | 'Intended Audience :: Science/Research', 53 | 'Topic :: Multimedia :: Graphics :: 3D Modeling', 54 | 55 | # Pick your license as you wish (should match "license" above) 56 | 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', 57 | 58 | # Specify the Python versions you support here. In particular, ensure 59 | # that you indicate whether you support Python 2, Python 3 or both. 60 | 'Programming Language :: Python :: 2', 61 | 'Programming Language :: Python :: 2.7', 62 | 'Programming Language :: Python :: 3', 63 | 'Programming Language :: Python :: 3.2', 64 | 'Programming Language :: Python :: 3.3', 65 | 'Programming Language :: Python :: 3.4', 66 | 'Programming Language :: Python :: 3.5', 67 | ], 68 | 69 | # What does your project relate to? 70 | keywords='reprap, gcode, calibration, stretch, arc compensation, arc optimization, arc conversion', 71 | 72 | # You can just specify the packages manually here if your project is 73 | # simple. Or you can use find_packages(). 74 | packages=find_packages(exclude=['contrib', 'docs', 'tests*']), 75 | # packages=['gcodeutils', ], 76 | 77 | # List run-time dependencies here. These will be installed by pip when 78 | # your project is installed. For an analysis of "install_requires" vs pip's 79 | # requirements files see: 80 | # https://packaging.python.org/en/latest/requirements.html 81 | install_requires=[], 82 | 83 | # List additional groups of dependencies here (e.g. development 84 | # dependencies). You can install these using the following syntax, 85 | # for example: 86 | # $ pip install -e .[dev,test] 87 | extras_require={ 88 | 'dev': ['check-manifest', 'pylint'], 89 | 'test': ['nose'], 90 | }, 91 | 92 | # If there are data files included in your packages that need to be 93 | # installed, specify them here. If using Python 2.6 or less, then these 94 | # have to be included in MANIFEST.in as well. 95 | # package_data={ 96 | # 'sample': ['package_data.dat'], 97 | # }, 98 | 99 | # Although 'package_data' is the preferred approach, in some case you may 100 | # need to place data files outside of your packages. See: 101 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa 102 | # In this case, 'data_file' will be installed into '/my_data' 103 | # data_files=[('my_data', ['data/data_file'])], 104 | data_files=[('cura_plugins', ['cura_plugins/tempcal_plugin.py', 'cura_plugins/stretch_plugin.py']), ], 105 | 106 | # To provide executable scripts, use entry points in preference to the 107 | # "scripts" keyword. Entry points provide cross-platform support and allow 108 | # pip to create the appropriate form of executable for the target platform. 109 | entry_points={ 110 | 'console_scripts': [ 111 | 'gcode_tempcal=gcodeutils.gcode_tempcal:main', 112 | 'gcode_mod=gcodeutils.gcode_mod:main', 113 | 'gcode_stretch=gcodeutils.gcode_stretch:main', 114 | 'gcode_optimize_arcs=gcodeutils.gcode_optimize_arcs:main', 115 | ], 116 | }, 117 | 118 | test_suite = 'nose.collector', 119 | ) 120 | --------------------------------------------------------------------------------