Tue, 19 Jan 2021 20:25:47 +0100
NeoCube laser cutting improvements
16 | 1 | """ |
2 | Lasercutter library | |
43 | 3 | 2015-2019 by NeoSoft, Malte Di Donato |
16 | 4 | Intended to use standalone or implemented in Pronterface/Printrun |
5 | """ | |
6 | ||
7 | """ | |
8 | LASERCUT SETTINGS | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
9 | Will be overridden from pronterface settings |
16 | 10 | """ |
19 | 11 | E_FACTOR = 0.5 |
16 | 12 | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
13 | from PIL import Image |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
14 | import sys |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
15 | import math |
16 | 16 | |
17 | ||
20 | 18 | # GENERAL HEADER AND FOOTER GCODE |
19 | GCODE_HEAD = """ | |
20 | ; GCode generated by laser.py pronterface library (marlin code flavour) | |
43 | 21 | ; 2015-2019 by NeoSoft - Malte Di Donato |
22 | ||
23 | G21 ; Metric | |
24 | ; We assume Z is in focus height and laser head is focus at bottom left of image! | |
25 | G92 X0 Y0 E0; set zero position - new origin | |
26 | G90 ; absolute positioning | |
27 | M201 X1000 Y1000 ; Set acceleration | |
28 | M203 X1000 Y1000 ; Set max feedrate | |
29 | M209 S0 ; disable firmware retraction, we dont want to burn holes... | |
30 | ;M85 S0 ; Disable idle hold timeout (BUG!) | |
31 | M84 ; enable motors | |
32 | ||
33 | """ | |
34 | ||
35 | GCODE_FOOT = """ | |
36 | M400 ; Wait for all moves to finish | |
37 | M5 ; Force laser off! | |
38 | G0 X0 Y0 F%.4f ; Move back to origin | |
39 | ; M501 ; undo all settings made | |
40 | """ % (100*60) | |
41 | ||
42 | ||
43 | GCODE_HEAD_MELZI = """ | |
44 | ; GCode generated by laser.py pronterface library (marlin code flavour) | |
45 | ; 2015-2019 by NeoSoft - Malte Di Donato | |
20 | 46 | |
47 | G21 ; Metric | |
48 | ; We assume Z is in focus height and laser head is focus at bottom left of image! | |
49 | G92 X0 Y0 E0; set zero position - new origin | |
50 | G90 ; absolute positioning | |
51 | M82 ; Set extruder (laser) to absolute positioning | |
52 | M201 X1000 Y1000 E1000 ; Set acceleration | |
53 | M203 X1000 Y1000 Z4 E1000 ; Set max feedrate | |
54 | M209 S0 ; disable firmware retraction, we dont want to burn holes... | |
55 | M302 ; Allow cold extrudes - doesnt matter because we hack the extruder physically off with the M571 E mod | |
56 | M571 S1 E1 ; Activate Laser output on extrusion, but block real motor movement! | |
35 | 57 | ;M85 S0 ; Disable idle hold timeout (BUG!) |
32 | 58 | M84 ; enable motors |
59 | ||
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
60 | """ |
20 | 61 | |
43 | 62 | GCODE_FOOT_MELZI = """ |
33
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
32
diff
changeset
|
63 | M400 ; Wait for all moves to finish |
32 | 64 | M42 P28 S0 ; Force laser off! |
35 | 65 | ;M85 S30 ; re-enable idle hold timeout (BUG!) |
32 | 66 | G0 X0 Y0 F%.4f ; Move back to origin |
35 | 67 | M571 S0 E0 ; disable extruder firmware hack |
33
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
32
diff
changeset
|
68 | ; M501 ; undo all settings made |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
69 | """ % (100*60) |
16 | 70 | |
71 | ||
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
72 | class LasercutterSettings: |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
73 | """ |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
74 | Default settings object |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
75 | """ |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
76 | def __init__(self): |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
77 | self.lc_engrave_speed = 10 |
23 | 78 | # 30mm/sec works for wood (regulate the output power to something between 10-30%) |
79 | # 30mm/sec for black anodized aluminum to get a light engraving @ 100% power | |
80 | # 10mm/sec for black anodized aluminum to get maximum possible engraving! @ 100% power | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
81 | self.lc_travel_speed = 120 |
16 | 82 | |
43 | 83 | # insert config option to enable the Melzi Marlin FW Hack (M571) |
84 | self.lc_melzi_hack = False | |
85 | ||
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
86 | # BITMAP: |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
87 | self.lc_bitmap_speed_factor = 1.0 |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
88 | self.lc_dpi = 300 |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
89 | self.lc_grey_threshold = 0 |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
90 | self.lc_change_dir = True |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
91 | self.lc_invert_cut = True |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
92 | |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
93 | # HPGL: |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
94 | self.lc_hpgl_speed_factor = 1.0 |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
95 | |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
96 | # SVG: |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
97 | self.lc_svg_speed_factor = 1.0 |
25 | 98 | self.lc_svg_smoothness = 0.2 |
99 | self.lc_svg_width = 50 | |
100 | self.lc_svg_height = 50 | |
101 | self.lc_svg_scalemode = "scale" | |
102 | ||
16 | 103 | |
104 | class Lasercutter: | |
105 | """ | |
106 | Lasercutter methods | |
20 | 107 | parameters: log = logger function (fuction has to accept a string) |
16 | 108 | """ |
109 | def __init__(self, pronterwindow = None): | |
110 | if pronterwindow: | |
111 | self.pronterwindow = pronterwindow | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
112 | self.settings = pronterwindow.settings |
21 | 113 | #self.log = pronterwindow.log |
114 | self.log = self.log_print | |
20 | 115 | self.pronterwindow.clear_log(None) |
16 | 116 | else: |
117 | self.pronterwindow = None | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
118 | self.settings = LasercutterSettings() |
16 | 119 | self.log = lambda : None |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
120 | |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
121 | # STATIC DEFINITIONS, DO NOT CHANGE WORLD's RULES! |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
122 | self.INCH = 25.4 # mm |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
123 | self.MM_PIXEL = round(self.INCH / self.settings.lc_dpi, 4) |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
124 | self.STEPS_PIXEL = self.MM_PIXEL * 80 # mine is 80 steps/mm on XY |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
125 | |
23 | 126 | self.log("Lasercutter library initialized\n%d DPI (%f mm/pixel)" % ( |
127 | self.settings.lc_dpi, self.MM_PIXEL)) | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
128 | if self.STEPS_PIXEL <= 5: |
23 | 129 | self.log("WARNING: STEPS PER PIXEL NEEDS TO BE > 5 (otherwise marlin joins lines): %f" % ( |
130 | self.STEPS_PIXEL)) | |
20 | 131 | self.log("Travel/Engrave speed: %d mm/sec, %d mm/sec" % ( |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
132 | self.settings.lc_travel_speed, self.settings.lc_engrave_speed) ) |
20 | 133 | self.log("") |
16 | 134 | |
21 | 135 | def log_print(self, msg): |
136 | print(msg) | |
137 | ||
16 | 138 | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
139 | def pixel2bit(self, pixel): |
16 | 140 | """Convert the pixel value to a bit.""" |
141 | # some really weird stuff here ;-P | |
142 | ||
143 | # RGB to greyscale | |
144 | #print pixel | |
145 | #print type(pixel) | |
146 | if isinstance(pixel, tuple): | |
147 | #rgb | |
148 | pixel = pixel[0]*0.2989 + pixel[1]*0.5870 + pixel[2]*0.1140 | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
149 | if pixel > self.settings.lc_grey_threshold: |
16 | 150 | return 1 |
151 | else: | |
152 | return 0 | |
153 | ||
154 | # color palette | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
155 | # TODO: get the grey value of the palette index instead of using pixel which is the palette index? |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
156 | if pixel <= self.settings.lc_grey_threshold: |
16 | 157 | return 1 |
158 | else: | |
159 | return 0 | |
160 | ||
161 | def image2gcode(self, filename): | |
162 | """ | |
163 | Open a image file and get the basic information about it. | |
164 | Then convert it to gcode (replacing the existing gcode buffer contents) | |
165 | """ | |
166 | try: | |
167 | im = Image.open(filename) | |
168 | except: | |
169 | self.log("Unable to open %s" % filename) | |
170 | return False | |
171 | ||
172 | self.log("Converting Image for lasercut:") | |
173 | self.log("File: %s" % filename) | |
174 | self.log("format: %s, mode: %s" % (im.format, im.mode)) | |
175 | width,height = im.size | |
176 | self.log("size: %d x %d pixels" % im.size) | |
177 | ||
178 | pix = im.load() | |
179 | ||
180 | fo = open(filename + ".g", "w") | |
43 | 181 | if self.settings.lc_melzi_hack: |
182 | fo.write("; Filename: %s\n%s" % (filename, GCODE_HEAD_MELZI)) | |
183 | else: | |
184 | fo.write("; Filename: %s\n%s" % (filename, GCODE_HEAD)) | |
16 | 185 | |
186 | fo.write(";Start engraving the raster image: %dx%d points @ %d DPI = %.0fx%.0f mm\n\n" % ( | |
23 | 187 | im.size[0], im.size[1], self.settings.lc_dpi, |
188 | im.size[0] * self.MM_PIXEL, im.size[1] * self.MM_PIXEL) ) | |
16 | 189 | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
190 | INVERT_Y = self.MM_PIXEL * (im.size[1] -1) * (-1) |
16 | 191 | DIR = 1 |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
192 | travel_speed = self.settings.lc_travel_speed * 60 |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
193 | engrave_speed = self.settings.lc_engrave_speed * 60 * self.settings.lc_bitmap_speed_factor |
16 | 194 | for X in range(im.size[0]): |
23 | 195 | gcode_col = "" |
42
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
196 | first_ymm = None |
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
197 | first_xmm = None |
16 | 198 | E = 0 |
199 | last_bit = 1 # we engrave on black pixel = 0 | |
200 | START_Y = 0 | |
201 | if DIR > 0: | |
202 | range_start = 0 | |
203 | range_stop = im.size[1] | |
204 | else: | |
205 | range_start = im.size[1] -1 | |
206 | range_stop = -1 | |
207 | ||
208 | for Y in range(range_start, range_stop, DIR): | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
209 | YMM = abs((Y * self.MM_PIXEL) + INVERT_Y) |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
210 | XMM = X * self.MM_PIXEL |
42
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
211 | |
16 | 212 | #print "X %d Y %d" % (X, Y) |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
213 | bit = self.pixel2bit(pix[X, Y]) |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
214 | if self.settings.lc_invert_cut: |
16 | 215 | if bit == 0: |
216 | bit = 1 | |
217 | else: | |
218 | bit = 0 | |
219 | if last_bit == bit: | |
220 | if bit == 1: | |
221 | # nothing to do, | |
222 | continue | |
223 | else: | |
224 | # are we at the end of Y range? | |
225 | #print Y | |
226 | if (Y == (im.size[1] - 1)) or (Y == 0): | |
227 | # draw line | |
228 | if DIR > 0: | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
229 | E = E + self.MM_PIXEL * (Y - START_Y) |
16 | 230 | else: |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
231 | E = E + self.MM_PIXEL * (START_Y - Y) |
23 | 232 | gcode_col += "G1 X%.4f Y%.4f E%.4f F%.4f\n" % ( |
233 | XMM, YMM, E * E_FACTOR, engrave_speed) | |
42
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
234 | if not first_xmm: |
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
235 | first_xmm = XMM + 0.1 # little offset needed! |
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
236 | first_ymm = YMM * 1 |
16 | 237 | else: |
42
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
238 | if not first_xmm: |
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
239 | first_xmm = XMM + 0.1 # little offset needed! |
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
240 | first_ymm = YMM * 1 |
16 | 241 | # bit value has changed! |
242 | if bit == 0: | |
243 | # jump to start of line to write | |
244 | START_Y = Y | |
23 | 245 | gcode_col += "G0 X%.4f Y%.4f F%.4f\n" % ( |
246 | XMM, YMM, travel_speed) | |
42
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
247 | last_xmm = None |
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
248 | last_ymm = None |
16 | 249 | else: |
250 | # end of line to write | |
251 | if DIR > 0: | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
252 | E = E + (self.MM_PIXEL * (Y - START_Y)) |
16 | 253 | else: |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
254 | E = E + (self.MM_PIXEL * (START_Y - Y)) |
23 | 255 | gcode_col += "G1 X%.4f Y%.4f E%.4f F%.4f\n" % ( |
256 | XMM, YMM, E * E_FACTOR, engrave_speed) | |
16 | 257 | last_bit = bit |
23 | 258 | if gcode_col <> "": |
259 | # we skip empty columns | |
42
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
260 | # place last position as G0 to be sure to switch off laser immediately at finish of the line! |
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
261 | if first_xmm: |
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
262 | fo.write("G0 X%.4f Y%.4f F%.4f ; force laser off\n" % ( |
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
263 | first_xmm, first_ymm, travel_speed)) |
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
37
diff
changeset
|
264 | |
23 | 265 | fo.write("M400 ; X=%d printing row: direction %i\nG92 E0\n%s" % ( |
266 | X, DIR, gcode_col)) | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
267 | if self.settings.lc_change_dir: |
16 | 268 | DIR = DIR * (-1) # change y direction on every X |
269 | ||
43 | 270 | if self.settings.lc_melzi_hack: |
271 | fo.write(GCODE_FOOT_MELZI) | |
272 | else: | |
273 | fo.write(GCODE_FOOT) | |
16 | 274 | fo.close() |
275 | ||
276 | if self.pronterwindow: | |
277 | self.log("") | |
278 | self.pronterwindow.load_gcode_async(filename + '.g') | |
279 | ||
280 | def hpgl2gcode(self, filename): | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
281 | # FOR HPGL: |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
282 | SCALE_FACTOR = 1.0 / 40.0 # 40 plotter units |
16 | 283 | OFFSET_X = 0.0 |
284 | OFFSET_Y = 0.0 | |
285 | ||
286 | self.log("Converting HPGL plot for lasercut:") | |
287 | self.log("File: %s" % filename) | |
288 | ||
289 | fi = open(filename, "r") | |
290 | fo = open(filename + ".g", "w") | |
43 | 291 | if self.settings.lc_melzi_hack: |
292 | fo.write("; Filename: %s\n%s" % (filename, GCODE_HEAD_MELZI)) | |
293 | else: | |
294 | fo.write("; Filename: %s\n%s" % (filename, GCODE_HEAD)) | |
16 | 295 | |
296 | G = "0" | |
297 | LASER_STATE = 0 | |
298 | last_coord = [0.0,0.0] | |
299 | last_cmd = "" | |
300 | ||
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
301 | travel_speed = self.settings.lc_travel_speed * 60 |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
302 | engrave_speed = self.settings.lc_engrave_speed * 60 * self.settings.lc_hpgl_speed_factor |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
303 | |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
304 | |
16 | 305 | for line in fi.readlines(): |
306 | for action in line.split(";"): | |
307 | action = action.strip() | |
308 | if action != "": | |
309 | cmd = action[:2] | |
310 | if cmd == "PD": | |
311 | LASER_STATE = 1 | |
312 | elif cmd == "PU": | |
313 | LASER_STATE = 0 | |
314 | if last_cmd == "PD": | |
315 | OFFSET_X = coord[0] * -1 | |
316 | OFFSET_Y = coord[1] * -1 | |
23 | 317 | fo.write("; PD PU detected, set coord offset %.4f x %.4f mm\n" % ( |
318 | OFFSET_X, OFFSET_Y)) | |
16 | 319 | elif cmd == "PA" or cmd == "PR": |
320 | # TODO: convert relative coordinates to absolute here! | |
321 | coord = action[2:].split(",") | |
322 | coord[0] = (float(coord[0]) + OFFSET_X) * SCALE_FACTOR | |
323 | coord[1] = (float(coord[1]) + OFFSET_Y) * SCALE_FACTOR | |
324 | if LASER_STATE: | |
325 | EN = " E%.4f F%.4f" % ( | |
326 | E_FACTOR * math.hypot(coord[0] - last_coord[0], coord[1] - last_coord[1]), | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
327 | engrave_speed) |
16 | 328 | else: |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
329 | EN = " F%.4f" % travel_speed |
16 | 330 | |
331 | fo.write("G%d X%.4f Y%.4f%s\n" % ( | |
332 | LASER_STATE, coord[0], coord[1], EN) ) | |
333 | last_coord = coord | |
334 | elif cmd == "IN": | |
335 | pass | |
336 | elif cmd == "PT": | |
337 | print "Ignoring pen thickness" | |
338 | else: | |
339 | print "UNKNOWN: %s" % action | |
340 | last_cmd = cmd | |
341 | ||
43 | 342 | if self.settings.lc_melzi_hack: |
343 | fo.write(GCODE_FOOT_MELZI) | |
344 | else: | |
345 | fo.write(GCODE_FOOT) | |
16 | 346 | fi.close() |
347 | fo.close() | |
348 | ||
349 | if self.pronterwindow: | |
350 | self.log("") | |
351 | self.pronterwindow.load_gcode_async(filename + '.g') | |
352 | ||
25 | 353 | def svg2gcode(self, filename): |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
354 | # Imports for SVG |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
355 | import xml.etree.ElementTree as ET |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
356 | from svg2gcode import shapes as shapes_pkg |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
357 | from svg2gcode.shapes import point_generator |
35 | 358 | from svg2gcode import simplepath, cspsubdiv, cubicsuperpath, simpletransform |
359 | ||
36 | 360 | bed_max_x = float(self.settings.lc_svg_width) |
361 | bed_max_y = float(self.settings.lc_svg_height) | |
18 | 362 | |
25 | 363 | self.log("Generating paths from SVG (outlines only)...") |
16 | 364 | svg_shapes = set(['rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'path']) |
365 | tree = ET.parse(filename) | |
366 | root = tree.getroot() | |
367 | ||
28 | 368 | # Todo: force viewbox values configurable? |
369 | #width = root.get('width') | |
370 | #height = root.get('height') | |
371 | # if width == None or height == None: | |
372 | viewbox = root.get('viewBox') | |
373 | if viewbox: | |
35 | 374 | _, _, width, height = viewbox.split() |
375 | else: | |
376 | # no viewbox element, try to get from svg root element | |
377 | try: | |
378 | width = root.attrib["width"] | |
379 | height = root.attrib["height"] | |
380 | self.log("No ViewBox, got dimensions from root element)") | |
381 | except: | |
382 | width = None | |
383 | height = None | |
16 | 384 | |
385 | if width == None or height == None: | |
386 | self.log("Unable to get width and height for the svg!") | |
387 | return False | |
28 | 388 | else: |
389 | self.log("SVG Dimensions are %s x %s" % (width, height)) | |
16 | 390 | |
27 | 391 | # TODO: use cm or mm as absolute dimensions! |
392 | width = float(width.replace("px", "").replace("pt", "").replace("mm", "")) | |
393 | height = float(height.replace("px", "").replace("pt", "").replace("mm", "")) | |
28 | 394 | |
25 | 395 | smoothness = self.settings.lc_svg_smoothness |
396 | if smoothness < 0.1: smoothness = 0.1 | |
16 | 397 | |
28 | 398 | # get the minimum x and y values to get an offset to 0,0 |
399 | ofs_x = 99999999999.0 | |
400 | ofs_y = 99999999999.0 | |
401 | max_x = -99999999999.0 | |
402 | max_y = -99999999999.0 | |
403 | for elem in root.iter(): | |
404 | try: | |
405 | _, tag_suffix = elem.tag.split('}') | |
406 | except ValueError: | |
407 | continue | |
408 | if tag_suffix in svg_shapes: | |
409 | shape_class = getattr(shapes_pkg, tag_suffix) | |
410 | shape_obj = shape_class(elem) | |
411 | d = shape_obj.d_path() | |
412 | m = shape_obj.transformation_matrix() | |
413 | ||
414 | if d: | |
415 | p = point_generator(d, m, smoothness) | |
416 | start = True | |
417 | for x,y,pen in p: | |
418 | if x < ofs_x: | |
419 | ofs_x = x | |
420 | if y < ofs_y: | |
421 | ofs_y = y | |
422 | if x > max_x: | |
423 | max_x = x | |
424 | if y > max_y: | |
425 | max_y = y | |
36 | 426 | |
427 | if self.settings.lc_svg_offset: | |
428 | ofs_x *= -1 | |
429 | ofs_y *= -1 | |
430 | max_x += ofs_x | |
431 | max_y += ofs_y | |
432 | self.log("Calculated Offset to 0,0 is %f,%f" % (ofs_x, ofs_y)) | |
433 | else: | |
434 | ofs_x = 0 | |
435 | ofs_y = 0 | |
28 | 436 | |
437 | """ | |
438 | self.log("Calculated Dimension is %f,%f" % (max_x, max_y)) | |
439 | width = max_x | |
440 | height = max_y | |
441 | """ | |
442 | ||
36 | 443 | if self.settings.lc_svg_scalemode == "original": |
444 | scale_x = 1.0 | |
445 | scale_y = 1.0 | |
446 | elif self.settings.lc_svg_scalemode == "scale": | |
447 | scale_x = bed_max_x / width | |
448 | scale_y = bed_max_y / height | |
449 | if (scale_x * height) > bed_max_y: | |
450 | # use y scale | |
451 | scale_x = scale_y | |
452 | elif (scale_y * width) > bed_max_x: | |
453 | # use x scale | |
454 | scale_y = scale_x | |
455 | # double-check | |
456 | if (scale_x * width > bed_max_x) or (scale_y * height > bed_max_y): | |
457 | scale_x = scale_y = min(bed_max_x, bed_max_y) / max(width, height) | |
25 | 458 | else: |
28 | 459 | scale_x = bed_max_x / width |
460 | scale_y = bed_max_y / height | |
16 | 461 | |
462 | self.log("Scaling factor: %.2f, %.2f" % (scale_x,scale_y)) | |
463 | ||
464 | fo = open(filename + ".g", "w") | |
43 | 465 | if self.settings.lc_melzi_hack: |
466 | fo.write("; Filename: %s\n%s" % (filename, GCODE_HEAD_MELZI)) | |
467 | fo.write("M571 S0 E1 ; On SVG we control the laser by ourself\n") | |
468 | else: | |
469 | fo.write("; Filename: %s\n%s" % (filename, GCODE_HEAD)) | |
16 | 470 | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
471 | travel_speed = self.settings.lc_travel_speed * 60 |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
472 | engrave_speed = self.settings.lc_engrave_speed * 60 * self.settings.lc_svg_speed_factor |
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
21
diff
changeset
|
473 | |
35 | 474 | errors = 0 |
37 | 475 | elemidx = 0 |
28 | 476 | |
16 | 477 | for elem in root.iter(): |
37 | 478 | elemidx += 1 |
16 | 479 | try: |
480 | _, tag_suffix = elem.tag.split('}') | |
481 | except ValueError: | |
482 | continue | |
483 | ||
484 | if tag_suffix in svg_shapes: | |
37 | 485 | try: |
486 | styles = elem.attrib['style'].split(';') | |
487 | except KeyError: | |
488 | styles = [] | |
489 | skip = False | |
490 | for style in styles: | |
491 | style = style.split(':') | |
492 | if style[0] == 'stroke': | |
493 | # ignore all stroke colors which are not #000000 | |
494 | if style[1] != "#000000": | |
495 | self.log("Ignoring shape %s (%i) by stroke color" % (tag_suffix, elemidx)) | |
496 | skip = True | |
497 | break | |
43 | 498 | # TODO: do a config option to enter the CUT color or something |
37 | 499 | if skip: |
500 | continue | |
501 | ||
502 | self.log("Parsing shape: %s (%i)" % (tag_suffix, elemidx)) | |
16 | 503 | shape_class = getattr(shapes_pkg, tag_suffix) |
504 | shape_obj = shape_class(elem) | |
505 | d = shape_obj.d_path() | |
32 | 506 | mat = shape_obj.transformation_matrix() |
16 | 507 | |
508 | if d: | |
43 | 509 | if self.settings.lc_melzi_hack: |
510 | fo.write("M400 ; start %s\n" % (tag_suffix)) | |
511 | fo.write("G92 E0\n") | |
16 | 512 | E = 0 |
513 | xo = 0 | |
514 | yo = 0 | |
32 | 515 | idxo = None |
516 | #p = point_generator(d, mat, smoothness) | |
517 | ||
518 | simple_path = simplepath.parsePath(d) | |
519 | if len(simple_path) == 0: | |
520 | self.log("Path length zero!") | |
521 | continue | |
522 | ||
523 | p = cubicsuperpath.parsePath(d) | |
524 | ||
525 | if mat: | |
526 | simpletransform.applyTransformToPath(mat, p) | |
527 | ||
528 | for sp in p: | |
529 | cspsubdiv.subdiv( sp, smoothness) | |
33
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
32
diff
changeset
|
530 | #self.log("Laser ON at: " + repr(sp[0][0])) |
32 | 531 | x = sp[0][0][0] + ofs_x |
532 | y = sp[0][0][1] + ofs_y | |
533 | y = height - y # invert the bed | |
534 | xs = scale_x * x | |
535 | ys = scale_y * y | |
43 | 536 | if self.settings.lc_melzi_hack: |
537 | fo.write("M400 ; Wait for all moves to finish\n") | |
538 | fo.write("M42 P28 S0 ; Turn off laser\n") | |
539 | fo.write("G0 X%0.4f Y%0.4f F%.4f ; Move to start of shape\n" % ( | |
540 | xs, ys, travel_speed)) | |
541 | fo.write("M400 ; Wait for all moves to finish\n") | |
542 | fo.write("M42 P28 S255 ; Turn on laser\n") | |
543 | else: | |
544 | fo.write("M5 ; Turn off laser\n") | |
545 | fo.write("G0 X%0.4f Y%0.4f F%.4f ; Move to start of shape\n" % ( | |
546 | xs, ys, travel_speed)) | |
547 | # todo: laser power as parameter? | |
548 | fo.write("M3 S100 ; Turn on laser\n") | |
19 | 549 | |
32 | 550 | xo = xs |
551 | yo = ys | |
552 | object_xs = xs | |
553 | object_ys = ys | |
554 | ||
555 | for csp in sp: | |
556 | ctrl_pt1 = csp[0] | |
557 | ctrl_pt2 = csp[1] | |
558 | end_pt = csp[2] | |
559 | ||
560 | x = end_pt[0] + ofs_x | |
561 | y = end_pt[1] + ofs_y | |
562 | ||
563 | y = height - y # invert the bed | |
564 | xs = round(scale_x * x, 4) | |
565 | ys = round(scale_y * y, 4) | |
566 | if xo == xs and yo == ys: continue | |
567 | ||
568 | #self.log(" Point " + repr(end_pt)) | |
35 | 569 | e_distance = math.hypot(xs - xo, ys - yo) |
570 | xo = xs | |
571 | yo = ys | |
572 | E = E + (e_distance) | |
32 | 573 | |
574 | if xs >= 0 and xs <= bed_max_x+0.1 and ys >= 0 and ys <= bed_max_y+0.1: | |
43 | 575 | if self.settings.lc_melzi_hack: |
576 | fo.write("G1 X%0.4f Y%0.4f E%.4f F%.4f\n" % ( | |
577 | xs, ys, E * E_FACTOR, engrave_speed)) | |
578 | else: | |
579 | fo.write("G1 X%0.4f Y%0.4f F%.4f\n" % ( | |
580 | xs, ys, engrave_speed)) | |
32 | 581 | else: |
43 | 582 | if self.settings.lc_melzi_hack: |
583 | fo.write("G0 X%0.4f Y%0.4f F%.4f\n" % ( | |
584 | xs, ys, travel_speed)) | |
585 | else: | |
586 | fo.write("M5 ; Turn off laser\n") | |
587 | fo.write("G0 X%0.4f Y%0.4f F%.4f\n" % ( | |
588 | xs, ys, travel_speed)) | |
589 | # todo: laser power as parameter? | |
590 | fo.write("M3 S100 ; Turn on laser\n") | |
35 | 591 | errors += 1 |
592 | if errors < 10: | |
593 | self.log("Position outside print dimension: %d, %d" % (xs, ys)) | |
32 | 594 | |
595 | #print " Point: ", end_pt[0], end_pt[1], pen | |
33
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
32
diff
changeset
|
596 | #self.log("Laser OFF at: " + repr(sp[-1][-1])) |
27 | 597 | |
43 | 598 | #if shape_obj.xml_node.get('fill'): |
599 | if tag_suffix == "polygon": | |
21 | 600 | # Close the polygon |
43 | 601 | if self.settings.lc_melzi_hack: |
602 | e_distance = math.hypot(object_xs - xo, object_ys - yo) | |
603 | E = E + (e_distance) | |
604 | fo.write("G1 X%0.4f Y%0.4f E%.4f F%.4f ; Close the object polygon\n" % ( | |
605 | object_xs, object_ys, E * E_FACTOR, engrave_speed)) | |
606 | else: | |
607 | fo.write("G1 X%0.4f Y%0.4f F%.4f ; Close the object polygon\n" % ( | |
608 | object_xs, object_ys, engrave_speed)) | |
609 | print "connecting filled polygon path end to start" | |
16 | 610 | |
43 | 611 | if self.settings.lc_melzi_hack: |
612 | fo.write(GCODE_FOOT_MELZI) | |
613 | else: | |
614 | fo.write(GCODE_FOOT) | |
20 | 615 | fo.close() |
35 | 616 | |
617 | if errors > 0: | |
618 | self.log("%i errors while generating gcode" % errors) | |
16 | 619 | |
620 | if self.pronterwindow: | |
621 | self.log("") | |
622 | self.pronterwindow.load_gcode_async(filename + '.g') | |
623 |