meerk40t 0.9.7051__py2.py3-none-any.whl → 0.9.7900__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. meerk40t/balormk/controller.py +3 -3
  2. meerk40t/balormk/device.py +7 -0
  3. meerk40t/balormk/driver.py +23 -14
  4. meerk40t/balormk/galvo_commands.py +18 -3
  5. meerk40t/balormk/gui/balorconfig.py +6 -0
  6. meerk40t/balormk/livelightjob.py +36 -14
  7. meerk40t/camera/camera.py +1 -0
  8. meerk40t/camera/gui/camerapanel.py +154 -58
  9. meerk40t/camera/plugin.py +46 -5
  10. meerk40t/core/elements/branches.py +90 -20
  11. meerk40t/core/elements/elements.py +59 -37
  12. meerk40t/core/elements/trace.py +10 -6
  13. meerk40t/core/node/node.py +2 -0
  14. meerk40t/core/plotplanner.py +7 -4
  15. meerk40t/device/gui/defaultactions.py +78 -14
  16. meerk40t/dxf/dxf_io.py +42 -0
  17. meerk40t/grbl/controller.py +245 -35
  18. meerk40t/grbl/device.py +102 -26
  19. meerk40t/grbl/driver.py +8 -2
  20. meerk40t/grbl/gui/grblconfiguration.py +6 -0
  21. meerk40t/grbl/gui/grblcontroller.py +1 -1
  22. meerk40t/gui/about.py +7 -0
  23. meerk40t/gui/choicepropertypanel.py +20 -30
  24. meerk40t/gui/devicepanel.py +27 -16
  25. meerk40t/gui/icons.py +15 -0
  26. meerk40t/gui/laserpanel.py +102 -54
  27. meerk40t/gui/materialtest.py +10 -0
  28. meerk40t/gui/mkdebug.py +268 -9
  29. meerk40t/gui/navigationpanels.py +65 -7
  30. meerk40t/gui/propertypanels/operationpropertymain.py +185 -91
  31. meerk40t/gui/scenewidgets/elementswidget.py +7 -1
  32. meerk40t/gui/scenewidgets/selectionwidget.py +24 -9
  33. meerk40t/gui/simulation.py +1 -1
  34. meerk40t/gui/statusbarwidgets/shapepropwidget.py +50 -40
  35. meerk40t/gui/statusbarwidgets/statusbar.py +2 -2
  36. meerk40t/gui/toolwidgets/toolmeasure.py +1 -1
  37. meerk40t/gui/toolwidgets/toolnodeedit.py +4 -1
  38. meerk40t/gui/toolwidgets/tooltabedit.py +9 -7
  39. meerk40t/gui/wxmeerk40t.py +2 -0
  40. meerk40t/gui/wxmmain.py +23 -9
  41. meerk40t/gui/wxmribbon.py +36 -0
  42. meerk40t/gui/wxutils.py +66 -42
  43. meerk40t/kernel/inhibitor.py +120 -0
  44. meerk40t/kernel/kernel.py +38 -0
  45. meerk40t/lihuiyu/controller.py +33 -3
  46. meerk40t/lihuiyu/device.py +99 -4
  47. meerk40t/lihuiyu/driver.py +62 -5
  48. meerk40t/lihuiyu/gui/lhycontrollergui.py +69 -24
  49. meerk40t/lihuiyu/gui/lhydrivergui.py +6 -0
  50. meerk40t/lihuiyu/laserspeed.py +17 -10
  51. meerk40t/lihuiyu/parser.py +23 -0
  52. meerk40t/main.py +1 -1
  53. meerk40t/moshi/gui/moshidrivergui.py +7 -0
  54. meerk40t/newly/controller.py +3 -2
  55. meerk40t/newly/device.py +23 -2
  56. meerk40t/newly/driver.py +8 -3
  57. meerk40t/newly/gui/newlyconfig.py +7 -0
  58. meerk40t/ruida/gui/ruidaconfig.py +7 -0
  59. meerk40t/tools/geomstr.py +68 -48
  60. meerk40t/tools/rasterplotter.py +0 -5
  61. meerk40t/tools/ttfparser.py +155 -82
  62. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/METADATA +1 -1
  63. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/RECORD +68 -67
  64. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/LICENSE +0 -0
  65. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/WHEEL +0 -0
  66. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/entry_points.txt +0 -0
  67. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/top_level.txt +0 -0
  68. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/zip-safe +0 -0
@@ -29,7 +29,6 @@ from meerk40t.gui.wxutils import (
29
29
  wxButton,
30
30
  wxCheckBox,
31
31
  wxComboBox,
32
- wxStaticBitmap,
33
32
  wxStaticText,
34
33
  )
35
34
  from meerk40t.kernel import lookup_listener, signal_listener
@@ -262,7 +261,7 @@ class LaserPanel(wx.Panel):
262
261
  self.check_laser_arm()
263
262
 
264
263
  self.button_outline = wxButton(self, wx.ID_ANY, _("Outline"))
265
- self.button_outline.SetToolTip(_("Trace the outline the job"))
264
+ self.button_outline.SetToolTip(_("Trace the outline of the job"))
266
265
  self.button_outline.SetBitmap(
267
266
  icons8_pentagon.GetBitmap(
268
267
  resize=self.icon_size,
@@ -332,6 +331,7 @@ class LaserPanel(wx.Panel):
332
331
  lb_speed = wxStaticText(self, wx.ID_ANY, _("Speed"))
333
332
  self.label_speed = wxStaticText(self, wx.ID_ANY, "0%")
334
333
  self.slider_size = 20
334
+ self.power_mode = "relative"
335
335
  self.slider_speed = wx.Slider(
336
336
  self, wx.ID_ANY, value=10, minValue=1, maxValue=20
337
337
  )
@@ -380,33 +380,67 @@ class LaserPanel(wx.Panel):
380
380
  self.button_start_was_clicked = False
381
381
 
382
382
  def update_override_controls(self):
383
+ def set_boundaries(slider, current_value, min_value, max_value):
384
+ slider.SetMin(min_value)
385
+ slider.SetMax(max_value)
386
+ slider.SetValue(current_value)
387
+
383
388
  flag_power = False
384
389
  override = False
385
- if hasattr(self.context.device.driver, "has_adjustable_power"):
386
- if self.context.device.driver.has_adjustable_power:
387
- flag_power = True
388
- # Let's establish the value and update the slider...
389
- value = self.context.device.driver.power_scale
390
- if value != 1:
391
- override = True
392
- half = self.slider_size / 2
393
- sliderval = int(value * half)
394
- sliderval = max(1, min(self.slider_size, sliderval))
395
- self.slider_power.SetValue(sliderval)
396
- self.on_slider_speed(None)
390
+ if (
391
+ hasattr(self.context.device.driver, "has_adjustable_power")
392
+ and self.context.device.driver.has_adjustable_power
393
+ ):
394
+ flag_power = True
395
+ # Let's establish the value and update the slider...
396
+ value = self.context.device.driver.power_scale
397
+ if value != 1:
398
+ override = True
399
+ half = self.slider_size / 2
400
+ sliderval = int(value * half)
401
+ sliderval = max(1, min(self.slider_size, sliderval))
402
+ set_boundaries(self.slider_power, sliderval, 1, self.slider_size)
403
+ self.slider_power.SetToolTip(
404
+ _("Increases/decreases the regular laser power by this amount.")
405
+ + "\n"
406
+ + _("This affects running jobs, so use with care!")
407
+ )
408
+ self.power_mode = "relative"
409
+ self.on_slider_power(None)
410
+ elif (
411
+ hasattr(self.context.device.driver, "has_adjustable_maximum_power")
412
+ and self.context.device.driver.has_adjustable_maximum_power
413
+ ):
414
+ flag_power = True
415
+ override = True
416
+ dev_mode = getattr(self.context.kernel.root, "developer_mode", False)
417
+ # Let's establish the value and update the slider...
418
+ min_value = 1
419
+ max_value = 100 if dev_mode else 50
420
+ sliderval = min(max_value, self.context.device.driver.max_power_scale)
421
+ set_boundaries(self.slider_power, sliderval, min_value, max_value)
422
+ self.slider_power.SetToolTip(
423
+ _("Sets the maximum laser power level.")
424
+ + "\n"
425
+ + _("Setting this too high may cause damage to your laser tube!")
426
+ )
427
+ self.power_mode = "maximum"
428
+ self.on_slider_power(None)
397
429
  flag_speed = False
398
- if hasattr(self.context.device.driver, "has_adjustable_speed"):
399
- if self.context.device.driver.has_adjustable_speed:
400
- flag_speed = True
401
- # Let's establish the value and update the slider...
402
- value = self.context.device.driver.speed_scale
403
- if value != 1:
404
- override = True
405
- half = self.slider_size / 2
406
- sliderval = int(value * half)
407
- sliderval = max(1, min(self.slider_size, sliderval))
408
- self.slider_speed.SetValue(sliderval)
409
- self.on_slider_speed(None)
430
+ if (
431
+ hasattr(self.context.device.driver, "has_adjustable_speed")
432
+ and self.context.device.driver.has_adjustable_speed
433
+ ):
434
+ flag_speed = True
435
+ # Let's establish the value and update the slider...
436
+ value = self.context.device.driver.speed_scale
437
+ if value != 1:
438
+ override = True
439
+ half = self.slider_size / 2
440
+ sliderval = int(value * half)
441
+ sliderval = max(1, min(self.slider_size, sliderval))
442
+ set_boundaries(self.slider_speed, sliderval, 1, self.slider_size)
443
+ self.on_slider_speed(None)
410
444
 
411
445
  self.sizer_power.Show(flag_power)
412
446
  self.sizer_power.ShowItems(flag_power)
@@ -427,18 +461,22 @@ class LaserPanel(wx.Panel):
427
461
  else:
428
462
  self.slider_power.Enable(False)
429
463
  self.slider_speed.Enable(False)
430
- if hasattr(self.context.device.driver, "has_adjustable_power"):
431
- if self.context.device.driver.has_adjustable_power:
432
- if event is not None:
433
- self.context.device.driver.set_power_scale(1.0)
434
- self.slider_power.SetValue(10)
435
- self.on_slider_power(None)
436
- if hasattr(self.context.device.driver, "has_adjustable_speed"):
437
- if self.context.device.driver.has_adjustable_speed:
438
- if event is not None:
439
- self.context.device.driver.set_speed_scale(1.0)
440
- self.slider_speed.SetValue(10)
441
- self.on_slider_speed(None)
464
+ if (
465
+ hasattr(self.context.device.driver, "has_adjustable_power")
466
+ and self.context.device.driver.has_adjustable_power
467
+ ):
468
+ if event is not None:
469
+ self.context.device.driver.set_power_scale(1.0)
470
+ self.slider_power.SetValue(10)
471
+ self.on_slider_power(None)
472
+ if (
473
+ hasattr(self.context.device.driver, "has_adjustable_speed")
474
+ and self.context.device.driver.has_adjustable_speed
475
+ ):
476
+ if event is not None:
477
+ self.context.device.driver.set_speed_scale(1.0)
478
+ self.slider_speed.SetValue(10)
479
+ self.on_slider_speed(None)
442
480
 
443
481
  def on_slider_speed(self, event):
444
482
  sliderval = self.slider_speed.GetValue()
@@ -452,12 +490,18 @@ class LaserPanel(wx.Panel):
452
490
 
453
491
  def on_slider_power(self, event):
454
492
  sliderval = self.slider_power.GetValue()
455
- half = self.slider_size / 2
456
- newvalue = sliderval - half # -> -9 to +10
457
- factor = 1 + newvalue / half
458
- if event is not None:
459
- self.context.device.driver.set_power_scale(factor)
460
- msg = f"{'+' if factor > 1 else ''}{100 * (factor - 1.0):.0f}%"
493
+ if self.power_mode == "maximum":
494
+ # Maximum power mode, so we just set the value.
495
+ if event is not None:
496
+ self.context.device.driver.max_power_scale = sliderval
497
+ msg = f"{sliderval}%"
498
+ else:
499
+ half = self.slider_size / 2
500
+ newvalue = sliderval - half # -> -9 to +10
501
+ factor = 1 + newvalue / half
502
+ if event is not None:
503
+ self.context.device.driver.set_power_scale(factor)
504
+ msg = f"{'+' if factor > 1 else ''}{100 * (factor - 1.0):.0f}%"
461
505
  self.label_power.SetLabel(msg)
462
506
 
463
507
  def on_optimize(self, event):
@@ -478,6 +522,14 @@ class LaserPanel(wx.Panel):
478
522
  if self.checkbox_optimize.GetValue() != newvalue:
479
523
  self.checkbox_optimize.SetValue(newvalue)
480
524
 
525
+ @signal_listener("pwm_mode_changed")
526
+ def on_pwm_mode_changed(self, origin, *message):
527
+ """
528
+ This is called when the power scale of the device changes.
529
+ It updates the slider and label accordingly.
530
+ """
531
+ self.update_override_controls()
532
+
481
533
  @signal_listener("device;modified")
482
534
  @signal_listener("device;renamed")
483
535
  @lookup_listener("service/device/active")
@@ -655,13 +707,12 @@ class LaserPanel(wx.Panel):
655
707
  self.context.setting(bool, "laserpane_hold", False)
656
708
  if plan.plan and self.context.laserpane_hold:
657
709
  self.context("planz spool\n")
710
+ elif self.checkbox_optimize.GetValue():
711
+ self.context(
712
+ "planz clear copy preprocess validate blob preopt optimize spool\n"
713
+ )
658
714
  else:
659
- if self.checkbox_optimize.GetValue():
660
- self.context(
661
- "planz clear copy preprocess validate blob preopt optimize spool\n"
662
- )
663
- else:
664
- self.context("planz clear copy preprocess validate blob spool\n")
715
+ self.context("planz clear copy preprocess validate blob spool\n")
665
716
  self.armed = False
666
717
  self.check_laser_arm()
667
718
  if self.context.auto_spooler:
@@ -807,10 +858,7 @@ class JobPanel(wx.Panel):
807
858
 
808
859
  if not pathname.lower().endswith(f".{extension}"):
809
860
  pathname += f".{extension}"
810
- if self.context.planner.do_optimization:
811
- optpart = "preopt optimize "
812
- else:
813
- optpart = ""
861
+ optpart = "preopt optimize " if self.context.planner.do_optimization else ""
814
862
  self.context(
815
863
  f'planz clear copy preprocess validate blob {optpart}save_job "{pathname}"\n'
816
864
  )
@@ -947,6 +947,11 @@ class TemplatePanel(wx.Panel):
947
947
  standard_items = False
948
948
  choices = self.parameters[idx][7]
949
949
  self.list_options_1.Set(choices)
950
+ self.list_options_1.SetToolTip(
951
+ _("Select the values you want to test for {param}").format(
952
+ param=self.parameters[idx][2]
953
+ )
954
+ )
950
955
  checked_strings = [
951
956
  s for s in self.context.template_list1.split("|") if s
952
957
  ]
@@ -981,6 +986,11 @@ class TemplatePanel(wx.Panel):
981
986
  standard_items = False
982
987
  choices = self.parameters[idx][7]
983
988
  self.list_options_2.Set(choices)
989
+ self.list_options_2.SetToolTip(
990
+ _("Select the values you want to test for {param}").format(
991
+ param=self.parameters[idx][2]
992
+ )
993
+ )
984
994
  checked_strings = [
985
995
  s for s in self.context.template_list2.split("|") if s
986
996
  ]
meerk40t/gui/mkdebug.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
- This module contains panels that display internal developer information.
3
- They will become visible if you type 'set debug_mode True' in the
4
- console and restart the program.
2
+ This module contains panels that display internal developer information.
3
+ They will become visible if you type 'set debug_mode True' in the
4
+ console and restart the program.
5
5
  """
6
6
 
7
7
  import time
@@ -10,6 +10,17 @@ import wx
10
10
  from wx import aui
11
11
 
12
12
  import meerk40t.gui.icons as mkicons
13
+ from meerk40t.constants import (
14
+ RASTER_B2T,
15
+ RASTER_CROSSOVER,
16
+ RASTER_GREEDY_H,
17
+ RASTER_GREEDY_V,
18
+ RASTER_HATCH,
19
+ RASTER_L2R,
20
+ RASTER_R2L,
21
+ RASTER_SPIRAL,
22
+ RASTER_T2B,
23
+ )
13
24
  from meerk40t.core.units import Angle, Length
14
25
  from meerk40t.gui.wxutils import (
15
26
  ScrolledPanel,
@@ -123,6 +134,25 @@ def register_panel_window(window, context):
123
134
  context.register("pane/debug_window", pane)
124
135
 
125
136
 
137
+ def register_panel_plotter(window, context):
138
+ pane = (
139
+ aui.AuiPaneInfo()
140
+ .Float()
141
+ .MinSize(225, 110)
142
+ .FloatingSize(400, 400)
143
+ .Caption(_("Plotter Test"))
144
+ .CaptionVisible(not context.pane_lock)
145
+ .Name("debug_plotter")
146
+ .Hide()
147
+ )
148
+ pane.dock_proportion = 225
149
+ pane.control = DebugRasterPlotterPanel(window, wx.ID_ANY, context=context)
150
+ pane.submenu = "_ZZ_" + _("Debug")
151
+ pane.helptext = _("Raster plotter test")
152
+ window.on_pane_create(pane)
153
+ context.register("pane/debug_plotter", pane)
154
+
155
+
126
156
  class ShutdownPanel(wx.Panel):
127
157
  """
128
158
  Tries to create a scenario that has led to multiple runtime errors durign shutdown
@@ -339,7 +369,7 @@ class DebugColorPanel(ScrolledPanel):
339
369
  sizer_main.Add(sizer_line, 0, wx.EXPAND, 0)
340
370
  count = 0
341
371
 
342
- col:wx.Colour = wx.SystemSettings().GetColour(getattr(wx, prop))
372
+ col: wx.Colour = wx.SystemSettings().GetColour(getattr(wx, prop))
343
373
  infosizer = wx.BoxSizer(wx.VERTICAL)
344
374
  box = wxStaticBitmap(
345
375
  self, wx.ID_ANY, size=wx.Size(32, 32), style=wx.SB_RAISED
@@ -518,8 +548,13 @@ class DebugIconPanel(wx.Panel):
518
548
  if obj is not None:
519
549
  if isinstance(obj, (mkicons.VectorIcon, mkicons.PyEmbeddedImage)):
520
550
  imgs = self.icon_show.Size
521
- ms = min(imgs[0], imgs[1]) * self.context.root.bitmap_correction_scale
522
- bmp = obj.GetBitmap(resize=ms, force_darkmode=self.context.themes.dark)
551
+ ms = (
552
+ min(imgs[0], imgs[1])
553
+ * self.context.root.bitmap_correction_scale
554
+ )
555
+ bmp = obj.GetBitmap(
556
+ resize=ms, force_darkmode=self.context.themes.dark
557
+ )
523
558
  self.icon_show.SetBitmap(bmp)
524
559
 
525
560
  def pane_show(self, *args):
@@ -578,11 +613,19 @@ class DebugWindowPanel(wx.Panel):
578
613
  toggle_left = wx.ToggleButton(self, wx.ID_ANY, "Toggle")
579
614
  radio_left = wx.RadioBox(self, wx.ID_ANY, choices=("Yes", "No", "Maybe"))
580
615
  btn_bmap_left = wx.BitmapButton(
581
- self, wx.ID_ANY, mkicons.icon_bell.GetBitmap(resize=mkicons.get_default_icon_size(self.context)/2)
616
+ self,
617
+ wx.ID_ANY,
618
+ mkicons.icon_bell.GetBitmap(
619
+ resize=mkicons.get_default_icon_size(self.context) / 2
620
+ ),
582
621
  )
583
622
  slider_left = wx.Slider(self, wx.ID_ANY, value=0, minValue=0, maxValue=100)
584
623
  static_left = wx.StaticBitmap(
585
- self, wx.ID_ANY, mkicons.icon_closed_door.GetBitmap(resize=mkicons.get_default_icon_size(self.context))
624
+ self,
625
+ wx.ID_ANY,
626
+ mkicons.icon_closed_door.GetBitmap(
627
+ resize=mkicons.get_default_icon_size(self.context)
628
+ ),
586
629
  )
587
630
  left_side.Add(cb_left, 0, 0, 0)
588
631
  left_side.Add(text_left, 0, 0, 0)
@@ -605,7 +648,11 @@ class DebugWindowPanel(wx.Panel):
605
648
  toggle_right = wxToggleButton(self, wx.ID_ANY, "Toggle")
606
649
  radio_right = wxRadioBox(self, wx.ID_ANY, choices=("Yes", "No", "Maybe"))
607
650
  static_right = wxStaticBitmap(
608
- self, wx.ID_ANY, mkicons.icon_closed_door.GetBitmap(resize=mkicons.get_default_icon_size(self.context))
651
+ self,
652
+ wx.ID_ANY,
653
+ mkicons.icon_closed_door.GetBitmap(
654
+ resize=mkicons.get_default_icon_size(self.context)
655
+ ),
609
656
  )
610
657
  # right_side.Add(cb_right, 0, 0, 0)
611
658
  right_side.Add(text_right, 0, 0, 0)
@@ -644,3 +691,215 @@ class DebugWindowPanel(wx.Panel):
644
691
 
645
692
  def pane_hide(self, *args):
646
693
  return
694
+
695
+
696
+ class DebugRasterPlotterPanel(wx.Panel):
697
+ def __init__(self, *args, context=None, **kwds):
698
+ # begin wxGlade: PositionPanel.__init__
699
+ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
700
+ wx.Panel.__init__(self, *args, **kwds)
701
+
702
+ self.context = context
703
+ self.context.themes.set_window_colors(self)
704
+
705
+ sizer_main = wx.BoxSizer(wx.VERTICAL)
706
+ info = StaticBoxSizer(
707
+ self, wx.ID_ANY, _("Raster Plotter Test Info"), wx.HORIZONTAL
708
+ )
709
+ info.Add(
710
+ wxStaticText(self, wx.ID_ANY, "Dimensions:"), 0, wx.ALIGN_CENTER_VERTICAL, 0
711
+ )
712
+ self.text_x = TextCtrl(self, wx.ID_ANY, "1", check="int")
713
+ self.text_y = TextCtrl(self, wx.ID_ANY, "1", check="int")
714
+ info.Add(self.text_x, 1, wx.EXPAND, 0)
715
+ info.Add(wxStaticText(self, wx.ID_ANY, "x"), 0, wx.ALIGN_CENTER_VERTICAL, 0)
716
+ info.Add(self.text_y, 1, wx.EXPAND, 0)
717
+ info.Add(wxStaticText(self, wx.ID_ANY, "pixel"), 0, wx.ALIGN_CENTER_VERTICAL, 0)
718
+ # raster properties
719
+ raster_sizer = StaticBoxSizer(
720
+ self, wx.ID_ANY, _("Raster Properties"), wx.VERTICAL
721
+ )
722
+ raster_type_sizer = wx.BoxSizer(wx.HORIZONTAL)
723
+ raster_sizer.Add(raster_type_sizer, 0, wx.EXPAND, 0)
724
+ self.raster_terms = [
725
+ (RASTER_T2B, "Top To Bottom"),
726
+ (RASTER_B2T, "Bottom To Top"),
727
+ (RASTER_R2L, "Right To Left"),
728
+ (RASTER_L2R, "Left To Right"),
729
+ (RASTER_HATCH, "Crosshatch"),
730
+ (RASTER_GREEDY_H, "Greedy horizontal"),
731
+ (RASTER_GREEDY_V, "Greedy vertical"),
732
+ (RASTER_CROSSOVER, "Crossover"),
733
+ (RASTER_SPIRAL, "Spiral"),
734
+ ]
735
+ # Look for registered raster (image) preprocessors,
736
+ # these are routines that take one image as parameter
737
+ # and deliver a set of (result image, method (aka raster_direction) )
738
+ # that will be dealt with independently
739
+ # The registered datastructure is (rasterid, description, method)
740
+ self.raster_terms.extend(
741
+ (key, description)
742
+ for key, description, method in self.context.kernel.lookup_all(
743
+ "raster_preprocessor/.*"
744
+ )
745
+ )
746
+ # Add a couple of testcases
747
+ # test_methods = (
748
+ # (-1, "Test: Horizontal Rectangle"),
749
+ # (-2, "Test: Vertical Rectangle"),
750
+ # (-3, "Test: Horizontal Snake"),
751
+ # (-4, "Test: Vertical Snake"),
752
+ # (-5, "Test: Spiral"),
753
+ # )
754
+ # self.raster_terms.extend(test_methods)
755
+
756
+ self.raster_methods = [key for key, info in self.raster_terms]
757
+
758
+ self.combo_raster_direction = wxComboBox(
759
+ self,
760
+ wx.ID_ANY,
761
+ style=wx.CB_DROPDOWN | wx.CB_READONLY,
762
+ )
763
+ raster_type_sizer.Add(
764
+ wxStaticText(self, wx.ID_ANY, "Raster Direction:"),
765
+ 0,
766
+ wx.ALIGN_CENTER_VERTICAL,
767
+ 0,
768
+ )
769
+ self.combo_raster_direction.AppendItems(
770
+ [info for key, info in self.raster_terms]
771
+ )
772
+ raster_type_sizer.Add(
773
+ self.combo_raster_direction, 1, wx.ALIGN_CENTER_VERTICAL, 0
774
+ )
775
+ self.check_raster_bidirectional = wxCheckBox(self, wx.ID_ANY, "Bidirectional")
776
+
777
+ raster_type_sizer.Add(
778
+ self.check_raster_bidirectional, 0, wx.ALIGN_CENTER_VERTICAL, 0
779
+ )
780
+ self.combo_raster_direction.SetSelection(0)
781
+
782
+ buttons = wx.BoxSizer(wx.HORIZONTAL)
783
+ self.btn_test_onepixel_black = wxButton(self, wx.ID_ANY, "Fully Black")
784
+ self.btn_test_onepixel_white = wxButton(self, wx.ID_ANY, "Fully White")
785
+ buttons.Add(self.btn_test_onepixel_black, 1, wx.EXPAND, 0)
786
+ buttons.Add(self.btn_test_onepixel_white, 1, wx.EXPAND, 0)
787
+ self.text_result = TextCtrl(
788
+ self, wx.ID_ANY, style=wx.TE_MULTILINE | wx.TE_READONLY
789
+ )
790
+
791
+ sizer_main.Add(info, 0, wx.EXPAND, 0)
792
+ sizer_main.Add(raster_sizer, 0, wx.EXPAND, 0)
793
+ sizer_main.Add(buttons, 0, wx.EXPAND, 0)
794
+ sizer_main.Add(self.text_result, 1, wx.EXPAND, 0)
795
+
796
+ self.SetSizer(sizer_main)
797
+ sizer_main.Fit(self)
798
+ self.Layout()
799
+
800
+ self.btn_test_onepixel_black.Bind(wx.EVT_BUTTON, self.test_empty_image)
801
+ self.btn_test_onepixel_white.Bind(wx.EVT_BUTTON, self.test_fully_covered_image)
802
+
803
+ def test_empty_image(self, event):
804
+ """
805
+ Tests the speed of rasterplotter for a fully black image.
806
+ """
807
+ import numpy as np
808
+ from PIL import Image, ImageDraw
809
+
810
+ from meerk40t.tools.rasterplotter import RasterPlotter
811
+
812
+ try:
813
+ x = int(self.text_x.GetValue())
814
+ y = int(self.text_y.GetValue())
815
+ except ValueError:
816
+ self.text_result.SetValue("Invalid dimensions. Please enter integers.")
817
+ return
818
+ raster_string = self.combo_raster_direction.GetStringSelection()
819
+ raster_direction = -1
820
+ for key, info in self.raster_terms:
821
+ if raster_string == info:
822
+ raster_direction = key
823
+ break
824
+ if raster_direction < 0:
825
+ self.text_result.SetValue("Invalid raster direction selected.")
826
+ return
827
+ bidir = self.check_raster_bidirectional.GetValue()
828
+
829
+ # Notabene: a black pixel is a non-burnt one, so we invert the logic here
830
+ image = Image.new("RGBA", (x, y), "white")
831
+ image = image.convert("L")
832
+ plotter = RasterPlotter(image.load(), x, y, direction=raster_direction, bidirectional=bidir)
833
+ t = time.time()
834
+ i = 0
835
+ res = []
836
+ pixels = 0
837
+ last_x, last_y = plotter.initial_position_in_scene()
838
+ for x, y, on in plotter.plot():
839
+ i += 1
840
+ if on:
841
+ pixels += (abs(x - last_x) + 1) * (abs(y - last_y) + 1)
842
+ res.append(f"{i}: ({x}, {y}) {'on' if on else 'off'}")
843
+ last_x, last_y = x, y
844
+ ipos = plotter.initial_position_in_scene()
845
+ lpos = plotter.final_position_in_scene()
846
+ res.insert(
847
+ 0,
848
+ f"Black found: {pixels} pixels: ranging from ({ipos[0]}, {ipos[1]}) to ({lpos[0]}, {lpos[1]})",
849
+ )
850
+ res.append(f"Time taken to finish process {time.time() - t:.3f}s\n")
851
+ self.text_result.SetValue("\n".join(res))
852
+
853
+ def test_fully_covered_image(self, event):
854
+ """
855
+ Tests the speed of rasterplotter for a fully black image.
856
+ """
857
+ import numpy as np
858
+ from PIL import Image, ImageDraw
859
+
860
+ from meerk40t.tools.rasterplotter import RasterPlotter
861
+
862
+ try:
863
+ x = int(self.text_x.GetValue())
864
+ y = int(self.text_y.GetValue())
865
+ except ValueError:
866
+ self.text_result.SetValue("Invalid dimensions. Please enter integers.")
867
+ return
868
+ if self.combo_raster_direction.GetSelection() < 0:
869
+ self.text_result.SetValue("Please select a raster direction.")
870
+ return
871
+ raster_string = self.combo_raster_direction.GetStringSelection()
872
+ raster_direction = -1
873
+ for key, info in self.raster_terms:
874
+ if raster_string == info:
875
+ raster_direction = key
876
+ break
877
+ if raster_direction < 0:
878
+ self.text_result.SetValue("Invalid raster direction selected.")
879
+ return
880
+ bidir = self.check_raster_bidirectional.GetValue()
881
+
882
+ # Notabene: a black pixel is a non-burnt one, so we invert the logic here
883
+ image = Image.new("RGBA", (x, y), "black")
884
+ image = image.convert("L")
885
+
886
+ plotter = RasterPlotter(image.load(), x, y, direction=raster_direction, bidirectional=bidir)
887
+ t = time.time()
888
+ i = 0
889
+ res = []
890
+ pixels = 0
891
+ ipos = plotter.initial_position_in_scene()
892
+ lpos = plotter.final_position_in_scene()
893
+ last_x, last_y = ipos
894
+ for x, y, on in plotter.plot():
895
+ i += 1
896
+ if on:
897
+ pixels += (abs(x - last_x) + 1) * (abs(y - last_y) + 1)
898
+ res.append(f"{i}: ({x}, {y}) {'on' if on else 'off'}")
899
+ last_x, last_y = x, y
900
+ res.insert(
901
+ 0,
902
+ f"White found: {pixels} pixels: ranging from ({ipos[0]}, {ipos[1]}) to ({lpos[0]}, {lpos[1]})",
903
+ )
904
+ res.append(f"Time taken to finish process {time.time() - t:.3f}s\n")
905
+ self.text_result.SetValue("\n".join(res))
@@ -1789,8 +1789,13 @@ class PulsePanel(wx.Panel):
1789
1789
  self.spin_pulse_duration = wx.SpinCtrl(
1790
1790
  self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER, value="50", min=1, max=1000
1791
1791
  )
1792
+ self.text_power = TextCtrl(
1793
+ self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER, value="1000", check="float"
1794
+ )
1795
+ self.label_power = wxStaticText(self, wx.ID_ANY, "ppi")
1792
1796
  self.__set_properties()
1793
1797
  self.__do_layout()
1798
+ self.update_power_controls()
1794
1799
 
1795
1800
  self.Bind(
1796
1801
  wx.EVT_BUTTON, self.on_button_navigate_pulse, self.button_navigate_pulse
@@ -1813,13 +1818,17 @@ class PulsePanel(wx.Panel):
1813
1818
 
1814
1819
  def __do_layout(self):
1815
1820
  # begin wxGlade: PulsePanel.__do_layout
1816
- sizer_5 = StaticBoxSizer(self, wx.ID_ANY, _("Short Pulse:"), wx.HORIZONTAL)
1817
- sizer_5.Add(self.button_navigate_pulse, 0, wx.ALIGN_CENTER_VERTICAL, 0)
1818
- sizer_5.Add(self.spin_pulse_duration, 1, wx.ALIGN_CENTER_VERTICAL, 0)
1821
+ self.main_sizer = StaticBoxSizer(
1822
+ self, wx.ID_ANY, _("Short Pulse:"), wx.HORIZONTAL
1823
+ )
1824
+ self.main_sizer.Add(self.button_navigate_pulse, 0, wx.ALIGN_CENTER_VERTICAL, 0)
1825
+ self.main_sizer.Add(self.spin_pulse_duration, 1, wx.ALIGN_CENTER_VERTICAL, 0)
1819
1826
  label_4 = wxStaticText(self, wx.ID_ANY, _(" ms"))
1820
- sizer_5.Add(label_4, 0, wx.ALIGN_CENTER_VERTICAL, 0)
1821
- self.SetSizer(sizer_5)
1822
- sizer_5.Fit(self)
1827
+ self.main_sizer.Add(label_4, 0, wx.ALIGN_CENTER_VERTICAL, 0)
1828
+ self.main_sizer.Add(self.text_power, 1, wx.ALIGN_CENTER_VERTICAL, 0)
1829
+ self.main_sizer.Add(self.label_power, 0, wx.ALIGN_CENTER_VERTICAL, 0)
1830
+ self.SetSizer(self.main_sizer)
1831
+ self.main_sizer.Fit(self)
1823
1832
  self.Layout()
1824
1833
  # end wxGlade
1825
1834
 
@@ -1827,11 +1836,60 @@ class PulsePanel(wx.Panel):
1827
1836
  self, event=None
1828
1837
  ): # wxGlade: Navigation.<event_handler>
1829
1838
  value = self.spin_pulse_duration.GetValue()
1830
- self.context(f"pulse {value}\n")
1839
+ powerstr = ""
1840
+ if self.text_power.IsShown():
1841
+ power = self.text_power.GetValue()
1842
+ if power:
1843
+ try:
1844
+ power = float(power)
1845
+ except ValueError:
1846
+ power = None
1847
+ if power:
1848
+ if self.context.device.setting(
1849
+ bool, "use_percent_for_power_display", False
1850
+ ):
1851
+ # Convert percent to ppi
1852
+ power = power * 10
1853
+ power = max(0, min(1000, int(power)))
1854
+ self.context.device.setting(float, "last_pulse_power", 1000)
1855
+ self.context.device.last_pulse_power = power
1856
+ powerstr = f" -p {power}"
1857
+ self.context(f"pulse {value}{powerstr}\n")
1831
1858
 
1832
1859
  def on_spin_pulse_duration(self, event=None): # wxGlade: Navigation.<event_handler>
1833
1860
  self.context.navigate_pulse = float(self.spin_pulse_duration.GetValue())
1834
1861
 
1862
+ def pane_show(self, *args):
1863
+ # Is the current device pwm pulse capable?
1864
+ self.context.listen("activate;device", self.on_update)
1865
+ self.update_power_controls()
1866
+
1867
+ def pane_hide(self, *args):
1868
+ self.context.unlisten("activate;device", self.on_update)
1869
+
1870
+ def on_update(self, origin, *args):
1871
+ self.update_power_controls()
1872
+
1873
+ def update_power_controls(self):
1874
+ show_power = getattr(self.context.device, "supports_pwm", False)
1875
+ self.text_power.Show(show_power)
1876
+ self.label_power.Show(show_power)
1877
+ pval = self.context.device.setting(float, "last_pulse_power", 1000)
1878
+ if pval is None:
1879
+ pval = 1000
1880
+ if self.context.device.setting(bool, "use_percent_for_power_display", False):
1881
+ self.text_power.SetValue(f"{pval/10.0:.1f}")
1882
+ self.text_power.set_range(0, 100)
1883
+ self.text_power.SetToolTip(_("Set the power of the laser pulse in percent"))
1884
+ self.label_power.SetLabel("%")
1885
+ else:
1886
+ self.text_power.SetValue(f"{pval}")
1887
+ self.text_power.set_range(0, 1000)
1888
+ self.text_power.SetToolTip(_("Set the power of the laser pulse in ppi"))
1889
+ self.label_power.SetLabel("ppi")
1890
+
1891
+ self.main_sizer.Layout()
1892
+
1835
1893
 
1836
1894
  # class SizePanel(wx.Panel):
1837
1895
  # object_ratio = None