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.
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/PKG-INFO +26 -12
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/README.md +25 -11
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/__init__.py +1 -1
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/_compiler.py +90 -42
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/interfaces/_gcode.py +7 -7
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/svg_parser/__init__.py +1 -1
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/svg_parser/_parser_methods.py +20 -2
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/LICENSE +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/pyproject.toml +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/__main__.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/LICENSE +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/__init__.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/__init__.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/interfaces/__init__.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/interfaces/_abstract_interface.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/compiler/interfaces/_fan_controlled_gcode.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/css_color.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/formulas.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/__init__.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_abstract_chain.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_abstract_curve.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_circular_arc.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_cubic_bazier.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_elliptical_arc.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_line.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_line_segment_chain.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_matrix.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_quadratic_bazier.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_raster_image.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_smooth_arc_chain.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/geometry/_vector.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/svg_parser/_path.py +0 -0
- {svg2gcode-3.3.1 → svg2gcode-3.3.4}/svg2gcode/svg_to_gcode/svg_parser/_transformation.py +0 -0
- {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.
|
|
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* (*
|
|
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
|
-
|
|
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
|
|
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* (*
|
|
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
|
-
|
|
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
|
|
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)
|
|
@@ -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
|
|
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.
|
|
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"
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 +
|
|
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' +
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
786
|
-
#
|
|
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
|
-
|
|
800
|
-
#
|
|
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:
|
|
805
|
-
self.body.extend([f"\n; no stroke color:
|
|
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
|
-
|
|
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 +
|
|
840
|
-
image_fill = np.full([img_height +
|
|
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
|
|
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";
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|