Przeglądaj źródła

publish laser engraver page. add video-thumb script.

Thomas Buck 1 rok temu
rodzic
commit
ed909ff926
44 zmienionych plików z 680 dodań i 16 usunięć
  1. 595
    15
      input/projects/laser-engraver.md
  2. 37
    1
      macros.py
  3. 12
    0
      static/css/style.css
  4. BIN
      static/img/laser_cut_test_1.jpg
  5. BIN
      static/img/laser_cut_test_1_small.jpg
  6. BIN
      static/img/laser_cut_test_2.jpg
  7. BIN
      static/img/laser_cut_test_2_small.jpg
  8. BIN
      static/img/laser_cut_test_3.jpg
  9. BIN
      static/img/laser_cut_test_3_small.jpg
  10. BIN
      static/img/laser_grid.jpg
  11. BIN
      static/img/laser_grid_small.jpg
  12. BIN
      static/img/laser_pagoda_1.jpg
  13. BIN
      static/img/laser_pagoda_1_small.jpg
  14. BIN
      static/img/laser_pagoda_2.jpg
  15. BIN
      static/img/laser_pagoda_2_small.jpg
  16. BIN
      static/img/laser_pagoda_3.jpg
  17. BIN
      static/img/laser_pagoda_3_small.jpg
  18. BIN
      static/img/laser_power_switch.jpg
  19. BIN
      static/img/laser_power_switch_small.jpg
  20. BIN
      static/img/laser_psu.jpg
  21. BIN
      static/img/laser_psu_small.jpg
  22. BIN
      static/img/laser_side_1.jpg
  23. BIN
      static/img/laser_side_1_small.jpg
  24. BIN
      static/img/laser_side_2.jpg
  25. BIN
      static/img/laser_side_2_small.jpg
  26. BIN
      static/img/laser_tree_1.jpg
  27. BIN
      static/img/laser_tree_1_small.jpg
  28. BIN
      static/img/laser_tree_2.jpg
  29. BIN
      static/img/laser_tree_2_small.jpg
  30. BIN
      static/img/laser_tree_3.jpg
  31. BIN
      static/img/laser_tree_3_small.jpg
  32. BIN
      static/img/laser_tree_segments.mp4
  33. BIN
      static/img/laser_tree_segments_poster.png
  34. BIN
      static/img/laser_tree_segments_thumb.png
  35. BIN
      static/img/laser_tree_test.mp4
  36. BIN
      static/img/laser_tree_test_poster.png
  37. BIN
      static/img/laser_tree_test_thumb.png
  38. BIN
      static/img/laser_x_support.jpg
  39. BIN
      static/img/laser_x_support_small.jpg
  40. BIN
      static/img/laser_y_gantry.jpg
  41. BIN
      static/img/laser_y_gantry_small.jpg
  42. BIN
      static/img/lasergrbl_settings.png
  43. BIN
      static/img/lasergrbl_settings_small.png
  44. 36
    0
      video-thumb

+ 595
- 15
input/projects/laser-engraver.md Wyświetl plik

1
 title: Laser Engraver
1
 title: Laser Engraver
2
-description: Marlin based CNC laser engraver with 3D printed parts
3
-x-parent: projects
2
+description: Marlin based CNC laser engraver / cutter with 3D printed parts
3
+parent: projects
4
 git: https://git.xythobuz.de/thomas/marlin/src/branch/laser-engraver
4
 git: https://git.xythobuz.de/thomas/marlin/src/branch/laser-engraver
5
-x-date: 2022-11-11
5
+date: 2022-11-25
6
 comments: true
6
 comments: true
7
 ---
7
 ---
8
 
8
 
9
 After dabbling in 3D printing for a couple of years now, I had to level-up my Makers toolkit.
9
 After dabbling in 3D printing for a couple of years now, I had to level-up my Makers toolkit.
10
 The next logical step, in my opinion, is laser engraving / laser cutting.
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.
11
+I have to admit I was initially a bit scared and still have some respect for the potential hazards posed by the strong, focussed laser beam.
12
+So I decided to go slow and start with a low powered 2.5W diode laser.
13
 
13
 
14
 ⚠️ Be sure to always use proper laser safety goggles when working with such machines! ⚠️
14
 ⚠️ Be sure to always use proper laser safety goggles when working with such machines! ⚠️
15
 
15
 
16
 <!--%
16
 <!--%
17
 lightgallery([
17
 lightgallery([
18
     [ "img/laser_tower_1.jpg", "Frontal view in Ikea Lack tower" ],
18
     [ "img/laser_tower_1.jpg", "Frontal view in Ikea Lack tower" ],
19
+    [ "img/laser_tree_test.mp4", "video/mp4", "", "", "Cutting a christmas tree decoration" ],
20
+    [ "img/laser_side_1.jpg", "View from left side" ],
21
+    [ "img/laser_side_2.jpg", "View from right side" ],
19
 ])
22
 ])
20
 %-->
23
 %-->
21
 
24
 
25
+### Table Of Contents
26
+
27
+* [Hardware](laser-engraver.html#hardware)
28
+    * [Mechanics](laser-engraver.html#mechanics)
29
+    * [Electronics](laser-engraver.html#electronics)
30
+* [Software](laser-engraver.html#software)
31
+    * [MCU Firmware](laser-engraver.html#mcu_firmware)
32
+    * [Host Software](laser-engraver.html#host_software)
33
+        * [LaserGRBL](laser-engraver.html#lasergrbl)
34
+        * [Inkscape](laser-engraver.html#inkscape)
35
+        * [FreeCAD](laser-engraver.html#freecad)
36
+        * [Working with G-Code](laser-engraver.html#g_code)
37
+* [Cutting Tests](laser-engraver.html#cutting_tests)
38
+* [Future Improvements](laser-engraver.html#future_improvements)
39
+* [Cutting Parameters](laser-engraver.html#cutting_parameters)
40
+* [More Pictures](laser-engraver.html#more_pictures)
41
+
22
 ## Hardware
42
 ## Hardware
43
+<a class="anchor" name="hardware"></a>
23
 
44
 
24
 I know I say this in a lot of articles here, probably in an attempt to justify my hoarding of electronic parts. 😳
45
 I know I say this in a lot of articles here, probably in an attempt to justify my hoarding of electronic parts. 😳
25
 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€.
46
 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€.
28
 Everything else came out of my parts bin.
49
 Everything else came out of my parts bin.
29
 
50
 
30
 ### Mechanics
51
 ### Mechanics
52
+<a class="anchor" name="mechanics"></a>
31
 
53
 
32
 The mechanism is based on the ["Cantilever Laser Engraver" by Meatball](https://www.printables.com/model/213526-cantilever-laser-engraver).
54
 The mechanism is based on the ["Cantilever Laser Engraver" by Meatball](https://www.printables.com/model/213526-cantilever-laser-engraver).
33
 This in turn is based on the ["Cantilever Laser Engraver" by GeoDave](https://www.thingiverse.com/thing:4605853).
55
 This in turn is based on the ["Cantilever Laser Engraver" by GeoDave](https://www.thingiverse.com/thing:4605853).
40
 <!--%
62
 <!--%
41
 lightgallery([
63
 lightgallery([
42
     [ "img/laser_frame.jpg", "Frame of laser engraver, made of 2020 rails" ],
64
     [ "img/laser_frame.jpg", "Frame of laser engraver, made of 2020 rails" ],
65
+    [ "img/laser_y_gantry.jpg", "Close view of Y gantry mechanism" ],
66
+    [ "img/laser_x_support.jpg", "Close view of X axis support wheel" ],
43
 ])
67
 ])
44
 %-->
68
 %-->
45
 
69
 
56
 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).
80
 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).
57
 
81
 
58
 To fit the whole machine comfortably inside my [Ikea Lack tower](ikea-lack.html) I mounted it on a 18mm mutliplex base plate with 540x440mm.
82
 To fit the whole machine comfortably inside my [Ikea Lack tower](ikea-lack.html) I mounted it on a 18mm mutliplex base plate with 540x440mm.
83
+The 3D printed feet of the Y-axis already have holes for countersunk screws, so I simply used wood screws there.
84
+To mount the mainboard PCB and laser heatsink I made 3.2mm holes for M3 screws and M3 stand-offs, drilling with a 6mm drill from the bottom to approximately half the depth of the base plate, so even cylindrical screw heads don't stand out from below the plate.
85
+I also added a power switch with a simple 3D printed mount.
86
+
87
+<!--%
88
+lightgallery([
89
+    [ "img/laser_power_switch.jpg", "Power switch with mount" ],
90
+])
91
+%-->
59
 
92
 
60
 ### Electronics
93
 ### Electronics
94
+<a class="anchor" name="electronics"></a>
61
 
95
 
62
 As mentioned above I used the electronics, namely mainboard, LCD and fans, from my old 3D printer.
96
 As mentioned above I used the electronics, namely mainboard, LCD and fans, from my old 3D printer.
63
 
97
 
95
 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.
129
 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.
96
 So if you do the same, make sure to only connect one of the two ground pins!
130
 So if you do the same, make sure to only connect one of the two ground pins!
97
 
131
 
132
+The power supply is simply reused from my [Fabrikator Mini](fabrikator-mini.html), it already was placed in the Ikea Lack tower.
133
+To save some space I now mounted it to the top of the next Lack table above the laser, with some strips of perforated metal sheet.
134
+Of course this is kind of problematic, as the rest of the machine is kind of designed to be easily transportable.
135
+I will have to find some kind of small alternative power supply.
136
+
137
+<!--%
138
+lightgallery([
139
+    [ "img/laser_psu.jpg", "12V power supply on underside of Ikea Lack" ],
140
+])
141
+%-->
142
+
98
 ## Software
143
 ## Software
144
+<a class="anchor" name="software"></a>
145
+
146
+There's a surprisingly large amount of software involved in this project.
147
+All just to move a lamp around a bit 💡🤔
99
 
148
 
100
 ### MCU Firmware
149
 ### MCU Firmware
150
+<a class="anchor" name="mcu_firmware"></a>
101
 
151
 
102
 Most DIY laser engravers or CNC machines seem to use the [GRBL firmware](https://github.com/gnea/grbl).
152
 Most DIY laser engravers or CNC machines seem to use the [GRBL firmware](https://github.com/gnea/grbl).
103
 Unfortunately this firmware only runs on Atmega328p MCUs, so I can not use it with the Atmega2560 on the GT2560 mainboard.
153
 Unfortunately this firmware only runs on Atmega328p MCUs, so I can not use it with the Atmega2560 on the GT2560 mainboard.
116
 These changes can be seen [in this commit](https://git.xythobuz.de/thomas/marlin/commit/58dbdff1d5b6e365bfd5ae4eeb7b42967688c51e).
166
 These changes can be seen [in this commit](https://git.xythobuz.de/thomas/marlin/commit/58dbdff1d5b6e365bfd5ae4eeb7b42967688c51e).
117
 I also opened a [pull request](https://github.com/MarlinFirmware/Marlin/pull/25003) to hopefully upstream them.
167
 I also opened a [pull request](https://github.com/MarlinFirmware/Marlin/pull/25003) to hopefully upstream them.
118
 
168
 
169
+Later I learned that Marlin does not allow setting home offsets further than 20mm away from endstops.
170
+So I also [added a custom menu command](https://git.xythobuz.de/thomas/marlin/commit/74cafcd77625003623d3a8a6378a4ff0ac6eda72) that simply executes [`G92`](https://marlinfw.org/docs/gcode/G092.html) `X0 Y0` to set the current position to zero.
171
+Using this I can generate G-Code where the object to cut starts at coordinates `(0, 0)` while being able to position it arbitrarily on the machine itself.
172
+
119
 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).
173
 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).
120
 
174
 
121
 ### Host Software
175
 ### Host Software
176
+<a class="anchor" name="host_software"></a>
122
 
177
 
123
 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.
178
 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.
124
 There are two basic approaches for generating paths for the laser engraver.
179
 There are two basic approaches for generating paths for the laser engraver.
137
 Below I will try to document all different free software packages I tried for laser engraving.
192
 Below I will try to document all different free software packages I tried for laser engraving.
138
 
193
 
139
 #### LaserGRBL
194
 #### LaserGRBL
195
+<a class="anchor" name="lasergrbl"></a>
140
 
196
 
141
 The first solution I found out about is [LaserGRBL](https://lasergrbl.com/).
197
 The first solution I found out about is [LaserGRBL](https://lasergrbl.com/).
142
 It is a Windows-only program, but at least it is open-source / free software.
198
 It is a Windows-only program, but at least it is open-source / free software.
154
 </pre>
210
 </pre>
155
 
211
 
156
 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.
212
 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.
157
-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.
213
+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, as well as at the end of the "Multiple Passes" G-Code section.
158
 
214
 
159
 <!--%
215
 <!--%
160
 lightgallery([
216
 lightgallery([
197
 So the speed does not apply to the G1 moves afterwards.
253
 So the speed does not apply to the G1 moves afterwards.
198
 This case is not handled yet.
254
 This case is not handled yet.
199
 
255
 
256
+In another vector job, I saw LaserGRBL generate output like the following.
257
+
258
+<pre class="sh_gcode">
259
+G0X3Y2
260
+S255
261
+G1Y5F200
262
+X0.7
263
+Y8.347
264
+X8.25
265
+X8.75Y8.847
266
+Y12.347
267
+</pre>
268
+
269
+This is the very compact GRBL G-Code dialect, which leaves out spaces and also the modes, as long as they don't change.
270
+So to handle that, I remember the last G0/G1 mode, prepending it as needed to lines, while also adding the missing spaces for better human readable output.
271
+
272
+As you can tell there are many different output formats from LaserGRBL and I'm not able to tell when it will generate what.
273
+But my script below should work for all variants I have seen up to now.
274
+
200
 <pre class="sh_python">
275
 <pre class="sh_python">
201
 #!/usr/bin/env python
276
 #!/usr/bin/env python
202
 
277
 
212
 
287
 
213
 power = ""
288
 power = ""
214
 speed = ""
289
 speed = ""
290
+mode_g0 = True
291
+
292
+def add_spaces(s):
293
+    prev = ' '
294
+    r = ""
295
+    for c in s:
296
+        if str(prev).isdigit() and str(c).isalpha():
297
+            r = r + " "
298
+        r = r + c
299
+        prev = c
300
+    return r
215
 
301
 
216
 with open(in_file, 'r') as fi, open(out_file, 'w') as fo:
302
 with open(in_file, 'r') as fi, open(out_file, 'w') as fo:
217
     for line in fi:
303
     for line in fi:
220
         elif line.startswith("F"):
306
         elif line.startswith("F"):
221
             speed = " " + line.rstrip()
307
             speed = " " + line.rstrip()
222
         else:
308
         else:
223
-            s = line.rstrip()
309
+            s = add_spaces(line.rstrip())
224
             if line.startswith("G1"):
310
             if line.startswith("G1"):
311
+                mode_g0 = False
225
                 s += power
312
                 s += power
226
                 s += speed
313
                 s += speed
227
             elif line.startswith("G0"):
314
             elif line.startswith("G0"):
315
+                mode_g0 = True
228
                 s += power
316
                 s += power
229
                 if (power != " S0") and (power != ""):
317
                 if (power != " S0") and (power != ""):
230
                     print("Warning: G0 move with power not zero!" + power)
318
                     print("Warning: G0 move with power not zero!" + power)
319
+            elif line.startswith("X") or line.startswith("Y"):
320
+                if mode_g0:
321
+                    s = "G0 " + s
322
+                    s += power
323
+                    if (power != " S0") and (power != ""):
324
+                        print("Warning: G0 move with power not zero!" + power)
325
+                else:
326
+                    s = "G1 " + s
327
+                    s += power
328
+                    s += speed
231
             s += "\n"
329
             s += "\n"
232
             fo.write(s)
330
             fo.write(s)
233
 </pre>
331
 </pre>
245
 LaserGRBL also can directly connect to the machine via the serial port.
343
 LaserGRBL also can directly connect to the machine via the serial port.
246
 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.
344
 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.
247
 You can jog the toolhead but homing is not possible.
345
 You can jog the toolhead but homing is not possible.
248
-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.
346
+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 used at first to run the program.
249
 
347
 
250
-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.
348
+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).
349
+At first I was not able to run this PlayOnLinux script due to Winetricks downloads failing with invalid checksums.
350
+But after trying a couple of days later, with the exact same script, the installation worked fine.
351
+I had to make the same changes to the settings as described for VirtualBox above.
251
 
352
 
252
-Also, for all the above tests with LaserGRBL I have imported raster bitmap images and either rastered them, or vectorized them within LaserGRBLs UI.
353
+Also for all the above tests with LaserGRBL I have imported raster bitmap images and either rastered them, or vectorized them within LaserGRBLs UI.
253
 With bitmaps it is possible to position the resulting G-Code paths with an offset from the zero position of the machine.
354
 With bitmaps it is possible to position the resulting G-Code paths with an offset from the zero position of the machine.
254
 This is not possible when using LaserGRBL to import SVG vector paths.
355
 This is not possible when using LaserGRBL to import SVG vector paths.
255
 With them, the output will always start at coordinates `(0, 0)`.
356
 With them, the output will always start at coordinates `(0, 0)`.
256
-You will then have to set the proper offset on the machine itself.
357
+You will then have to set the proper offset on the machine itself, as described in the Marlin section above.
257
 
358
 
258
 #### Inkscape
359
 #### Inkscape
360
+<a class="anchor" name="inkscape"></a>
259
 
361
 
260
 Inkscape includes the [G-Code Tools Plugin from the russian-language CNC-Club forums](https://www.cnc-club.ru/forum/viewtopic.php?t=35).
362
 Inkscape includes the [G-Code Tools Plugin from the russian-language CNC-Club forums](https://www.cnc-club.ru/forum/viewtopic.php?t=35).
261
 Unfortunately I was not able to find much up-to-date english-language documentation for this.
363
 Unfortunately I was not able to find much up-to-date english-language documentation for this.
262
 It is also relatively complicated and unintuitive.
364
 It is also relatively complicated and unintuitive.
263
 
365
 
264
-TODO
265
-
266
 I was able to get it to generate G-Code from a path, but not with any laser power instructions yet.
366
 I was able to get it to generate G-Code from a path, but not with any laser power instructions yet.
367
+This section will be updated if I have more success in the future.
368
+Until then I'm just using Inkscape to export svg files for LaserGRBL.
267
 
369
 
268
 #### FreeCAD
370
 #### FreeCAD
371
+<a class="anchor" name="freecad"></a>
269
 
372
 
270
 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.
373
 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.
271
 It is not really complete and fool-proof yet, unfortunately.
374
 It is not really complete and fool-proof yet, unfortunately.
272
 And it is also not designed for pure 2D machines, like laser engravers, by default.
375
 And it is also not designed for pure 2D machines, like laser engravers, by default.
273
 
376
 
274
-TODO
275
-
276
 I have not yet tested that.
377
 I have not yet tested that.
277
 
378
 
278
 #### Working with G-Code
379
 #### Working with G-Code
380
+<a class="anchor" name="g_code"></a>
279
 
381
 
280
 One nice feature I saw in LaserGRBL, but was not able to use with Marlin, is the ability to draw the outline of the object to be cut, for positioning of the stock material.
382
 One nice feature I saw in LaserGRBL, but was not able to use with Marlin, is the ability to draw the outline of the object to be cut, for positioning of the stock material.
281
 So I quickly made up a small Python script that analyzes a G-Code file, taking the coordinates from all G1 cutting moves and generating their bounding box, which is then drawn on the lowest possible power setting multiple times.
383
 So I quickly made up a small Python script that analyzes a G-Code file, taking the coordinates from all G1 cutting moves and generating their bounding box, which is then drawn on the lowest possible power setting multiple times.
528
     write("G0 X0 Y0 F" + str(speed_g0))
630
     write("G0 X0 Y0 F" + str(speed_g0))
529
 </pre>
631
 </pre>
530
 
632
 
633
+I like the idea but unfortunately it has some problems in practice.
634
+When changing the focus level of my laser diode, it's very easy to also change the offset in the X and Y axes.
635
+So the grid is not properly aligned for long.
636
+But it still looks nice.
637
+
638
+<!--%
639
+lightgallery([
640
+    [ "img/laser_grid.jpg", "Closer view of grid on base plate" ],
641
+])
642
+%-->
643
+
644
+If you look closely, you can see the first run I did with a power setting of 50 / 255.
645
+This was not strong enough, the lines are only faintly visible.
646
+So I did another run with 100 / 255 over it.
647
+This was enough power for nice visible lines, but the laser offset unfortunately shifted slightly between the runs.
648
+
649
+As I grew tired of removing, flashing and re-inserting of the SD card, I decided to also write a quick little G-Code sender for the serial port.
650
+
651
+<pre class="sh_python">
652
+#!/usr/bin/env python
653
+
654
+import sys
655
+import serial
656
+
657
+if len(sys.argv) < 3:
658
+    print("Usage:")
659
+    print("    " + sys.argv[0] + " /dev/serial/port input.gcode")
660
+    sys.exit(1)
661
+
662
+port_file = sys.argv[1]
663
+in_file = sys.argv[2]
664
+
665
+port = serial.Serial()
666
+port.port = port_file
667
+port.baudrate = 115200
668
+#port.timeout = 0.0
669
+
670
+max_cmd_buffer = 5
671
+counter = 0
672
+
673
+def connect_serial():
674
+    try:
675
+        port.open()
676
+        if port.is_open:
677
+            print("connected to: " + port_file)
678
+        else:
679
+            print("error connecting to " + port_file)
680
+            sys.exit(1)
681
+    except serial.serialutil.SerialException:
682
+        print("error connecting to " + port_file)
683
+        sys.exit(1)
684
+
685
+def wait_for_text(s):
686
+    while True:
687
+        response = port.read_until().decode().strip()
688
+        print("rx w: " + response)
689
+        if response.startswith(s):
690
+            break
691
+        elif response.startswith("Error"):
692
+            print(response)
693
+            print("CNC returned error. aborting.")
694
+            abort_print(False)
695
+            port.close()
696
+            sys.exit(1)
697
+
698
+def abort_print(wait_for_oks = True):
699
+    global counter
700
+
701
+    print("tx: M5")
702
+    port.write("M5\n".encode())
703
+    counter += 1
704
+
705
+    print("tx: G0 X0 Y0 F1000")
706
+    port.write("G0 X0 Y0 F1000\n".encode())
707
+    counter += 1
708
+
709
+    if wait_for_oks:
710
+        while counter > 0:
711
+            wait_for_text("ok")
712
+            counter -= 1
713
+
714
+def send_file(f):
715
+    global counter
716
+
717
+    for line in f:
718
+        if ";" in line:
719
+            line = line.split(";")[0]
720
+
721
+        line = line.strip()
722
+
723
+        if len(line) <= 0:
724
+            print("skipping empty line")
725
+            continue
726
+
727
+        print("tx: " + line)
728
+        tx = line.encode() + b'\n'
729
+        port.write(tx)
730
+
731
+        counter += 1
732
+        print("cnt=" + str(counter))
733
+
734
+        while counter >= max_cmd_buffer:
735
+            response = port.read_until().decode().strip()
736
+            #print("rx: " + response)
737
+            if response.startswith("ok"):
738
+                counter -= 1
739
+                print("cnt=" + str(counter))
740
+            elif response.startswith("Error"):
741
+                print(response)
742
+                print("CNC returned error. aborting.")
743
+                abort_print(False)
744
+                port.close()
745
+                sys.exit(1)
746
+
747
+def main():
748
+    connect_serial()
749
+
750
+    print("waiting for CNC to reset")
751
+    wait_for_text("start")
752
+
753
+    print("auto-homing after reset")
754
+    print("tx: G28")
755
+    port.write("G28\n".encode())
756
+    wait_for_text("ok")
757
+
758
+    try:
759
+        with open(in_file, 'r') as f:
760
+            send_file(f)
761
+
762
+        while counter > 0:
763
+            wait_for_text("ok")
764
+            counter -= 1
765
+    except KeyboardInterrupt:
766
+        print("user interrupt. aborting.")
767
+        abort_print(True)
768
+
769
+    port.close()
770
+
771
+if __name__ == '__main__':
772
+    main()
773
+</pre>
774
+
775
+Of course it's important to use the proper speed, power and number of iterations for each object to cut.
776
+Not enough power or iterations, or too high of a speed, and the object will still be part of the stock material.
777
+Too much power or iterations and not only the object will be cut, but also the base plate below.
778
+
779
+So I first did some tests manually, which is relatively time consuming.
780
+I then made another Python script go generate G-Code for a cutting test, varying speed and number of iterations.
781
+
782
+<pre class="sh_python">
783
+#!/usr/bin/env python
784
+
785
+# general settings
786
+filename = "cut_test.gcode"
787
+pwr = 255
788
+pwr_num = 150
789
+speed_g0 = 3000
790
+speed_g1_num = 500
791
+
792
+# settings of single test shape
793
+width = 10
794
+height = 10
795
+dist = 2
796
+
797
+# range for tests
798
+iterations = [ 5, 10, 15, 20 ]
799
+speeds_g1 = [ 1000, 500, 200 ]
800
+
801
+def drawSquare(w, h, x, y, speed_g1):
802
+    write("G0 X" + str(x) + " Y" + str(y) + " S0 F" + str(speed_g0))
803
+    square = [
804
+        ( x + w, y ),
805
+        ( x + w, y + h ),
806
+        ( x, y + h ),
807
+        ( x, y ),
808
+    ]
809
+    for xp, yp in square:
810
+        write("G1 X" + str(xp) + " Y" + str(yp) + " S" + str(pwr) + " F" + str(speed_g1))
811
+
812
+def drawHeart(w, h, x, y, speed_g1):
813
+    heart = [
814
+        ( 0.0, 5.0 / 8.0 ),
815
+        ( 0.0, 6.0 / 8.0 ),
816
+        ( 0.1, 7.0 / 8.0 ),
817
+        ( 0.2, 8.0 / 8.0 ),
818
+        ( 0.3, 8.0 / 8.0 ),
819
+        ( 0.4, 7.0 / 8.0 ),
820
+        ( 0.5, 6.0 / 8.0 ),
821
+        ( 0.6, 7.0 / 8.0 ),
822
+        ( 0.7, 8.0 / 8.0 ),
823
+        ( 0.8, 8.0 / 8.0 ),
824
+        ( 0.9, 7.0 / 8.0 ),
825
+        ( 1.0, 6.0 / 8.0 ),
826
+        ( 1.0, 5.0 / 8.0 ),
827
+        ( 0.9, 4.0 / 8.0 ),
828
+        ( 0.8, 3.0 / 8.0 ),
829
+        ( 0.7, 2.0 / 8.0 ),
830
+        ( 0.6, 1.0 / 8.0 ),
831
+        ( 0.5, 0.0 / 8.0 ),
832
+        ( 0.4, 1.0 / 8.0 ),
833
+        ( 0.3, 2.0 / 8.0 ),
834
+        ( 0.2, 3.0 / 8.0 ),
835
+        ( 0.1, 4.0 / 8.0 ),
836
+        ( 0.0, 5.0 / 8.0 )
837
+    ]
838
+    write("G0 X" + str(x + heart[0][0] * w) + " Y" + str(y + heart[0][1] * h) + " S0 F" + str(speed_g0))
839
+    for xp, yp in heart:
840
+        write("G1 X" + str(x + xp * w) + " Y" + str(y + yp * h) + " S" + str(pwr) + " F" + str(speed_g1))
841
+
842
+def drawShape(w, h, x, y, speed_g1):
843
+    #drawSquare(w, h, x, y, speed_g1)
844
+    drawHeart(w, h, x, y, speed_g1)
845
+
846
+digits = [
847
+    [
848
+        # 0
849
+        (0.0, 0.0),
850
+        (0.0, 1.0),
851
+        (1.0, 1.0),
852
+        (1.0, 0.0),
853
+        (0.0, 0.0)
854
+    ], [
855
+        # 1
856
+        (0.5, 0.0),
857
+        (0.5, 1.0)
858
+    ], [
859
+        # 2
860
+        (0.0, 1.0),
861
+        (1.0, 1.0),
862
+        (1.0, 0.5),
863
+        (0.0, 0.5),
864
+        (0.0, 0.0),
865
+        (1.0, 0.0),
866
+    ], [
867
+        # 3
868
+        (0.0, 1.0),
869
+        (1.0, 1.0),
870
+        (1.0, 0.5),
871
+        (0.0, 0.5),
872
+        (1.0, 0.5),
873
+        (1.0, 0.0),
874
+        (0.0, 0.0),
875
+    ], [
876
+        # 4
877
+        (0.0, 1.0),
878
+        (0.0, 0.5),
879
+        (1.0, 0.5),
880
+        (1.0, 1.0),
881
+        (1.0, 0.0),
882
+    ], [
883
+        # 5
884
+        (1.0, 1.0),
885
+        (0.0, 1.0),
886
+        (0.0, 0.5),
887
+        (1.0, 0.5),
888
+        (1.0, 0.0),
889
+        (0.0, 0.0),
890
+    ], [
891
+        # 6
892
+        (1.0, 1.0),
893
+        (0.0, 1.0),
894
+        (0.0, 0.0),
895
+        (1.0, 0.0),
896
+        (1.0, 0.5),
897
+        (0.0, 0.5),
898
+    ], [
899
+        # 7
900
+        (0.0, 1.0),
901
+        (1.0, 1.0),
902
+        (1.0, 0.0),
903
+    ], [
904
+        # 8
905
+        (1.0, 0.5),
906
+        (1.0, 1.0),
907
+        (0.0, 1.0),
908
+        (0.0, 0.5),
909
+        (1.0, 0.5),
910
+        (1.0, 0.0),
911
+        (0.0, 0.0),
912
+        (0.0, 0.5),
913
+    ], [
914
+        # 9
915
+        (1.0, 0.5),
916
+        (1.0, 1.0),
917
+        (0.0, 1.0),
918
+        (0.0, 0.5),
919
+        (1.0, 0.5),
920
+        (1.0, 0.0),
921
+        (0.0, 0.0),
922
+    ]
923
+]
924
+
925
+font_w = 1.5
926
+font_h = 3.0
927
+font_d = 0.5
928
+
929
+def draw_digit(f, i, size_x, size_y, off_x, off_y):
930
+    dig = digits[i]
931
+    n = 0
932
+    for p in dig:
933
+        x, y = p
934
+        s = ""
935
+
936
+        if n == 0:
937
+            s += "G0 S0 F" + str(speed_g0)
938
+        else:
939
+            s += "G1 S" + str(pwr_num) + " F" + str(speed_g1_num)
940
+
941
+        s += " X" + str(off_x + size_x * x)
942
+        s += " Y" + str(off_y + size_y * y)
943
+
944
+        print(s)
945
+        f.write(s + "\n")
946
+        n += 1
947
+
948
+    return s
949
+
950
+def draw_number(f, n, x, y):
951
+    if n >= 1000:
952
+        draw_digit(f, int(n / 1000),       font_w, font_h, x,                         y)
953
+        draw_digit(f, int((n / 100)) % 10, font_w, font_h, x + font_d + font_w,       y)
954
+        draw_digit(f, int((n / 10) % 10),  font_w, font_h, x + 2 * (font_d + font_w), y)
955
+        draw_digit(f, int(n % 10),         font_w, font_h, x + 3 * (font_d + font_w), y)
956
+    elif n >= 100:
957
+        draw_digit(f, int(n / 100),       font_w, font_h, x,                         y)
958
+        draw_digit(f, int((n / 10) % 10), font_w, font_h, x + font_d + font_w,       y)
959
+        draw_digit(f, int(n % 10),        font_w, font_h, x + 2 * (font_d + font_w), y)
960
+    elif n >= 10:
961
+        draw_digit(f, int(n / 10), font_w, font_h, x,                   y)
962
+        draw_digit(f, int(n % 10), font_w, font_h, x + font_d + font_w, y)
963
+    else:
964
+        draw_digit(f, n, font_w, font_h, x, y)
965
+
966
+with open(filename, 'w') as f:
967
+    def write(s):
968
+        print(s)
969
+        f.write(s + "\n")
970
+
971
+    # header
972
+    #write("G28")
973
+    write("G90")
974
+    write("M3 I")
975
+    write("M3 S0")
976
+    write("")
977
+
978
+    # first line is not having the power applied
979
+    # TODO
980
+    write("G0 X0 Y0 S0 F" + str(speed_g0))
981
+    write("G1 X1 Y0 S1 F" + str(speeds_g1[0]))
982
+    write("G0 X0 Y0 S0 F" + str(speed_g0))
983
+    write("G1 X0 Y1 S1 F" + str(speeds_g1[0]))
984
+    write("G0 X0 Y0 S0 F" + str(speed_g0))
985
+
986
+    max_num_str_len = 4
987
+    str_w = (max_num_str_len * font_w) + ((max_num_str_len - 1) * font_d)
988
+
989
+    write("; iterations")
990
+    for i in range(0, len(iterations)):
991
+        write("; " + str(iterations[i]))
992
+        draw_number(f, iterations[i], (width + str_w) / 2.0 + dist + i * (width + dist), 0)
993
+    write("")
994
+
995
+    write("; speeds")
996
+    for i in range(0, len(speeds_g1)):
997
+        write("; " + str(speeds_g1[i]))
998
+        draw_number(f, speeds_g1[i], 0, (height + font_h) / 2.0 + dist + i * (height + dist))
999
+
1000
+    for i in range(0, len(speeds_g1)):
1001
+        for j in range(0, len(iterations)):
1002
+            write("; speed=" + str(speeds_g1[i]) + " n=" + str(iterations[j]))
1003
+            for k in range(0, iterations[j]):
1004
+                drawShape(width, height, str_w + dist + j * (width + dist), font_h + dist + i * (height + dist), speeds_g1[i])
1005
+            write("")
1006
+
1007
+    # footer
1008
+    write("M5")
1009
+    write("G0 X0 Y0 F" + str(speed_g0))
1010
+</pre>
1011
+
1012
+I'm drawing digits again, like in the Grid G-Code generator above.
1013
+It seems I need to create some kind of library or so if I continue this route of generating G-Code.
1014
+
1015
+<!--%
1016
+lightgallery([
1017
+    [ "img/laser_cut_test_1.jpg", "Test sheet being cut" ],
1018
+    [ "img/laser_cut_test_3.jpg", "Finished test run" ],
1019
+])
1020
+%-->
1021
+
1022
+In the pictures above, the 3x3 grid of hearts on the right was made with the laser slightly out of focus.
1023
+It was not able to cut through in this run, even with 30 iterations.
1024
+I also still had the text not aligned properly.
1025
+
1026
+The 3x1 and 3x3 runs on the left side were made after properly focussing the laser and fixing the text alignment.
1027
+Here 200mm/min with 10 iterations was already enough to cut through the 3mm plywood.
1028
+
1029
+## Cutting Tests
1030
+<a class="anchor" name="cutting_tests"></a>
1031
+
1032
+For the first 'real' test I decided to cut the assembly jig for the [Pagoda antenna](https://www.maartenbaert.be/quadcopters/antennas/pagoda-antenna/#design-files) by Maarten Baert.
1033
+The files are available in dxf and svg format.
1034
+I was able to easily import the svg file in LaserGRBL to create G-Code, afterwards converting it to the proper format using my script described above.
1035
+
1036
+<!--%
1037
+lightgallery([
1038
+    [ "img/laser_pagoda_1.jpg", "Front side of the Pagoda jig sheet" ],
1039
+    [ "img/laser_pagoda_2.jpg", "Back side of the Pagoda jig sheet" ],
1040
+    [ "img/laser_pagoda_3.jpg", "Assembled Pagoda antenna jig" ],
1041
+])
1042
+%-->
1043
+
1044
+As in one of my previous tests I used 200mm/min with 10 iterations.
1045
+But this was not quite enough to get the parts out easily.
1046
+In hindsight I should have used at least 15 iterations.
1047
+But the result was still usable, even with only 10 iterations.
1048
+
1049
+Next my SO requested some christmas decoration, so I decided to attempt to cut this [Christmas Tree Ornament](https://www.thingiverse.com/thing:43041) by TomKeddie on Thingiverse.
1050
+It is made for 4.8mm plywood, so I scaled it down by a factor of `3.0 / 4.8 = 0.625`.
1051
+The design comes as a dxf file, so I imported that in Inkscape and then exported an svg file from that.
1052
+This I then imported in LaserGRBL, generated G-Code and converted that using the script above.
1053
+
1054
+<!--%
1055
+lightgallery([
1056
+    [ "img/laser_tree_test.mp4", "video/mp4", "", "", "Cutting a christmas tree decoration" ],
1057
+    [ "img/laser_tree_segments.mp4", "video/mp4", "", "", "Segmented lines while cutting" ],
1058
+])
1059
+%-->
1060
+
1061
+As you can tell from the second video, what should be single continuous lines has instead sometimes been split up into many small path segments, which LaserGRBL is cutting in non-optimal ordering.
1062
+I have not yet found a way to easily clean these paths up.
1063
+
1064
+<!--%
1065
+lightgallery([
1066
+    [ "img/laser_tree_1.jpg", "Front side of christmas tree sheet" ],
1067
+    [ "img/laser_tree_2.jpg", "Back side of christmas tree sheet" ],
1068
+    [ "img/laser_tree_3.jpg", "Finished christmas tree ornament" ],
1069
+])
1070
+%-->
1071
+
1072
+The end-result does not seem to be really affected by this however, although with 200mm/min and 15 iterations the parts were not removable as easily as I hoped.
1073
+So I think I either have to adjust the focus better or go up to 20 iterations for the 3mm birch plywood.
1074
+
1075
+## Future Improvements
1076
+<a class="anchor" name="future_improvements"></a>
1077
+
1078
+I didn't expect it to be this bad, but cutting wood really produces a noticeable amount of smoke and the smell of burnt wood.
1079
+So in the long run I will have to add some kind of air filtration system to my Ikea Lack tower.
1080
+Also I now better understand the need for Air Assist on laser cutters.
1081
+My small fans don't help much in keeping the light-path free of smoke and soot.
1082
+But I will hopefully get away with running it for a while without these additions.
1083
+
1084
+Another obvious improvement is the addition of a Raspberry Pi and a Webcam, to allow remote monitoring of long-running jobs.
1085
+I also came across the [LaserWeb project](https://laserweb.yurl.ch/) which seems to be similar in spirit to OctoPrint for 3D printers, but also with the ability of processing vector and bitmap files from within the web interface.
1086
+This would be a very useful addition and solve a lot of the software workflow problems illustrated above.
1087
+Also theres more than enough room left on my base plate for a SBC.
1088
+
1089
+## Cutting Parameters
1090
+<a class="anchor" name="cutting_parameters"></a>
1091
+
1092
+Here are the results of all cutting tests I've made up to now.
1093
+I will update this table as soon as I gather new data 🧑‍🔬
1094
+
1095
+<!--%
1096
+tableHelper([ "align-right", "align-right", "align-right", "align-right", "align-right", "align-right", "align-right", "align-left", "align-left" ],
1097
+    [ "Type", "Material", "Thickness<br><span style=\"font-size: small;\">(in mm)</span>", "Power<br><span style=\"font-size: small;\">(in W)</span>", "PWM<br><span style=\"font-size: small;\">(out of 255)</span>", "Speed<br><span style=\"font-size: small;\">(in mm/min)</span>", "Iterations", "Remarks", "Date" ], [
1098
+        [ "<s>Plywood</s>", "<s>Birch</s>", "<s>3</s>", "<s>2.5</s>", "<s>255</s>", "<s>200</s>", "<s>5</s>", "<s>Not possible to remove.</s>", "<s>2022-11-24</s>" ],
1099
+        [ "Plywood", "Birch", "3", "2.5", "255", "200", "10", "Difficult to remove.", "2022-11-24" ],
1100
+        [ "Plywood", "Birch", "3", "2.5", "255", "200", "15", "Easily removable.", "2022-11-24" ],
1101
+        [ "Plywood", "Birch", "3", "2.5", "255", "200", "20", "Freely fell out.", "2022-11-24" ],
1102
+        [ "<s>Plywood</s>", "<s>Birch</s>", "<s>3</s>", "<s>2.5</s>", "<s>255</s>", "<s>500</s>", "<s>15</s>", "<s>Not possible to remove.</s>", "<s>2022-11-24</s>" ],
1103
+        [ "Plywood", "Birch", "3", "2.5", "255", "500", "20", "Difficult to remove.", "2022-11-24" ],
1104
+        [ "Plywood", "Birch", "3", "2.5", "255", "200", "15", "Difficult to remove.", "2022-11-25" ],
1105
+    ]
1106
+)
1107
+%-->
1108
+
531
 ## More Pictures
1109
 ## More Pictures
1110
+<a class="anchor" name="more_pictures"></a>
532
 
1111
 
533
 <div class="collapse">Some more photographs I didn't use above.</div>
1112
 <div class="collapse">Some more photographs I didn't use above.</div>
534
 <div class="collapsecontent">
1113
 <div class="collapsecontent">
535
 <!--%
1114
 <!--%
536
 lightgallery([
1115
 lightgallery([
537
     [ "img/laser_tower_2.jpg", "Frontal view in Ikea Lack tower" ],
1116
     [ "img/laser_tower_2.jpg", "Frontal view in Ikea Lack tower" ],
1117
+    [ "img/laser_cut_test_2.jpg", "Finished test run" ],
538
     [ "img/laser_gt2560.jpg", "GT2560 mainboard of laser engraver" ],
1118
     [ "img/laser_gt2560.jpg", "GT2560 mainboard of laser engraver" ],
539
 ])
1119
 ])
540
 %-->
1120
 %-->

+ 37
- 1
macros.py Wyświetl plik

214
 #     [ "image-link", "description" ],
214
 #     [ "image-link", "description" ],
215
 #     [ "image-link", "thumbnail-link", "description" ],
215
 #     [ "image-link", "thumbnail-link", "description" ],
216
 #     [ "youtube-link", "thumbnail-link", "description" ],
216
 #     [ "youtube-link", "thumbnail-link", "description" ],
217
-#     [ "video-link", "mime", "thumbnail-link", "image-link", "description" ]
217
+#     [ "video-link", "mime", "thumbnail-link", "image-link", "description" ],
218
+#     [ "video-link", "mime", "", "", "description" ],
218
 # ])
219
 # ])
219
 
220
 
220
 # it will also auto-generate thumbnails and resize and strip EXIF from images
221
 # it will also auto-generate thumbnails and resize and strip EXIF from images
221
 # using the included web-image-resize script.
222
 # using the included web-image-resize script.
223
+# and it can generate video thumbnails and posters with the video-thumb script.
222
 
224
 
223
 def lightgallery_check_thumbnail(link, thumb):
225
 def lightgallery_check_thumbnail(link, thumb):
224
     # only check local image links
226
     # only check local image links
245
     script = os.path.join(os.getcwd(), 'web-image-resize')
247
     script = os.path.join(os.getcwd(), 'web-image-resize')
246
     os.system(script + ' ' + path)
248
     os.system(script + ' ' + path)
247
 
249
 
250
+def lightgallery_check_thumbnail_video(link, thumb, poster):
251
+    # only check local image links
252
+    if not link.startswith('img/'):
253
+        return
254
+
255
+    # generate thumbnail filenames video-thumb will create
256
+    x = link.rfind('.')
257
+    thumb_l = link[:x] + '_thumb.png'
258
+    poster_l = link[:x] + '_poster.png'
259
+
260
+    # only run when desired thumb path matches calculated ones
261
+    if (thumb_l != thumb) or (poster_l != poster):
262
+        return
263
+
264
+    # generate fs path to images
265
+    path = os.path.join(os.getcwd(), 'static', link)
266
+    thumb_p = os.path.join(os.getcwd(), 'static', thumb)
267
+    poster_p = os.path.join(os.getcwd(), 'static', poster)
268
+
269
+    # no need to generate thumb again
270
+    if os.path.exists(thumb_p) or os.path.exists(poster_p):
271
+        return
272
+
273
+    # run video-thumb to generate thumbnail
274
+    script = os.path.join(os.getcwd(), 'video-thumb')
275
+    os.system(script + ' ' + path)
276
+
248
 def lightgallery(links):
277
 def lightgallery(links):
249
     videos = [l for l in links if len(l) == 5]
278
     videos = [l for l in links if len(l) == 5]
250
     v_i = -1
279
     v_i = -1
274
         elif len(l) == 5:
303
         elif len(l) == 5:
275
             v_i += 1
304
             v_i += 1
276
             link, mime, thumb, poster, alt = videos[v_i]
305
             link, mime, thumb, poster, alt = videos[v_i]
306
+            if len(thumb) <= 0:
307
+                x = link.rfind('.')
308
+                thumb = link[:x] + '_thumb.png'
309
+            if len(poster) <= 0:
310
+                x = link.rfind('.')
311
+                poster = link[:x] + '_poster.png'
312
+            lightgallery_check_thumbnail_video(link, thumb, poster)
277
             print '<div class="border" data-poster="' + poster + '" data-sub-html="' + alt + '" data-html="#video' + str(v_i) + '"><a href="' + link + '"><img class="pic" src="' + thumb + '"></a></div>'
313
             print '<div class="border" data-poster="' + poster + '" data-sub-html="' + alt + '" data-html="#video' + str(v_i) + '"><a href="' + link + '"><img class="pic" src="' + thumb + '"></a></div>'
278
         else:
314
         else:
279
             raise NameError('Invalid number of arguments for lightgallery')
315
             raise NameError('Invalid number of arguments for lightgallery')

+ 12
- 0
static/css/style.css Wyświetl plik

25
     padding-bottom: 0.2em;
25
     padding-bottom: 0.2em;
26
 }
26
 }
27
 
27
 
28
+s {
29
+    text-decoration-thickness: 0.2em;
30
+}
31
+
32
+s:hover {
33
+  text-decoration: none;
34
+}
35
+
28
 #nav {
36
 #nav {
29
     border-bottom-style: solid;
37
     border-bottom-style: solid;
30
     border-bottom-width: 2px;
38
     border-bottom-width: 2px;
296
     border-color: #32CD32;
304
     border-color: #32CD32;
297
 }
305
 }
298
 
306
 
307
+s {
308
+    text-decoration-color: #FF0000;
309
+}
310
+
299
 .releasecard, .collapse {
311
 .releasecard, .collapse {
300
     background-color: #C0C0C0;
312
     background-color: #C0C0C0;
301
 }
313
 }

BIN
static/img/laser_cut_test_1.jpg Wyświetl plik


BIN
static/img/laser_cut_test_1_small.jpg Wyświetl plik


BIN
static/img/laser_cut_test_2.jpg Wyświetl plik


BIN
static/img/laser_cut_test_2_small.jpg Wyświetl plik


BIN
static/img/laser_cut_test_3.jpg Wyświetl plik


BIN
static/img/laser_cut_test_3_small.jpg Wyświetl plik


BIN
static/img/laser_grid.jpg Wyświetl plik


BIN
static/img/laser_grid_small.jpg Wyświetl plik


BIN
static/img/laser_pagoda_1.jpg Wyświetl plik


BIN
static/img/laser_pagoda_1_small.jpg Wyświetl plik


BIN
static/img/laser_pagoda_2.jpg Wyświetl plik


BIN
static/img/laser_pagoda_2_small.jpg Wyświetl plik


BIN
static/img/laser_pagoda_3.jpg Wyświetl plik


BIN
static/img/laser_pagoda_3_small.jpg Wyświetl plik


BIN
static/img/laser_power_switch.jpg Wyświetl plik


BIN
static/img/laser_power_switch_small.jpg Wyświetl plik


BIN
static/img/laser_psu.jpg Wyświetl plik


BIN
static/img/laser_psu_small.jpg Wyświetl plik


BIN
static/img/laser_side_1.jpg Wyświetl plik


BIN
static/img/laser_side_1_small.jpg Wyświetl plik


BIN
static/img/laser_side_2.jpg Wyświetl plik


BIN
static/img/laser_side_2_small.jpg Wyświetl plik


BIN
static/img/laser_tree_1.jpg Wyświetl plik


BIN
static/img/laser_tree_1_small.jpg Wyświetl plik


BIN
static/img/laser_tree_2.jpg Wyświetl plik


BIN
static/img/laser_tree_2_small.jpg Wyświetl plik


BIN
static/img/laser_tree_3.jpg Wyświetl plik


BIN
static/img/laser_tree_3_small.jpg Wyświetl plik


BIN
static/img/laser_tree_segments.mp4 Wyświetl plik


BIN
static/img/laser_tree_segments_poster.png Wyświetl plik


BIN
static/img/laser_tree_segments_thumb.png Wyświetl plik


BIN
static/img/laser_tree_test.mp4 Wyświetl plik


BIN
static/img/laser_tree_test_poster.png Wyświetl plik


BIN
static/img/laser_tree_test_thumb.png Wyświetl plik


BIN
static/img/laser_x_support.jpg Wyświetl plik


BIN
static/img/laser_x_support_small.jpg Wyświetl plik


BIN
static/img/laser_y_gantry.jpg Wyświetl plik


BIN
static/img/laser_y_gantry_small.jpg Wyświetl plik


BIN
static/img/lasergrbl_settings.png Wyświetl plik


BIN
static/img/lasergrbl_settings_small.png Wyświetl plik


+ 36
- 0
video-thumb Wyświetl plik

1
+#!/bin/bash
2
+
3
+if [ -z "$1" ]; then
4
+    echo "No argument supplied"
5
+    exit 1
6
+fi
7
+
8
+sdir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
9
+dir=$(dirname "${1}")
10
+video=$(basename -- "${1}")
11
+ext="${video##*.}"
12
+name="${video%.*}"
13
+thumb="${name}_thumb.png"
14
+poster="${name}_poster.png"
15
+tmp="${name}_tmp.png"
16
+
17
+echo $sdir
18
+
19
+echo "Directory : $dir"
20
+echo "Input file: $video"
21
+echo "Thumbnail : $thumb"
22
+echo "Poster    : $poster"
23
+
24
+cd "$dir"
25
+
26
+echo ffmpeg -i $video -vf "select=eq(n\,0)" -n -update 1 -frames:v 1 $poster
27
+ffmpeg -i $video -vf "select=eq(n\,0)" -n -update 1 -frames:v 1 $poster
28
+
29
+echo convert "$poster" -auto-orient -thumbnail 300x300\> -strip "$tmp"
30
+convert "$poster" -auto-orient -thumbnail 300x300\> -strip "$tmp"
31
+
32
+echo convert $tmp \( $sdir/static/lg/video-play.png -background none -gravity center \) -composite $thumb
33
+convert $tmp \( $sdir/static/lg/video-play.png -background none -gravity center \) -composite $thumb
34
+
35
+echo rm -rf $tmp
36
+rm -rf "$tmp"

Ładowanie…
Anuluj
Zapisz