├── .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 |
--------------------------------------------------------------------------------