svg2gcode 3.3.1__tar.gz → 3.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/PKG-INFO +26 -12
  2. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/README.md +25 -11
  3. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/__init__.py +1 -1
  4. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/_compiler.py +90 -42
  5. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/interfaces/_gcode.py +7 -7
  6. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/svg_parser/__init__.py +1 -1
  7. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/svg_parser/_parser_methods.py +20 -2
  8. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/LICENSE +0 -0
  9. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/pyproject.toml +0 -0
  10. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/__main__.py +0 -0
  11. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/LICENSE +0 -0
  12. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/__init__.py +0 -0
  13. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/__init__.py +0 -0
  14. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/interfaces/__init__.py +0 -0
  15. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/interfaces/_abstract_interface.py +0 -0
  16. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/interfaces/_fan_controlled_gcode.py +0 -0
  17. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/css_color.py +0 -0
  18. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/formulas.py +0 -0
  19. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/__init__.py +0 -0
  20. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_abstract_chain.py +0 -0
  21. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_abstract_curve.py +0 -0
  22. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_circular_arc.py +0 -0
  23. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_cubic_bazier.py +0 -0
  24. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_elliptical_arc.py +0 -0
  25. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_line.py +0 -0
  26. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_line_segment_chain.py +0 -0
  27. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_matrix.py +0 -0
  28. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_quadratic_bazier.py +0 -0
  29. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_raster_image.py +0 -0
  30. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_smooth_arc_chain.py +0 -0
  31. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_vector.py +0 -0
  32. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/svg_parser/_path.py +0 -0
  33. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/svg_parser/_transformation.py +0 -0
  34. {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/svg_parser/debug_methods.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: svg2gcode
3
- Version: 3.3.1
3
+ Version: 3.3.4
4
4
  Summary: svg2gcode: convert an SVG (Scalable Vector Graphic) image to gcode.
5
5
  Keywords: scalable vector graphics,svg,image,laser cutter,laser engraving
6
6
  Author-email: Johannes Noordanus <mailjohannes.mailnoordanus@gmail.com>
@@ -14,7 +14,7 @@ Project-URL: Home, https://github.com/johannesnoordanus/svg2gcode
14
14
 
15
15
  # svg2gcode
16
16
 
17
- *svg2gcode* is a *WYSIWYG* converter of Scalable Vector Graphic *(SVG)* drawings. It is based on library *SvgToGcode* (*fork*: *https://github.com/johannesnoordanus/SvgToGcode*)<sup>(*)</sup>.
17
+ *svg2gcode* is a *WYSIWYG* converter of Scalable Vector Graphic *(SVG)* drawings. It is based on library *SvgToGcode* (*https://github.com/johannesnoordanus/SvgToGcode*)<sup>(*)</sup>.
18
18
 
19
19
  Drawings and images can be composed using Inkscape (or other *SVG* image software), saved to a *.svg* file and - *WYSIWYG* - converted to gcode.
20
20
  Color coding can be used to mark what part(s) of the drawing to cut, engrave or even ignore.
@@ -31,13 +31,16 @@ More info can be obtained by looking at the documentation and examples below and
31
31
 
32
32
  *svg2gcode* has three related programs: *image2gcode* mentioned above has similar capabilities but handles raster image files (like *png* and *jpg*) directly, *gcode2image* performs the inverse function and is capable of showing multiple writes (burns) to the same location and last *grblhud* which gives full control over gcode execution.
33
33
 
34
+ <sup>(*)</sup> Note that an upgraded and corrected version of this library is included.
35
+
34
36
  -----
35
37
 
36
38
  Please consider supporting me, so I can make this application better and add new functionality to it: <http://paypal.me/johannesnoordanus/5,00>
37
39
 
38
40
  My next update will add *fill-rule* 'nonzero' (see information on *fill-rule* below).
39
41
 
40
- <sup>(*)</sup> Note that an upgraded and corrected version of this library is included.
42
+ -----
43
+
41
44
  ### Supported SVG elements and attributes
42
45
 
43
46
  *SVG* *path* and *image* elements (specific: ```<svg:path ..>```tags and images```<svg:image ..>```tags) are supported. Other drawing objects must be converted to a *path* first to be able to translate them to a gcode sequence.
@@ -45,6 +48,25 @@ My next update will add *fill-rule* 'nonzero' (see information on *fill-rule* be
45
48
  Attributes *stroke* (color), *stroke-width*, *stroke_alpha*, *fill* (color), *fill-rule*, *fill_alpha* are supported.
46
49
  Currently only value *evenodd* of *fill-rule* is supported.
47
50
 
51
+ ### *WYSIWYG*
52
+
53
+ As seen above, not all *svg* elements are directly supported, but they are after a *path* conversion within the composer (Inkscape: ```menu Path->Object/Stroke to Path```). Without a *path* conversion *svg2gcode* simply ignores all elements except *path* and *image*. Text (fonts) require another step after *selecting* all text and clicking menu ```Object to Path```. Clicking ```menu Object->Ungroup``` generates a separate *path* for each letter which contains all relevant attributes. (Otherwise Inkscape uses a group tag ```<g``` to set some attributes for all letters within the text that are not repeated for each individual letter.)
54
+
55
+ Note that the latest version of *svg2gcode* (3.3.3) has support for ```<g``` tags (attribute inheritance) and is able to correctly convert text without ```Ungroup```.
56
+
57
+ You can use *gcode2image* to check the conversion result:
58
+ ```
59
+ > svg2gcode some.svg some.gc
60
+ > gcode2image --showimage --flip --grid --showorigin some.gc some.png
61
+ ```
62
+ The image shown should be the same as the one shown by the composer (Inkscape). This is also a good way to measure the conversion result. Note that ```--grid``` and ```--showorigin``` can give some inteference. To be sure run *gcode2image* without them.
63
+
64
+ #### Notes:
65
+ - drawing objects - within the composer - must be converted to a```path```to be translated to a gcode sequence
66
+ - image objects should **not** be converted to a ```path```
67
+ - images must be linked or embedded using base64 (Inkscape default).
68
+ - images can be in several formats (my tests included *.png* and *.jpg* image files)
69
+ - *SVG* source documents must be in unit 'mm' and set to ```1 'user unit' is 1 mm``` (Inkscape default)
48
70
  ### Important Commandline options
49
71
 
50
72
  Option ```--color_coded``` can be used to specify what part(s) of the drawing to cut, engrave or even ignore.
@@ -90,7 +112,7 @@ Note that engraving is the default action, so if option *color_coded* isn't set
90
112
 
91
113
  This makes it possible to make a drawing where all path elements are drawn in their respective colors to make an engraving of the entire vector drawing.
92
114
  When you set the engrave action for a set of colors, only those colors will be engraved (having their respective colors), all other paths having no action set are ignored.
93
- Also, when a path has no stroke attribute (or it is set to none) the element cannot be engraved (because it has no color) so it is interpreted as a cut action.
115
+ Also, when a path has no stroke attribute (or it is set to none) the element cannot be engraved (because it has no color) so it is ignored.
94
116
 
95
117
  Option ```--pathcut``` can be used to override all stroke attributes and force cutting of all *paths* of the *SVG*.
96
118
  Note that this option cannot be set at the same time as *--color_coded* above.
@@ -121,7 +143,6 @@ General optimizations
121
143
  - borders are drawn in parallel and in one go, following the *path* coordinates.
122
144
  - fill has support for alpha channel and 'fill-opacity'
123
145
 
124
-
125
146
  ### Install:
126
147
  ```
127
148
  >
@@ -347,10 +368,3 @@ One containing the drawings of the .svg, the other containing the images:
347
368
 
348
369
  Default *svg2gcode* uses constant burn mode *M3*. This can be changed by setting option *--no-constantburn* which selects burn mode *M4*. Mode *M4* is not suitable for engravings because it automatically compensates (laser)power for speed. This conflicts with the specific gcode settings given by *image2gcode* (called by *svg2gcode*) for each pixel. In fact some experiments show that *M4* causes loss of quality and image deterioration when speed is increased. On white oak images had too much black and grey which did not go away for substantially higher speed. When switched back to constant burn (*M3*) mode, the same high speed gave excellent images having a sepia (licht yellow brown) color tone.
349
370
 
350
- ### Notes:
351
- - drawing objects - within the composer - must be converted to a```path```to be translated to a gcode sequence
352
- - also, image objects should **not** be converted to a ```path```
353
- - images must be linked or embedded using base64.
354
- - images can be in several formats (my tests included *.png* and *.jpg* image files)
355
- - SVG source documents must be in unit 'mm' (and set to 1 'user unit' is 1 mm) which is the default for Inkscape (check document settings and look at the 'scaling' parameter)
356
-
@@ -1,6 +1,6 @@
1
1
  # svg2gcode
2
2
 
3
- *svg2gcode* is a *WYSIWYG* converter of Scalable Vector Graphic *(SVG)* drawings. It is based on library *SvgToGcode* (*fork*: *https://github.com/johannesnoordanus/SvgToGcode*)<sup>(*)</sup>.
3
+ *svg2gcode* is a *WYSIWYG* converter of Scalable Vector Graphic *(SVG)* drawings. It is based on library *SvgToGcode* (*https://github.com/johannesnoordanus/SvgToGcode*)<sup>(*)</sup>.
4
4
 
5
5
  Drawings and images can be composed using Inkscape (or other *SVG* image software), saved to a *.svg* file and - *WYSIWYG* - converted to gcode.
6
6
  Color coding can be used to mark what part(s) of the drawing to cut, engrave or even ignore.
@@ -17,13 +17,16 @@ More info can be obtained by looking at the documentation and examples below and
17
17
 
18
18
  *svg2gcode* has three related programs: *image2gcode* mentioned above has similar capabilities but handles raster image files (like *png* and *jpg*) directly, *gcode2image* performs the inverse function and is capable of showing multiple writes (burns) to the same location and last *grblhud* which gives full control over gcode execution.
19
19
 
20
+ <sup>(*)</sup> Note that an upgraded and corrected version of this library is included.
21
+
20
22
  -----
21
23
 
22
24
  Please consider supporting me, so I can make this application better and add new functionality to it: <http://paypal.me/johannesnoordanus/5,00>
23
25
 
24
26
  My next update will add *fill-rule* 'nonzero' (see information on *fill-rule* below).
25
27
 
26
- <sup>(*)</sup> Note that an upgraded and corrected version of this library is included.
28
+ -----
29
+
27
30
  ### Supported SVG elements and attributes
28
31
 
29
32
  *SVG* *path* and *image* elements (specific: ```<svg:path ..>```tags and images```<svg:image ..>```tags) are supported. Other drawing objects must be converted to a *path* first to be able to translate them to a gcode sequence.
@@ -31,6 +34,25 @@ My next update will add *fill-rule* 'nonzero' (see information on *fill-rule* be
31
34
  Attributes *stroke* (color), *stroke-width*, *stroke_alpha*, *fill* (color), *fill-rule*, *fill_alpha* are supported.
32
35
  Currently only value *evenodd* of *fill-rule* is supported.
33
36
 
37
+ ### *WYSIWYG*
38
+
39
+ As seen above, not all *svg* elements are directly supported, but they are after a *path* conversion within the composer (Inkscape: ```menu Path->Object/Stroke to Path```). Without a *path* conversion *svg2gcode* simply ignores all elements except *path* and *image*. Text (fonts) require another step after *selecting* all text and clicking menu ```Object to Path```. Clicking ```menu Object->Ungroup``` generates a separate *path* for each letter which contains all relevant attributes. (Otherwise Inkscape uses a group tag ```<g``` to set some attributes for all letters within the text that are not repeated for each individual letter.)
40
+
41
+ Note that the latest version of *svg2gcode* (3.3.3) has support for ```<g``` tags (attribute inheritance) and is able to correctly convert text without ```Ungroup```.
42
+
43
+ You can use *gcode2image* to check the conversion result:
44
+ ```
45
+ > svg2gcode some.svg some.gc
46
+ > gcode2image --showimage --flip --grid --showorigin some.gc some.png
47
+ ```
48
+ The image shown should be the same as the one shown by the composer (Inkscape). This is also a good way to measure the conversion result. Note that ```--grid``` and ```--showorigin``` can give some inteference. To be sure run *gcode2image* without them.
49
+
50
+ #### Notes:
51
+ - drawing objects - within the composer - must be converted to a```path```to be translated to a gcode sequence
52
+ - image objects should **not** be converted to a ```path```
53
+ - images must be linked or embedded using base64 (Inkscape default).
54
+ - images can be in several formats (my tests included *.png* and *.jpg* image files)
55
+ - *SVG* source documents must be in unit 'mm' and set to ```1 'user unit' is 1 mm``` (Inkscape default)
34
56
  ### Important Commandline options
35
57
 
36
58
  Option ```--color_coded``` can be used to specify what part(s) of the drawing to cut, engrave or even ignore.
@@ -76,7 +98,7 @@ Note that engraving is the default action, so if option *color_coded* isn't set
76
98
 
77
99
  This makes it possible to make a drawing where all path elements are drawn in their respective colors to make an engraving of the entire vector drawing.
78
100
  When you set the engrave action for a set of colors, only those colors will be engraved (having their respective colors), all other paths having no action set are ignored.
79
- Also, when a path has no stroke attribute (or it is set to none) the element cannot be engraved (because it has no color) so it is interpreted as a cut action.
101
+ Also, when a path has no stroke attribute (or it is set to none) the element cannot be engraved (because it has no color) so it is ignored.
80
102
 
81
103
  Option ```--pathcut``` can be used to override all stroke attributes and force cutting of all *paths* of the *SVG*.
82
104
  Note that this option cannot be set at the same time as *--color_coded* above.
@@ -107,7 +129,6 @@ General optimizations
107
129
  - borders are drawn in parallel and in one go, following the *path* coordinates.
108
130
  - fill has support for alpha channel and 'fill-opacity'
109
131
 
110
-
111
132
  ### Install:
112
133
  ```
113
134
  >
@@ -332,10 +353,3 @@ One containing the drawings of the .svg, the other containing the images:
332
353
  ### Burn mode M3/M4:
333
354
 
334
355
  Default *svg2gcode* uses constant burn mode *M3*. This can be changed by setting option *--no-constantburn* which selects burn mode *M4*. Mode *M4* is not suitable for engravings because it automatically compensates (laser)power for speed. This conflicts with the specific gcode settings given by *image2gcode* (called by *svg2gcode*) for each pixel. In fact some experiments show that *M4* causes loss of quality and image deterioration when speed is increased. On white oak images had too much black and grey which did not go away for substantially higher speed. When switched back to constant burn (*M3*) mode, the same high speed gave excellent images having a sepia (licht yellow brown) color tone.
335
-
336
- ### Notes:
337
- - drawing objects - within the composer - must be converted to a```path```to be translated to a gcode sequence
338
- - also, image objects should **not** be converted to a ```path```
339
- - images must be linked or embedded using base64.
340
- - images can be in several formats (my tests included *.png* and *.jpg* image files)
341
- - SVG source documents must be in unit 'mm' (and set to 1 'user unit' is 1 mm) which is the default for Inkscape (check document settings and look at the 'scaling' parameter)
@@ -1,4 +1,4 @@
1
1
  """
2
2
  svg2gcode: convert an SVG (Scalable Vector Graphic) image to gcode.
3
3
  """
4
- __version__ = "3.3.1"
4
+ __version__ = "3.3.4"
@@ -19,6 +19,7 @@ from svg2gcode.svg_to_gcode import DEFAULT_SETTING
19
19
  from svg2gcode.svg_to_gcode import TOLERANCES, SETTING, check_setting
20
20
 
21
21
  from svg2gcode.svg_to_gcode import css_color
22
+ from svg2gcode.svg_to_gcode.svg_parser import NAMESPACES, ElementTree_MY_PARENT
22
23
 
23
24
  from svg2gcode import __version__
24
25
 
@@ -66,6 +67,9 @@ class Compiler:
66
67
 
67
68
  self.interface.set_machine_parameters(self.settings)
68
69
 
70
+ # toggle
71
+ self.fan_on = self.settings['fan']
72
+
69
73
  if custom_header is None:
70
74
  custom_header = []
71
75
 
@@ -77,7 +81,7 @@ class Compiler:
77
81
  self.interface.set_distance_mode(self.settings["distance_mode"])] + custom_header
78
82
  self.footer = custom_footer
79
83
 
80
- # path object gcode
84
+ # path gcode
81
85
  self.body: list[str] = []
82
86
  # image gcode
83
87
  self.gcode: list[str] = []
@@ -128,12 +132,13 @@ class Compiler:
128
132
 
129
133
  :param passes: the number of passes that should be made. Every pass the machine moves_down (z-axis) by
130
134
  self.pass_depth and self.body is repeated.
131
- :return returns the assembled code. self.header + [self.body, -self.pass_depth] * passes + self.footer
135
+ :return returns the assembled g-code. [self.body, -self.pass_depth] * passes
132
136
  """
133
137
  if len(self.body) == 0:
134
138
  logger.debug("Compile with an empty body (no curves).")
135
139
  return ''
136
140
 
141
+ fan_off = False
137
142
  gcode = []
138
143
  for i in range(passes):
139
144
  gcode += [f"; pass #{i+1}"]
@@ -141,13 +146,11 @@ class Compiler:
141
146
 
142
147
  if i < (passes - 1) and self.settings["pass_depth"] > 0:
143
148
  # If it isn't the last pass, turn off the laser and move down
144
- gcode.append(self.interface.laser_off())
149
+ gcode.append(self.interface.laser_off(fan_off))
145
150
  gcode.append(self.interface.set_relative_coordinates())
146
151
  gcode.append(self.interface.linear_move(z=-self.settings["pass_depth"]))
147
152
  gcode.append(self.interface.set_distance_mode(self.settings["distance_mode"]))
148
153
 
149
- gcode += [self.interface.laser_off()]
150
-
151
154
  # remove all ""
152
155
  gcode = filter(lambda command: len(command) > 0, gcode)
153
156
 
@@ -160,11 +163,12 @@ class Compiler:
160
163
  """
161
164
 
162
165
  # laser off, fan on, M3 or M4 burn mode
163
- header_gc = ["M5","M8", 'M3' if self.settings["laser_mode"] == "constant" else 'M4']
164
- # laser off, fan off
165
- footer_gc = ["M5","M9"]
166
+ header_gc = ["M5"]
167
+ if self.settings['fan'] and self.settings["splitfile"]:
168
+ header_gc += ['M8']
169
+ header_gc += ['M3' if self.settings["laser_mode"] == "constant" else 'M4']
166
170
 
167
- return '\n'.join(header_gc + self.gcode + footer_gc)
171
+ return '\n'.join(header_gc + self.gcode)
168
172
 
169
173
  def compile_to_file(self, file_name: str, svg_file_name: str, curves: list[Curve], passes=1):
170
174
  """
@@ -182,15 +186,17 @@ class Compiler:
182
186
  self.append_curves(curves)
183
187
 
184
188
  header = '\n'.join(self.header) + '\n'
189
+ footer = '\n'.join(self.footer) + '\n'
185
190
 
186
191
  if len(self.body) > 0:
187
192
  # write path objects
188
193
  with open(file_name, 'w') as file:
189
- emit_program_end = self.interface.program_end() if (self.settings["splitfile"] or len(self.gcode) == 0) else ""
190
- file.write(self.gcode_file_header() + header + self.compile(passes=passes) + '\n' + emit_program_end)
194
+ #emit_program_end = self.interface.program_end() if (self.settings["splitfile"] or len(self.gcode) == 0) else ""
195
+ program_end = footer if (self.settings["splitfile"] or len(self.gcode) == 0) else ""
196
+ file.write(self.gcode_file_header() + header + self.compile(passes=passes) + '\n' + program_end)
191
197
  logger.info(f"Generated {file_name}")
192
198
  else:
193
- logger.warn(f'No path (curve) data found, skipping "{file_name}"')
199
+ logger.warn(f'No path (curve) data found nothing added to "{file_name}"')
194
200
 
195
201
  image_file_name = file_name.rsplit('.',1)[0] + "_images." + file_name.rsplit('.',1)[1]
196
202
  if len(self.gcode) == 0:
@@ -202,13 +208,13 @@ class Compiler:
202
208
  if self.settings["splitfile"]:
203
209
  # emit image objects to <filename>_images.<gcext>
204
210
  with open(image_file_name, 'w') as file:
205
- file.write(self.gcode_file_header() + header + self.compile_images() + '\n' + self.interface.program_end() + '\n')
211
+ file.write(self.gcode_file_header() + header + self.compile_images() + '\n' + footer)
206
212
  logger.info(f"Generated {image_file_name}")
207
213
  else:
208
214
  # emit images objects in same file
209
215
  open_mode = 'w' if len(self.body) == 0 else 'a+'
210
216
  with open(file_name, open_mode) as file:
211
- file.write((self.gcode_file_header() if len(self.body) == 0 else "") + '\n' + self.compile_images() + '\n' + self.interface.program_end() + '\n')
217
+ file.write((self.gcode_file_header() if len(self.body) == 0 else "") + '\n' + self.compile_images() + '\n' + footer)
212
218
  logger.info(f"Added image(s) to {file_name}")
213
219
 
214
220
  def append_line_chain(self, line_chain: LineSegmentChain, step: float, color: int = None, speed: int = None):
@@ -233,20 +239,24 @@ class Compiler:
233
239
  laser_power = color if color is not None else self.settings["laser_power"]
234
240
  movement_speed = speed if speed is not None else self.settings["movement_speed"]
235
241
 
242
+ # do not turn fan off
243
+ fan_off = False
244
+
236
245
  # Move to the next line_chain when the next line segment doesn't connect to the end of the previous one.
237
246
  if self.interface.position is None or abs(self.interface.position - start) > TOLERANCES["operation"]:
238
247
  if self.interface.position is None or self.settings["rapid_move"]:
239
248
  # move to the next line_chain: set laser off, rapid move to start of chain,
240
249
  # set movement (cutting) speed, set laser mode and power on
241
- code += [self.interface.laser_off(), self.interface.rapid_move(start.x, start.y),
250
+ code += [self.interface.laser_off(fan_off), self.interface.rapid_move(start.x, start.y),
242
251
  self.interface.set_movement_speed(movement_speed),
243
- self.interface.set_laser_mode(self.settings["laser_mode"]), self.interface.set_laser_power_value(laser_power)]
252
+ self.interface.set_laser_mode(self.settings["laser_mode"]), self.interface.set_laser_power_value(laser_power,self.fan_on)]
244
253
  else:
245
254
  # move to the next line_chain: set laser mode, set laser power to 0 (cutting is off),
246
255
  # set movement speed, (no rapid) move to start of chain, set laser to power
247
- code += [self.interface.set_laser_mode(self.settings["laser_mode"]), self.interface.set_laser_power_value(0),
256
+ code += [self.interface.set_laser_mode(self.settings["laser_mode"]), self.interface.set_laser_power_value(0,self.fan_on),
248
257
  self.interface.set_movement_speed(movement_speed), self.interface.linear_move(start.x, start.y),
249
258
  self.interface.set_laser_power_value(laser_power)]
259
+ self.fan_on = False
250
260
 
251
261
  self.boundingbox.update(start)
252
262
 
@@ -370,7 +380,7 @@ class Compiler:
370
380
  arguments["showoverscan"] = img_attrib['gcode_showoverscan'] if 'gcode_showoverscan' in img_attrib else self.settings["image_showoverscan"]
371
381
  arguments["offset"] = (float(img_attrib['x']), float(img_attrib['y']))
372
382
  arguments["name"] = img_attrib['id']
373
- arguments["invert"] = img_attrib['invert'] if 'invert' in img_attrib else False
383
+ arguments["invert"] = img_attrib['invert'] if 'invert' in img_attrib else True
374
384
 
375
385
  # get image parameters
376
386
  params = ''
@@ -472,6 +482,25 @@ class Compiler:
472
482
  if 'gcode_pathcut' in curve.path_attrib:
473
483
  style['pathcut'] = curve.path_attrib['gcode_pathcut']
474
484
 
485
+ # check missing attributes, if any
486
+ if ElementTree_MY_PARENT in curve.path_attrib:
487
+ parent = curve.path_attrib[ElementTree_MY_PARENT]
488
+
489
+ # find first parent g (group) tag, if any
490
+ while parent and parent.tag != "{%s}g" % NAMESPACES["svg"]:
491
+ if ElementTree_MY_PARENT in parent.attrib:
492
+ parent = parent.attrib[ElementTree_MY_PARENT]
493
+ else:
494
+ parent = None
495
+ if parent:
496
+ # fill in missing attribues, if any
497
+ for key, value in style.items():
498
+ if not value and key in ['fill', 'fill-rule', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity']:
499
+ # get missing attribute (if any)
500
+ if key in parent.attrib:
501
+ attrib = parent.attrib[key]
502
+ if attrib and attrib != 'none':
503
+ style[key] = attrib
475
504
  return style
476
505
 
477
506
  def color_coded_paths(self, set_color_coded = False) -> ():
@@ -671,7 +700,10 @@ class Compiler:
671
700
  # Invert b&w value and apply alpha channel - when available - to the inverted b&w value
672
701
  # Library function image2gcode - as it is now - cannot invert a color value and then apply the alpha channel.
673
702
  # Note that step 6 of '# Render svg fill attribute' below, sets option 'invert' of image2gcode to false to use fill_color directly.)
674
- fill_color = Image2gcode.linear_power(css_color.parse_css_color2bw8(style['fill']), self.settings["maximum_image_laser_power"])
703
+
704
+ # invert (byte value)
705
+ fill_color = Image2gcode.linear_power(css_color.parse_css_color2bw8(style['fill']), 255)
706
+
675
707
  if style['fill-rule'] is not None:
676
708
  fill_rule = style['fill-rule']
677
709
  if fill_rule == "nonzero":
@@ -687,7 +719,7 @@ class Compiler:
687
719
  fill_alpha = 1
688
720
  if len(rgba) == 4:
689
721
  fill_alpha = rgba[3]
690
- fill_color = fill_color * fill_alpha
722
+ fill_color = round(fill_color * fill_alpha)
691
723
 
692
724
  return (stroke_width, stroke_color, stroke_alpha, fill_color, fill_alpha, fill_rule)
693
725
 
@@ -747,14 +779,6 @@ class Compiler:
747
779
  stroke_width, stroke_color, stroke_alpha, fill_color, fill_alpha, fill_rule = get_style_info_of_line_chain(line_chain)
748
780
 
749
781
  if len(stroke_color):
750
- # Get steps (offsets) for the lines that make the border
751
- steps = [0]
752
- if stroke_width:
753
- for delta in range(stroke_width):
754
- if delta:
755
- steps.append(round(delta * pixel_size, self.precision))
756
- if not (delta == stroke_width and stroke_width % 2 != 0):
757
- steps.append(round(-delta * pixel_size, self.precision))
758
782
 
759
783
  # Get stroke alpha channel (opacity)
760
784
  # fill opacity attribute overrides rgba property
@@ -766,8 +790,8 @@ class Compiler:
766
790
 
767
791
  # engrave values
768
792
  # set inversed b&w value (and apply alpha channel, when available)
769
- inverse_bw = Image2gcode.linear_power(css_color.parse_css_color2bw8(stroke_color), \
770
- self.settings["maximum_image_laser_power"]) * stroke_alpha
793
+ inverse_bw = round(Image2gcode.linear_power(css_color.parse_css_color2bw8(stroke_color),
794
+ self.settings["maximum_image_laser_power"]) * stroke_alpha)
771
795
  # set laser head movement speed
772
796
  speed = self.settings["image_movement_speed"]
773
797
 
@@ -782,31 +806,51 @@ class Compiler:
782
806
  # color_coded set cut path for this stroke_color
783
807
  self.body.extend([f"\n; --color_coded cut path '{name_id}', color '{stroke_color}'"])
784
808
  render_pathwidth(line_chain, [0], None, None, boundingbox)
785
- else:
786
- # engrave path ...
809
+ elif inverse_bw:
810
+ # with path color, setengrave path ...
787
811
  pathignore, pathcut, pathengrave = self.color_coded_paths()
788
812
 
789
813
  # ... if color_coded did not set engrave (at all) or set engrave for this stroke_color
790
814
  if len(pathengrave) == 0 or engravepath:
815
+ # Get steps (offsets) for the lines that make the border
816
+ steps = [0]
817
+ if stroke_width:
818
+ for delta in range(stroke_width):
819
+ if delta:
820
+ steps.append(round(delta * pixel_size, self.precision))
821
+ if not (delta == stroke_width and stroke_width % 2 != 0):
822
+ steps.append(round(-delta * pixel_size, self.precision))
823
+
791
824
  if len(pathengrave) == 0:
792
825
  self.body.extend([f"\n; --color_coded: engrave not set, path '{name_id}', color '{stroke_color}'"])
793
826
  else:
794
827
  self.body.extend([f"\n; --color_coded: engrave path '{name_id}', color '{stroke_color}'"])
795
828
  render_pathwidth(line_chain, steps, inverse_bw, speed, boundingbox)
796
829
  else:
830
+ # ignore path
797
831
  self.body.extend([f"\n; --color_coded: ignore path '{name_id}', color '{stroke_color}'"])
798
832
 
799
- else:
800
- # color_coded isn't set: engrave path
833
+ elif inverse_bw:
834
+ # Get steps (offsets) for the lines that make the border
835
+ steps = [0]
836
+ if stroke_width:
837
+ for delta in range(stroke_width):
838
+ if delta:
839
+ steps.append(round(delta * pixel_size, self.precision))
840
+ if not (delta == stroke_width and stroke_width % 2 != 0):
841
+ steps.append(round(-delta * pixel_size, self.precision))
842
+
843
+ # color_coded isn't set: with path color engrave path
801
844
  self.body.extend([f"\n; engrave path '{name_id}', color '{stroke_color}'"])
802
845
  render_pathwidth(line_chain, steps, inverse_bw, speed, boundingbox)
803
846
  else:
804
- # line stroke isn't set: cut path
805
- self.body.extend([f"\n; no stroke color: cut path '{name_id}'"])
806
- render_pathwidth(line_chain, [0], None, None, boundingbox)
847
+ # line stroke isn't set: ignore path
848
+ self.body.extend([f"\n; no stroke color set: ignore path '{name_id}'"])
849
+ #render_pathwidth(line_chain, [0], None, None, boundingbox)
807
850
 
808
851
  # Render svg 'fill' attribute
809
- if not self.settings["nofill"] and fill_color is not None and boundingbox.get() is not None:
852
+ #if not self.settings["nofill"] and fill_color is not None and boundingbox.get() is not None:
853
+ if not self.settings["nofill"] and fill_color is not None:
810
854
  # fill a path
811
855
  # this is done in 6 steps:
812
856
  # step 1: create two raster images matching the bbox
@@ -817,7 +861,11 @@ class Compiler:
817
861
  # step 5: filter stray pixels (to remove noise from the action above)
818
862
  # step 6: generate gcode from image_fill (by execution function image2gcode)
819
863
 
820
- self.body.extend([f"\n; fill path '{name_id}'"])
864
+ # update boundingbox for this 'name_id'
865
+ for line_chain in path_curves[name_id]:
866
+ for line in line_chain:
867
+ boundingbox.update(line.start)
868
+ boundingbox.update(line.end)
821
869
 
822
870
  # get bounding box info from the path border
823
871
  lowerleft = boundingbox.get()[0]
@@ -836,8 +884,8 @@ class Compiler:
836
884
 
837
885
  # step 1: create two raster images matching the bbox
838
886
  # init
839
- image_mark = np.full([img_height + 2, img_width + scan_error], 255, dtype=np.uint8)
840
- image_fill = np.full([img_height + 2, img_width + scan_error], 0, dtype=np.uint8)
887
+ image_mark = np.full([img_height + 4, img_width + scan_error + 2], 255, dtype=np.uint8)
888
+ image_fill = np.full([img_height + 4, img_width + scan_error + 2], 0, dtype=np.uint8)
841
889
 
842
890
  # step 2: add marker lines just inside the line chains of the path
843
891
  # - determine the inside of the line chain
@@ -854,7 +902,7 @@ class Compiler:
854
902
  # delta line chain is inside or outside the base line chain, but when we compare
855
903
  # the bbox sizes we know.
856
904
  # Note that this method can be used to determine if a line chain is drawn clockwise
857
- # or anti-clockwise because a posive delta offset should be 'outside' the line chain
905
+ # or anti-clockwise because a positive delta offset should be 'outside' the line chain
858
906
  # in this case (depending on the definition/calculation of the delta function).
859
907
  # we can use this to implement the other svg fill rule: 'nonzero'.
860
908
  bbox = Boundingbox()
@@ -154,22 +154,22 @@ class Gcode(Interface):
154
154
 
155
155
  return command + ''
156
156
 
157
- def laser_off(self):
157
+ def laser_off(self, fan_off = True):
158
158
  self._laser_mode = 'M5'
159
159
  self._laser_mode_changed = True
160
- new_mode = "M5" + ("\nM9" if self._machine_params['fan'] else '') # laser off, (fan off when available)
160
+ new_mode = "M5" + ("\nM9" if self._machine_params['fan'] and fan_off else '') # laser off, (fan off when available)
161
161
  return f"{new_mode}"
162
162
 
163
- def set_laser_power(self, power):
163
+ def set_laser_power(self, power, fan_on = True):
164
164
  print("laser power!")
165
165
  if power < 0 or power > 1:
166
166
  raise ValueError(f"{power} is out of bounds. Laser power must be given between 0 and 1. "
167
167
  f"The interface will scale it correctly.")
168
168
 
169
169
  # set power for next linear move
170
- return self.set_laser_power_value(int(linear_map(self._machine_params['minimum_laser_power'], self._machine_params['maximum_laser_power'], power)))
170
+ return self.set_laser_power_value(int(linear_map(self._machine_params['minimum_laser_power'], self._machine_params['maximum_laser_power'], power)), fan_on)
171
171
 
172
- def set_laser_power_value(self, machine_value) -> str:
172
+ def set_laser_power_value(self, machine_value, fan_on = True) -> str:
173
173
  """
174
174
  Set laser power directly - in target machine values - not as a fraction.
175
175
  """
@@ -177,10 +177,10 @@ class Gcode(Interface):
177
177
  self._next_laser_power = machine_value
178
178
 
179
179
  # (fan on when available), laser_on
180
- new_mode = ("\nM8" if self._machine_params['fan'] else '') + ("\n" + self._laser_mode if self._laser_mode_changed else '')
180
+ new_mode = ("\nM8" if self._machine_params['fan'] and fan_on else '') + ("\n" + self._laser_mode if self._laser_mode_changed else '')
181
181
  self._laser_mode_changed = False
182
182
  # return laser mode (M3 constant laser power or M4 dynamic laser power) when laser mode changed
183
- return f"; Cut at F{self._next_speed} {self._unit}/min, power S{machine_value}{new_mode}"
183
+ return f"; F{self._next_speed} {self._unit}/min, power S{machine_value}{new_mode}"
184
184
 
185
185
  def set_laser_mode(self, mode):
186
186
  # set constant/dynamic laser power mode
@@ -8,4 +8,4 @@ specific maintenance notes:
8
8
 
9
9
  from svg2gcode.svg_to_gcode.svg_parser._transformation import Transformation
10
10
  from svg2gcode.svg_to_gcode.svg_parser._path import Path
11
- from svg2gcode.svg_to_gcode.svg_parser._parser_methods import parse_file, parse_string, parse_root
11
+ from svg2gcode.svg_to_gcode.svg_parser._parser_methods import parse_file, parse_string, parse_root, NAMESPACES, ElementTree_MY_PARENT
@@ -11,6 +11,23 @@ from svg2gcode.svg_to_gcode.geometry import Curve, RasterImage
11
11
  NAMESPACES = {'svg': 'http://www.w3.org/2000/svg',
12
12
  'xlink':'http://www.w3.org/1999/xlink'}
13
13
 
14
+ ElementTree_MY_PARENT = '__my_parent__'
15
+
16
+ def addParentInfo(et):
17
+ for child in et:
18
+ child.attrib[ElementTree_MY_PARENT] = et
19
+ addParentInfo(child)
20
+
21
+ def stripParentInfo(et):
22
+ for child in et:
23
+ child.attrib.pop(ElementTree_MY_PARENT, 'None')
24
+ stripParentInfo(child)
25
+
26
+ def getParent(et):
27
+ if ElementTree_MY_PARENT in et.attrib:
28
+ return et.attrib[ElementTree_MY_PARENT]
29
+ else:
30
+ return None
14
31
 
15
32
  def _has_style(element: ElementTree.Element, key: str, value: str) -> bool:
16
33
  """
@@ -178,8 +195,7 @@ def parse_root(root: ElementTree.Element, transform_origin=True, viewbox=None, d
178
195
  add_viewbox_transformation(transformation, transform_origin, viewbox, origin, scale, rotate))
179
196
 
180
197
  curves.extend(path.curves)
181
- else:
182
- if element.tag == "{%s}image" % NAMESPACES["svg"]:
198
+ elif element.tag == "{%s}image" % NAMESPACES["svg"]:
183
199
  # svg image
184
200
 
185
201
  # instantiate curve (image)
@@ -209,6 +225,7 @@ def parse_string(svg_string: str, transform_origin=True, viewbox=None, draw_hidd
209
225
  :return: A list of geometric curves describing the svg. Use the Compiler sub-module to compile them to gcode.
210
226
  """
211
227
  root = ElementTree.fromstring(svg_string)
228
+ addParentInfo(root)
212
229
  return parse_root(root, transform_origin, viewbox, draw_hidden, origin=delta_origin, scale=scale_factor, rotate=rotate_deg)
213
230
 
214
231
 
@@ -227,4 +244,5 @@ def parse_file(file_path: str, transform_origin=True, viewbox=None, draw_hidden=
227
244
  :return: A list of geometric curves describing the svg. Use the Compiler sub-module to compile them to gcode.
228
245
  """
229
246
  root = ElementTree.parse(file_path).getroot()
247
+ addParentInfo(root)
230
248
  return parse_root(root, transform_origin, viewbox, draw_hidden, origin=delta_origin, scale=scale_factor, rotate=rotate_deg)
File without changes
File without changes