|
@@ -0,0 +1,426 @@
|
|
1
|
+title: Laser Engraver
|
|
2
|
+description: Marlin based CNC laser engraver with 3D printed parts
|
|
3
|
+x-parent: projects
|
|
4
|
+git: https://git.xythobuz.de/thomas/marlin/src/branch/laser-engraver
|
|
5
|
+x-date: 2022-11-11
|
|
6
|
+comments: true
|
|
7
|
+---
|
|
8
|
+
|
|
9
|
+After dabbling in 3D printing for a couple of years now, I now had to level-up my Makers toolkit.
|
|
10
|
+The next logical step, in my opinion, is laser engraving / laser cutting.
|
|
11
|
+I have to admit, I was initially a bit scared, and still have some respect for the potential hazards posed by the strong and focussed laser beam.
|
|
12
|
+So I decided to go slow and start with a low powered diode laser.
|
|
13
|
+
|
|
14
|
+⚠️ Be sure to always use proper laser safety goggles when working with such machines! ⚠️
|
|
15
|
+
|
|
16
|
+## Hardware
|
|
17
|
+
|
|
18
|
+I know I say this in a lot of articles here, probably in an attempt to justify my hoarding of electronic parts. 😳
|
|
19
|
+But this was even better than most of my previous projects, in the sense that I didn't have to buy any parts at all, except for the 2500mW 450nm laser diode, which I got used from my colleague [Philipp](https://www.phschoen.de/) for just 13€.
|
|
20
|
+The rails I bought many years ago and never used.
|
|
21
|
+The steppers, mainboard, display, fans and cables came from my now disassembled [CTC i3](ctc-i3.html).
|
|
22
|
+Everything else came out of my parts bin.
|
|
23
|
+
|
|
24
|
+### Mechanics
|
|
25
|
+
|
|
26
|
+The mechanism is based on the ["Cantilever Laser Engraver" by Meatball](https://www.printables.com/model/213526-cantilever-laser-engraver).
|
|
27
|
+This in turn is based on the ["Cantilever Laser Engraver" by GeoDave](https://www.thingiverse.com/thing:4605853).
|
|
28
|
+It uses 2020 aluminium extrusions with V-slot wheels for the X and Y axis.
|
|
29
|
+
|
|
30
|
+I really like this simple design that does not require a lot of parts.
|
|
31
|
+It has some problems with twisting of the Y-rail due to the weight hanging from the cantilever arm, but this can easily be solved by supporting the free-hanging side of the X-rail.
|
|
32
|
+Otherwise the length of the axes can be customized freely.
|
|
33
|
+
|
|
34
|
+<!--%
|
|
35
|
+lightgallery([
|
|
36
|
+ [ "img/laser_frame.jpg", "Frame of laser engraver, made of 2020 rails" ],
|
|
37
|
+])
|
|
38
|
+%-->
|
|
39
|
+
|
|
40
|
+I had to do some modifications to the design files provided by Meatball.
|
|
41
|
+
|
|
42
|
+The "Y-Axis Gantry Upper" has enlargened the holes for two of the four wheels, to be able to fit eccentric nuts that provide the required tensioning on the wheels.
|
|
43
|
+The "Y-Axis Gantry Lower" however has normal sized holes.
|
|
44
|
+So when using these parts, the two wheels are not actually moved perpendicular towards the extrusion, but are instead angled sideways.
|
|
45
|
+To avoid this problem I made the two holes in the lower bigger as well and used four instead of two eccentric nuts.
|
|
46
|
+I also slightly increased the distance between the two sets of wheels on the Y-axis, because it was very hard to fit the rail, even with the eccentric nuts as loose as possible.
|
|
47
|
+
|
|
48
|
+I also designed custom endstop mounts using M2.5 heat-melt inserts, as well as a support wheel for the free-hanging side of the cantilever arm.
|
|
49
|
+
|
|
50
|
+The OpenSCAD and STL files for these parts [can be found on my Printables profile](https://www.printables.com/model/314945-cantilever-laser-engraver-fixes).
|
|
51
|
+
|
|
52
|
+### Electronics
|
|
53
|
+
|
|
54
|
+TODO intro
|
|
55
|
+
|
|
56
|
+<!--%
|
|
57
|
+lightgallery([
|
|
58
|
+ [ "img/laser_gt2560.jpg", "GT2560 mainboard of laser engraver" ],
|
|
59
|
+])
|
|
60
|
+%-->
|
|
61
|
+
|
|
62
|
+To connect the laser diode TTL input, a PWM capable pin from the microcontroller is required.
|
|
63
|
+To find one, we need to take a look at the schematics for the GT2560 mainboard, which can be found in the [Geeetech Forums](https://www.geeetech.com/forum/viewtopic.php?f=13&t=19092&start=10).
|
|
64
|
+
|
|
65
|
+<!--%
|
|
66
|
+lightgallery([
|
|
67
|
+ [ "img/GT2560_1.png", "First page of GT2560 schematics" ],
|
|
68
|
+ [ "img/GT2560_2.png", "Second page of GT2560 schematics" ],
|
|
69
|
+])
|
|
70
|
+%-->
|
|
71
|
+
|
|
72
|
+I decided to use `PL4`, the `DIR` pin of one of the now unused stepper drivers.
|
|
73
|
+To connect to it I prepared a little piece of perfboard, to be placed in the slots like a stepstick, with an additional pull-up resistor to the 5V logic voltage present on the same header.
|
|
74
|
+To find the pin number for the Marlin configuration, we can take a look at the [Arduino Mega pin mappings](https://docs.arduino.cc/hacking/hardware/PinMapping2560), which show `PL4` to be Arduino pin `45`.
|
|
75
|
+
|
|
76
|
+<!--%
|
|
77
|
+lightgallery([
|
|
78
|
+ [ "img/laser_ttl_pwm.jpg", "TTL PWM input of laser, with pull-up resistor" ],
|
|
79
|
+])
|
|
80
|
+%-->
|
|
81
|
+
|
|
82
|
+I'm not only connecting the laser TTL input to the mainboard, I'm also switching the 12V supply as well, by using the MOSFET originally intended for the hotend heater.
|
|
83
|
+This brought a little problem however.
|
|
84
|
+Both connectors of the laser PCB have a ground connection.
|
|
85
|
+And the ground connection is also the one that's switched, when connecting the laser to the power supply.
|
|
86
|
+So even when the MOSFET was turned off the laser still had a ground connection and was powered through the ground pin of the TTL connector, so it could never fully turn off.
|
|
87
|
+So if you do the same, make sure to only connect one of the two ground pins!
|
|
88
|
+
|
|
89
|
+TODO display
|
|
90
|
+
|
|
91
|
+## Software
|
|
92
|
+
|
|
93
|
+### MCU Firmware
|
|
94
|
+
|
|
95
|
+TODO marlin configuration
|
|
96
|
+
|
|
97
|
+Apparently I seem to be the first person that tries to run an Ultimaker Controller 2004 LCD without a Z-Axis.
|
|
98
|
+I had to add a couple of `#ifdef Z_AXIS` in `src/lcd/HD44780/marlinui_HD44780.cpp`.
|
|
99
|
+
|
|
100
|
+You can see all the modifications to the configuration I initially made to get the machine running [in this commit](https://git.xythobuz.de/thomas/marlin/commit/41cd87398d539f41c2ebe54b5f675c6c8b5ce04b).
|
|
101
|
+My current Marlin configuration for the laser engraver can be found [on my Gitea instance](https://git.xythobuz.de/thomas/marlin/src/branch/laser-engraver).
|
|
102
|
+
|
|
103
|
+### Host Software
|
|
104
|
+
|
|
105
|
+Besides the microcontroller firmware, we also need some host software to prepare the G-Code from whatever kind of input file we start out with.
|
|
106
|
+There are two basic approaches for generating paths for the laser engraver.
|
|
107
|
+One is "rasterization", where a bitmap image is turned into commands line-by-line, enabling and disabling the laser at different parts of each line.
|
|
108
|
+This will draw an image similar to how a CRT TV works, which is useful eg. for engraving logos.
|
|
109
|
+
|
|
110
|
+The other variant is "vectorization", where either a bitmap is converted to a vector path, or we start out with a vector file format, like svg.
|
|
111
|
+The laser can then follow the outline and optionally also fill between the outlines in some kind of pattern.
|
|
112
|
+This can then be used to cut shapes from objects.
|
|
113
|
+
|
|
114
|
+At this point I should also mention [Lightburn](https://lightburnsoftware.com/).
|
|
115
|
+This software is used both for the Laser cutters at [Toolbox Bodensee](https://toolbox-bodensee.de/), as well as by my neighbour, who uses it with an [Ortur Laser Master 2](https://ortur.net/products/laser-master-2-pro).
|
|
116
|
+So I had some contact with it, and I have to admit, it works well and has a lot of functionality.
|
|
117
|
+But it is commercial paid software, not under any free software license, so [it is not an option for me](https://www.gnu.org/proprietary/proprietary.html).
|
|
118
|
+
|
|
119
|
+Below I will try to document all different free software packages I tried for laser engraving.
|
|
120
|
+
|
|
121
|
+#### LaserGRBL
|
|
122
|
+
|
|
123
|
+The first solution I found out about is [LaserGRBL](https://lasergrbl.com/).
|
|
124
|
+It is a Windows-only program, but at least it is open-source / free software.
|
|
125
|
+As the name implies however, it is intended for use with the [GRBL firmware](https://github.com/gnea/grbl).
|
|
126
|
+This firmware only runs on Atmega328p MCUs, so I can not use it.
|
|
127
|
+
|
|
128
|
+The G-Code flavor of GRBL is considerably different compared to Marlin, but I was able to get it to work mostly.
|
|
129
|
+
|
|
130
|
+For rasterization of bitmap images, GRBL produces G-Code with the laser power levels put inline with the movement commands.
|
|
131
|
+
|
|
132
|
+<pre class="sh_gcode">
|
|
133
|
+G0 X25 Y10 S0
|
|
134
|
+G1 X26.5 S150
|
|
135
|
+G0 X28.25 Y10.25 S0
|
|
136
|
+G1 X23 S150
|
|
137
|
+</pre>
|
|
138
|
+
|
|
139
|
+Without further modifications, this only moves the toolhead, while keeping the laser off at all times, because Marlin by default does not process the power-level parts of the G-Code lines.
|
|
140
|
+To get this to work, the "continuous inline power mode" has to be enabled by putting `M3 I` in the G-Code header setting of LaserGRBL.
|
|
141
|
+
|
|
142
|
+<!--%
|
|
143
|
+lightgallery([
|
|
144
|
+ [ "img/lasergrbl_settings.png", "LaserGRBL G-Code settings" ],
|
|
145
|
+])
|
|
146
|
+%-->
|
|
147
|
+
|
|
148
|
+Here are the results of my first attempts of engraving a rasterized image into a piece of scrap wood.
|
|
149
|
+The rocket used 6 lines/mm, with default speed and a PWM range of 0-255.
|
|
150
|
+The smirkey used 4 lines/mm, with default speed and a PWM range of 0-150.
|
|
151
|
+
|
|
152
|
+<!--%
|
|
153
|
+lightgallery([
|
|
154
|
+ [ "img/laser_raster_result.jpg", "My first cuts of some rasterized images" ],
|
|
155
|
+])
|
|
156
|
+%-->
|
|
157
|
+
|
|
158
|
+When trying to engrave vectorized paths, like the outline of an image, LaserGRBL produces different output, however.
|
|
159
|
+The power levels are now put on their own lines, but without any G-Code command, as usual shorthand used by GRBL.
|
|
160
|
+
|
|
161
|
+<pre class="sh_gcode">
|
|
162
|
+S0
|
|
163
|
+G0 X14.382 Y14.618
|
|
164
|
+S150
|
|
165
|
+G1 X14.618 Y14.382
|
|
166
|
+S0
|
|
167
|
+G0 X14.618 Y14.618
|
|
168
|
+S150
|
|
169
|
+G1 X14.457 Y14.457
|
|
170
|
+</pre>
|
|
171
|
+
|
|
172
|
+Using a simple Python script, we can convert to the same format as used for rasterization.
|
|
173
|
+It simply reads the input file into the output file, skipping every line starting with an uppercase letter 'S', instead appending that to the next line.
|
|
174
|
+
|
|
175
|
+I also noticed the speed set in LaserGRBL did not properly apply in Marlin.
|
|
176
|
+This is because, in vector mode, it puts a single "Fxxx" line at the beginning.
|
|
177
|
+Even though Marlin can interpret this in our configuration, there is no G1 mode set before, so Marlin does not know where to apply the speed.
|
|
178
|
+So I am also handling this case in the script as well.
|
|
179
|
+For raster input, LaserGRBL only puts the speed in a single G0 move at the beginning.
|
|
180
|
+So the speed does not apply to the G1 moves afterwards.
|
|
181
|
+This case is not handled yet.
|
|
182
|
+
|
|
183
|
+<pre class="sh_python">
|
|
184
|
+#!/usr/bin/env python
|
|
185
|
+
|
|
186
|
+import sys
|
|
187
|
+
|
|
188
|
+if len(sys.argv) < 3:
|
|
189
|
+ print("Usage:")
|
|
190
|
+ print(" " + sys.argv[0] + " input.nc output.gcode")
|
|
191
|
+ sys.exit(1)
|
|
192
|
+
|
|
193
|
+in_file = sys.argv[1]
|
|
194
|
+out_file = sys.argv[2]
|
|
195
|
+
|
|
196
|
+power = ""
|
|
197
|
+speed = ""
|
|
198
|
+
|
|
199
|
+with open(in_file, 'r') as fi, open(out_file, 'w') as fo:
|
|
200
|
+ for line in fi:
|
|
201
|
+ if line.startswith("S"):
|
|
202
|
+ power = " " + line.rstrip()
|
|
203
|
+ elif line.startswith("F"):
|
|
204
|
+ speed = " " + line.rstrip()
|
|
205
|
+ else:
|
|
206
|
+ s = line.rstrip()
|
|
207
|
+ if line.startswith("G1"):
|
|
208
|
+ s += power
|
|
209
|
+ s += speed
|
|
210
|
+ elif line.startswith("G0"):
|
|
211
|
+ s += power
|
|
212
|
+ if (power != " S0") and (power != ""):
|
|
213
|
+ print("Warning: G0 move with power not zero!" + power)
|
|
214
|
+ s += "\n"
|
|
215
|
+ fo.write(s)
|
|
216
|
+</pre>
|
|
217
|
+
|
|
218
|
+Here is my first attempt of cutting a vector outline of an image out of a piece of paper.
|
|
219
|
+I used Marlins default speed and a PWM setting of 150 out of 255.
|
|
220
|
+
|
|
221
|
+<!--%
|
|
222
|
+lightgallery([
|
|
223
|
+ [ "img/laser_vector_result.jpg", "My first cut of a vector outline" ],
|
|
224
|
+])
|
|
225
|
+%-->
|
|
226
|
+
|
|
227
|
+For these tests I have simply used LaserGRBL to produce a G-Code file, which I have then transferred to an SD card for direct use in the engraver.
|
|
228
|
+LaserGRBL also can directly connect to the machine via the serial port.
|
|
229
|
+This has worked for me for a bit, but even though you can set the G-Code flavor to Marlin in the LaserGRBL settings, it does not really work properly.
|
|
230
|
+You can jog the toolhead but homing is not possible.
|
|
231
|
+I also had to reduce the polling frequency in the LaserGRBL settings to 'Quiet' and disable Soft-Reset to get it to work inside the VirtualBox VM I use to run the program.
|
|
232
|
+
|
|
233
|
+Apparently it is also possible to [run LaserGRBL in Wine](https://github.com/arkypita/LaserGRBL/issues/5#issuecomment-873564055) / [with PlayOnLinux](https://github.com/arkypita/LaserGRBL/raw/master/POL_LaserGRBL_setup.sh), but on my Arch system I was not able to get either of these running, due to problems with the required Winetricks.
|
|
234
|
+
|
|
235
|
+Also, for all the above tests with LaserGRBL I have imported raster bitmap images and either rastered them, or vectorized them within LaserGRBLs UI.
|
|
236
|
+With bitmaps it is possible to position the resulting G-Code paths with an offset from the zero position of the machine.
|
|
237
|
+This is not possible when using LaserGRBL to import SVG vector paths.
|
|
238
|
+With them, the output will always start at coordinates `(0, 0)`.
|
|
239
|
+You will then have to set the proper offset on the machine itself.
|
|
240
|
+
|
|
241
|
+#### Inkscape
|
|
242
|
+
|
|
243
|
+Inkscape includes the [G-Code Tools Plugin from the russian-language CNC-Club forums](https://www.cnc-club.ru/forum/viewtopic.php?t=35).
|
|
244
|
+Unfortunately I was not able to find much up-to-date english-language documentation for this.
|
|
245
|
+It is also relatively complicated and unintuitive.
|
|
246
|
+
|
|
247
|
+TODO
|
|
248
|
+
|
|
249
|
+I was able to get it to generate G-Code from a path, but not with any laser power instructions yet.
|
|
250
|
+
|
|
251
|
+#### FreeCAD
|
|
252
|
+
|
|
253
|
+FreeCAD has the [Path Workbench](https://wiki.freecadweb.org/Path_Workbench), which can be used to create G-Code instructions for all kinds of CNC machines.
|
|
254
|
+It is not really complete and fool-proof yet, unfortunately.
|
|
255
|
+And it is also not designed for pure 2D machines, like laser engravers, by default.
|
|
256
|
+
|
|
257
|
+TODO
|
|
258
|
+
|
|
259
|
+I have not yet tested that.
|
|
260
|
+
|
|
261
|
+#### Generating G-Code
|
|
262
|
+
|
|
263
|
+I also did some experimentation with programatically generating G-Code myself, using a simple Python script.
|
|
264
|
+It's drawing a grid for reference on the base plate of the machine, including numerical position indicators.
|
|
265
|
+
|
|
266
|
+<pre class="sh_python">
|
|
267
|
+#!/usr/bin/env python
|
|
268
|
+
|
|
269
|
+filename = "grid.gcode"
|
|
270
|
+w = 200
|
|
271
|
+h = 280
|
|
272
|
+d = 10
|
|
273
|
+pwr = 100
|
|
274
|
+speed_g0 = 3000
|
|
275
|
+speed_g1 = 1000
|
|
276
|
+
|
|
277
|
+digits = [
|
|
278
|
+ [
|
|
279
|
+ # 0
|
|
280
|
+ (0.0, 0.0),
|
|
281
|
+ (0.0, 1.0),
|
|
282
|
+ (1.0, 1.0),
|
|
283
|
+ (1.0, 0.0),
|
|
284
|
+ (0.0, 0.0)
|
|
285
|
+ ], [
|
|
286
|
+ # 1
|
|
287
|
+ (0.5, 0.0),
|
|
288
|
+ (0.5, 1.0)
|
|
289
|
+ ], [
|
|
290
|
+ # 2
|
|
291
|
+ (0.0, 1.0),
|
|
292
|
+ (1.0, 1.0),
|
|
293
|
+ (1.0, 0.5),
|
|
294
|
+ (0.0, 0.5),
|
|
295
|
+ (0.0, 0.0),
|
|
296
|
+ (1.0, 0.0),
|
|
297
|
+ ], [
|
|
298
|
+ # 3
|
|
299
|
+ (0.0, 1.0),
|
|
300
|
+ (1.0, 1.0),
|
|
301
|
+ (1.0, 0.5),
|
|
302
|
+ (0.0, 0.5),
|
|
303
|
+ (1.0, 0.5),
|
|
304
|
+ (1.0, 0.0),
|
|
305
|
+ (0.0, 0.0),
|
|
306
|
+ ], [
|
|
307
|
+ # 4
|
|
308
|
+ (0.0, 1.0),
|
|
309
|
+ (0.0, 0.5),
|
|
310
|
+ (1.0, 0.5),
|
|
311
|
+ (1.0, 1.0),
|
|
312
|
+ (1.0, 0.0),
|
|
313
|
+ ], [
|
|
314
|
+ # 5
|
|
315
|
+ (1.0, 1.0),
|
|
316
|
+ (0.0, 1.0),
|
|
317
|
+ (0.0, 0.5),
|
|
318
|
+ (1.0, 0.5),
|
|
319
|
+ (1.0, 0.0),
|
|
320
|
+ (0.0, 0.0),
|
|
321
|
+ ], [
|
|
322
|
+ # 6
|
|
323
|
+ (1.0, 1.0),
|
|
324
|
+ (0.0, 1.0),
|
|
325
|
+ (0.0, 0.0),
|
|
326
|
+ (1.0, 0.0),
|
|
327
|
+ (1.0, 0.5),
|
|
328
|
+ (0.0, 0.5),
|
|
329
|
+ ], [
|
|
330
|
+ # 7
|
|
331
|
+ (0.0, 1.0),
|
|
332
|
+ (1.0, 1.0),
|
|
333
|
+ (1.0, 0.0),
|
|
334
|
+ ], [
|
|
335
|
+ # 8
|
|
336
|
+ (1.0, 0.5),
|
|
337
|
+ (1.0, 1.0),
|
|
338
|
+ (0.0, 1.0),
|
|
339
|
+ (0.0, 0.5),
|
|
340
|
+ (1.0, 0.5),
|
|
341
|
+ (1.0, 0.0),
|
|
342
|
+ (0.0, 0.0),
|
|
343
|
+ (0.0, 0.5),
|
|
344
|
+ ], [
|
|
345
|
+ # 9
|
|
346
|
+ (1.0, 0.5),
|
|
347
|
+ (1.0, 1.0),
|
|
348
|
+ (0.0, 1.0),
|
|
349
|
+ (0.0, 0.5),
|
|
350
|
+ (1.0, 0.5),
|
|
351
|
+ (1.0, 0.0),
|
|
352
|
+ (0.0, 0.0),
|
|
353
|
+ ]
|
|
354
|
+]
|
|
355
|
+
|
|
356
|
+font_w = 1.5
|
|
357
|
+font_h = 3.0
|
|
358
|
+font_d = 0.5
|
|
359
|
+
|
|
360
|
+def draw_digit(f, i, sx, sy, ox, oy):
|
|
361
|
+ dig = digits[i]
|
|
362
|
+ n = 0
|
|
363
|
+ for p in dig:
|
|
364
|
+ x, y = p
|
|
365
|
+ s = ""
|
|
366
|
+
|
|
367
|
+ if n == 0:
|
|
368
|
+ s += "G0 S0 F" + str(speed_g0)
|
|
369
|
+ else:
|
|
370
|
+ s += "G1 S" + str(pwr) + " F" + str(speed_g1)
|
|
371
|
+
|
|
372
|
+ s += " X" + str(ox + sx * x)
|
|
373
|
+ s += " Y" + str(oy + sy * y)
|
|
374
|
+
|
|
375
|
+ print(s)
|
|
376
|
+ f.write(s + "\n")
|
|
377
|
+ n += 1
|
|
378
|
+
|
|
379
|
+ return s
|
|
380
|
+
|
|
381
|
+with open(filename, 'w') as f:
|
|
382
|
+ def write(s):
|
|
383
|
+ print(s)
|
|
384
|
+ f.write(s + "\n")
|
|
385
|
+
|
|
386
|
+ # header
|
|
387
|
+ write("G90")
|
|
388
|
+ write("M3 I")
|
|
389
|
+ write("M3 S0")
|
|
390
|
+
|
|
391
|
+ # first line is not having the power applied
|
|
392
|
+ # so just draw it twice
|
|
393
|
+ # TODO why?
|
|
394
|
+ write("G0 X0 Y0 S0 F" + str(speed_g0))
|
|
395
|
+ write("G1 X0 Y" + str(h) + " S" + str(pwr) + " F" + str(speed_g1))
|
|
396
|
+
|
|
397
|
+ # vertical lines
|
|
398
|
+ for x in range(0, w + d, d):
|
|
399
|
+ write("G0 X" + str(x) + " Y0 S0 F" + str(speed_g0))
|
|
400
|
+ write("G1 X" + str(x) + " Y" + str(h) + " S" + str(pwr) + " F" + str(speed_g1))
|
|
401
|
+
|
|
402
|
+ # horizontal lines
|
|
403
|
+ for y in range(0, h + d, d):
|
|
404
|
+ write("G0 X0 Y" + str(y) + " S0 F" + str(speed_g0))
|
|
405
|
+ write("G1 X" + str(w) + " Y" + str(y) + " S" + str(pwr) + " F" + str(speed_g1))
|
|
406
|
+
|
|
407
|
+ for x in range(0, w, d):
|
|
408
|
+ n = int(x / 1)
|
|
409
|
+ if n >= 10:
|
|
410
|
+ draw_digit(f, int(n / 10), font_w, font_h, font_d + x, font_d)
|
|
411
|
+ draw_digit(f, int(n % 10), font_w, font_h, 2 * font_d + font_w + x, font_d)
|
|
412
|
+ else:
|
|
413
|
+ draw_digit(f, n, font_w, font_h, font_d + x, font_d)
|
|
414
|
+
|
|
415
|
+ for y in range(d, h, d):
|
|
416
|
+ n = int(y / 1)
|
|
417
|
+ if n >= 10:
|
|
418
|
+ draw_digit(f, int(n / 10), font_w, font_h, font_d, font_d + y)
|
|
419
|
+ draw_digit(f, int(n % 10), font_w, font_h, 2 * font_d + font_w, font_d + y)
|
|
420
|
+ else:
|
|
421
|
+ draw_digit(f, n, font_w, font_h, font_d, font_d + y)
|
|
422
|
+
|
|
423
|
+ # footer
|
|
424
|
+ write("M5")
|
|
425
|
+ write("G0 X0 Y0 F" + str(speed_g0))
|
|
426
|
+</pre>
|