meerk40t 0.9.7030__py2.py3-none-any.whl → 0.9.7050__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 (85) hide show
  1. meerk40t/balormk/clone_loader.py +3 -2
  2. meerk40t/balormk/controller.py +38 -13
  3. meerk40t/balormk/cylindermod.py +1 -0
  4. meerk40t/balormk/device.py +13 -9
  5. meerk40t/balormk/driver.py +9 -2
  6. meerk40t/balormk/galvo_commands.py +3 -1
  7. meerk40t/balormk/gui/gui.py +6 -0
  8. meerk40t/balormk/livelightjob.py +338 -321
  9. meerk40t/balormk/mock_connection.py +4 -3
  10. meerk40t/balormk/usb_connection.py +11 -2
  11. meerk40t/camera/camera.py +19 -14
  12. meerk40t/camera/gui/camerapanel.py +6 -0
  13. meerk40t/core/cutplan.py +101 -78
  14. meerk40t/core/elements/element_treeops.py +435 -140
  15. meerk40t/core/elements/elements.py +100 -9
  16. meerk40t/core/elements/shapes.py +259 -72
  17. meerk40t/core/elements/tree_commands.py +10 -5
  18. meerk40t/core/node/blobnode.py +19 -4
  19. meerk40t/core/node/elem_ellipse.py +18 -8
  20. meerk40t/core/node/elem_image.py +51 -19
  21. meerk40t/core/node/elem_line.py +18 -8
  22. meerk40t/core/node/elem_path.py +18 -8
  23. meerk40t/core/node/elem_point.py +10 -4
  24. meerk40t/core/node/elem_polyline.py +19 -11
  25. meerk40t/core/node/elem_rect.py +18 -8
  26. meerk40t/core/node/elem_text.py +11 -5
  27. meerk40t/core/node/filenode.py +2 -8
  28. meerk40t/core/node/groupnode.py +11 -11
  29. meerk40t/core/node/image_processed.py +11 -5
  30. meerk40t/core/node/image_raster.py +11 -5
  31. meerk40t/core/node/node.py +64 -16
  32. meerk40t/core/node/refnode.py +2 -1
  33. meerk40t/core/planner.py +25 -11
  34. meerk40t/core/svg_io.py +91 -34
  35. meerk40t/device/dummydevice.py +7 -1
  36. meerk40t/extra/vtracer.py +222 -0
  37. meerk40t/grbl/device.py +96 -9
  38. meerk40t/grbl/driver.py +15 -5
  39. meerk40t/gui/about.py +20 -0
  40. meerk40t/gui/devicepanel.py +20 -16
  41. meerk40t/gui/gui_mixins.py +4 -0
  42. meerk40t/gui/icons.py +330 -253
  43. meerk40t/gui/laserpanel.py +27 -3
  44. meerk40t/gui/laserrender.py +41 -21
  45. meerk40t/gui/magnetoptions.py +158 -65
  46. meerk40t/gui/materialtest.py +569 -310
  47. meerk40t/gui/navigationpanels.py +229 -24
  48. meerk40t/gui/propertypanels/hatchproperty.py +2 -0
  49. meerk40t/gui/propertypanels/imageproperty.py +160 -106
  50. meerk40t/gui/propertypanels/wobbleproperty.py +6 -2
  51. meerk40t/gui/ribbon.py +6 -1
  52. meerk40t/gui/scenewidgets/gridwidget.py +29 -32
  53. meerk40t/gui/scenewidgets/rectselectwidget.py +190 -192
  54. meerk40t/gui/simulation.py +75 -77
  55. meerk40t/gui/spoolerpanel.py +27 -7
  56. meerk40t/gui/statusbarwidgets/defaultoperations.py +84 -48
  57. meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
  58. meerk40t/gui/tips.py +15 -1
  59. meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
  60. meerk40t/gui/wxmmain.py +242 -114
  61. meerk40t/gui/wxmscene.py +107 -24
  62. meerk40t/gui/wxmtree.py +4 -2
  63. meerk40t/gui/wxutils.py +286 -15
  64. meerk40t/image/imagetools.py +129 -65
  65. meerk40t/internal_plugins.py +4 -0
  66. meerk40t/kernel/kernel.py +67 -18
  67. meerk40t/kernel/settings.py +28 -9
  68. meerk40t/lihuiyu/device.py +24 -12
  69. meerk40t/main.py +14 -9
  70. meerk40t/moshi/device.py +20 -6
  71. meerk40t/network/console_server.py +22 -6
  72. meerk40t/newly/device.py +10 -3
  73. meerk40t/newly/gui/gui.py +10 -0
  74. meerk40t/ruida/device.py +22 -2
  75. meerk40t/ruida/loader.py +9 -4
  76. meerk40t/ruida/rdjob.py +48 -8
  77. meerk40t/tools/geomstr.py +240 -123
  78. meerk40t/tools/rasterplotter.py +185 -94
  79. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/METADATA +1 -1
  80. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/RECORD +85 -84
  81. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/LICENSE +0 -0
  82. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/WHEEL +0 -0
  83. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/entry_points.txt +0 -0
  84. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/top_level.txt +0 -0
  85. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/zip-safe +0 -0
@@ -5,6 +5,7 @@ import wx
5
5
  from wx import aui
6
6
 
7
7
  from meerk40t.core.node.effect_hatch import HatchEffectNode
8
+ from meerk40t.core.node.effect_wobble import WobbleEffectNode
8
9
  from meerk40t.core.node.op_cut import CutOpNode
9
10
  from meerk40t.core.node.op_engrave import EngraveOpNode
10
11
  from meerk40t.core.node.op_image import ImageOpNode
@@ -18,6 +19,7 @@ from meerk40t.gui.wxutils import (
18
19
  dip_size,
19
20
  wxButton,
20
21
  wxCheckBox,
22
+ wxCheckListBox,
21
23
  wxComboBox,
22
24
  wxListBox,
23
25
  wxStaticText,
@@ -184,21 +186,45 @@ class TemplatePanel(wx.Panel):
184
186
  self.storage = storage
185
187
  self.callback = None
186
188
  self.current_op = None
187
- opchoices = [_("Cut"), _("Engrave"), _("Raster"), _("Image"), _("Hatch")]
189
+ opchoices = [
190
+ _("Cut"),
191
+ _("Engrave"),
192
+ _("Raster"),
193
+ _("Image"),
194
+ _("Hatch"),
195
+ _("Wobble"),
196
+ ]
188
197
  # Setup 5 Op nodes - they aren't saved yet
189
198
  self.default_op = []
199
+ self.secondary_default_op = []
190
200
  # A tuple defining whether a free color-selection scheme is allowed, linked to default_op
191
201
  self.color_scheme_free = []
192
202
  self.default_op.append(CutOpNode())
203
+ self.secondary_default_op.append(None)
193
204
  self.color_scheme_free.append(True)
205
+
194
206
  self.default_op.append(EngraveOpNode())
195
207
  self.color_scheme_free.append(True)
208
+ self.secondary_default_op.append(None)
209
+
196
210
  self.default_op.append(RasterOpNode())
197
211
  self.color_scheme_free.append(False)
212
+ self.secondary_default_op.append(None)
213
+
198
214
  self.default_op.append(ImageOpNode())
199
215
  self.color_scheme_free.append(True)
216
+ self.secondary_default_op.append(None)
217
+
218
+ # Hatch = Engrave
200
219
  op = EngraveOpNode()
201
220
  self.default_op.append(op)
221
+ self.secondary_default_op.append(HatchEffectNode())
222
+ self.color_scheme_free.append(True)
223
+
224
+ # Wobble = Cut
225
+ op = CutOpNode()
226
+ self.default_op.append(op)
227
+ self.secondary_default_op.append(WobbleEffectNode())
202
228
  self.color_scheme_free.append(True)
203
229
 
204
230
  self.use_image = [False] * len(self.default_op)
@@ -257,6 +283,10 @@ class TemplatePanel(wx.Panel):
257
283
  self.text_dim_1.set_range(0, 50)
258
284
  self.text_delta_1 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
259
285
  self.text_delta_1.set_range(0, 50)
286
+ self.list_options_1 = wxCheckListBox(
287
+ self, wx.ID_ANY, label=_("Pick values"), majorDimension=3
288
+ )
289
+
260
290
  self.unit_param_1a = wxStaticText(self, wx.ID_ANY, "")
261
291
  self.unit_param_1b = wxStaticText(self, wx.ID_ANY, "")
262
292
 
@@ -276,6 +306,9 @@ class TemplatePanel(wx.Panel):
276
306
  self.text_max_2 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
277
307
  self.text_dim_2 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
278
308
  self.text_dim_2.set_range(0, 50)
309
+ self.list_options_2 = wxCheckListBox(
310
+ self, wx.ID_ANY, label=_("Pick values"), majorDimension=3
311
+ )
279
312
  self.text_delta_2 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
280
313
  self.text_delta_2.set_range(0, 50)
281
314
  self.unit_param_2a = wxStaticText(self, wx.ID_ANY, "")
@@ -318,7 +351,7 @@ class TemplatePanel(wx.Panel):
318
351
  sizer_param_optype.Add(sizer_param_check, 1, wx.EXPAND, 0)
319
352
 
320
353
  sizer_param_xy = wx.BoxSizer(wx.HORIZONTAL)
321
- sizer_param_x = StaticBoxSizer(
354
+ self.sizer_param_x = StaticBoxSizer(
322
355
  self, wx.ID_ANY, _("First parameter (X-Axis)"), wx.VERTICAL
323
356
  )
324
357
 
@@ -327,7 +360,7 @@ class TemplatePanel(wx.Panel):
327
360
  size_it(mylbl, LABEL_WIDTH)
328
361
  hline_param_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
329
362
  hline_param_1.Add(self.combo_param_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
330
-
363
+ self.min_max_container_1 = wx.BoxSizer(wx.VERTICAL)
331
364
  hline_count_1 = wx.BoxSizer(wx.HORIZONTAL)
332
365
  mylbl = wxStaticText(self, wx.ID_ANY, _("Count:"))
333
366
  size_it(mylbl, LABEL_WIDTH)
@@ -350,6 +383,9 @@ class TemplatePanel(wx.Panel):
350
383
  hline_max_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
351
384
  hline_max_1.Add(self.text_max_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
352
385
  hline_max_1.Add(self.unit_param_1b, 0, wx.ALIGN_CENTER_VERTICAL, 0)
386
+ self.min_max_container_1.Add(hline_count_1, 0, wx.EXPAND, 0)
387
+ self.min_max_container_1.Add(hline_min_1, 0, wx.EXPAND, 0)
388
+ self.min_max_container_1.Add(hline_max_1, 0, wx.EXPAND, 0)
353
389
 
354
390
  hline_dim_1 = wx.BoxSizer(wx.HORIZONTAL)
355
391
  mylbl = wxStaticText(self, wx.ID_ANY, _("Width:"))
@@ -374,15 +410,14 @@ class TemplatePanel(wx.Panel):
374
410
  hline_color_1.Add(self.combo_color_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
375
411
  hline_color_1.Add(self.check_color_direction_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
376
412
 
377
- sizer_param_x.Add(hline_param_1, 0, wx.EXPAND, 0)
378
- sizer_param_x.Add(hline_count_1, 0, wx.EXPAND, 0)
379
- sizer_param_x.Add(hline_min_1, 0, wx.EXPAND, 0)
380
- sizer_param_x.Add(hline_max_1, 0, wx.EXPAND, 0)
381
- sizer_param_x.Add(hline_dim_1, 0, wx.EXPAND, 0)
382
- sizer_param_x.Add(hline_delta_1, 0, wx.EXPAND, 0)
383
- sizer_param_x.Add(hline_color_1, 0, wx.EXPAND, 0)
413
+ self.sizer_param_x.Add(hline_param_1, 0, wx.EXPAND, 0)
414
+ self.sizer_param_x.Add(self.min_max_container_1, 0, wx.EXPAND, 0)
415
+ self.sizer_param_x.Add(self.list_options_1, 0, wx.EXPAND, 0)
416
+ self.sizer_param_x.Add(hline_dim_1, 0, wx.EXPAND, 0)
417
+ self.sizer_param_x.Add(hline_delta_1, 0, wx.EXPAND, 0)
418
+ self.sizer_param_x.Add(hline_color_1, 0, wx.EXPAND, 0)
384
419
 
385
- sizer_param_y = StaticBoxSizer(
420
+ self.sizer_param_y = StaticBoxSizer(
386
421
  self, wx.ID_ANY, _("Second parameter (Y-Axis)"), wx.VERTICAL
387
422
  )
388
423
 
@@ -392,6 +427,7 @@ class TemplatePanel(wx.Panel):
392
427
  hline_param_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
393
428
  hline_param_2.Add(self.combo_param_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
394
429
 
430
+ self.min_max_container_2 = wx.BoxSizer(wx.VERTICAL)
395
431
  hline_count_2 = wx.BoxSizer(wx.HORIZONTAL)
396
432
  mylbl = wxStaticText(self, wx.ID_ANY, _("Count:"))
397
433
  size_it(mylbl, LABEL_WIDTH)
@@ -414,6 +450,10 @@ class TemplatePanel(wx.Panel):
414
450
  hline_max_2.Add(self.text_max_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
415
451
  hline_max_2.Add(self.unit_param_2b, 0, wx.ALIGN_CENTER_VERTICAL, 0)
416
452
 
453
+ self.min_max_container_2.Add(hline_count_2, 0, wx.EXPAND, 0)
454
+ self.min_max_container_2.Add(hline_min_2, 0, wx.EXPAND, 0)
455
+ self.min_max_container_2.Add(hline_max_2, 0, wx.EXPAND, 0)
456
+
417
457
  hline_dim_2 = wx.BoxSizer(wx.HORIZONTAL)
418
458
  mylbl = wxStaticText(self, wx.ID_ANY, _("Height:"))
419
459
  size_it(mylbl, LABEL_WIDTH)
@@ -437,16 +477,15 @@ class TemplatePanel(wx.Panel):
437
477
  hline_color_2.Add(self.combo_color_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
438
478
  hline_color_2.Add(self.check_color_direction_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
439
479
 
440
- sizer_param_y.Add(hline_param_2, 0, wx.EXPAND, 0)
441
- sizer_param_y.Add(hline_count_2, 0, wx.EXPAND, 0)
442
- sizer_param_y.Add(hline_min_2, 0, wx.EXPAND, 0)
443
- sizer_param_y.Add(hline_max_2, 0, wx.EXPAND, 0)
444
- sizer_param_y.Add(hline_dim_2, 0, wx.EXPAND, 0)
445
- sizer_param_y.Add(hline_delta_2, 0, wx.EXPAND, 0)
446
- sizer_param_y.Add(hline_color_2, 0, wx.EXPAND, 0)
480
+ self.sizer_param_y.Add(hline_param_2, 0, wx.EXPAND, 0)
481
+ self.sizer_param_y.Add(self.min_max_container_2, 0, wx.EXPAND, 0)
482
+ self.sizer_param_y.Add(self.list_options_2, 0, wx.EXPAND, 0)
483
+ self.sizer_param_y.Add(hline_dim_2, 0, wx.EXPAND, 0)
484
+ self.sizer_param_y.Add(hline_delta_2, 0, wx.EXPAND, 0)
485
+ self.sizer_param_y.Add(hline_color_2, 0, wx.EXPAND, 0)
447
486
 
448
- sizer_param_xy.Add(sizer_param_x, 1, wx.EXPAND, 0)
449
- sizer_param_xy.Add(sizer_param_y, 1, wx.EXPAND, 0)
487
+ sizer_param_xy.Add(self.sizer_param_x, 1, wx.EXPAND, 0)
488
+ sizer_param_xy.Add(self.sizer_param_y, 1, wx.EXPAND, 0)
450
489
 
451
490
  sizer_main.Add(sizer_param_optype, 0, wx.EXPAND, 0)
452
491
  sizer_main.Add(sizer_param_xy, 0, wx.EXPAND, 0)
@@ -556,6 +595,8 @@ class TemplatePanel(wx.Panel):
556
595
  self.combo_images.Bind(wx.EVT_COMBOBOX, self.on_combo_image)
557
596
  self.spin_count_1.Bind(wx.EVT_SPINCTRL, self.validate_input)
558
597
  self.spin_count_2.Bind(wx.EVT_SPINCTRL, self.validate_input)
598
+ self.Bind(wx.EVT_CHECKLISTBOX, self.validate_input, self.list_options_1)
599
+ self.Bind(wx.EVT_CHECKLISTBOX, self.validate_input, self.list_options_2)
559
600
 
560
601
  self.SetSizer(sizer_main)
561
602
  self.Layout()
@@ -577,6 +618,7 @@ class TemplatePanel(wx.Panel):
577
618
  return result
578
619
 
579
620
  def on_combo_image(self, event):
621
+ self.validate_input(event)
580
622
  op = self.combo_ops.GetSelection()
581
623
  if op != 3: # No Image?
582
624
  return
@@ -589,11 +631,19 @@ class TemplatePanel(wx.Panel):
589
631
  self.text_dim_1.SetValue(f"{wd.mm:.1f}")
590
632
  self.text_dim_2.SetValue(f"{ht.mm:.1f}")
591
633
 
634
+ def on_selection_list(self, event):
635
+ return
636
+
592
637
  def set_callback(self, routine):
593
638
  self.callback = routine
594
639
  idx = self.combo_ops.GetSelection()
640
+ opnode = None
641
+ secondary_node = None
642
+ if idx >= 0:
643
+ opnode = self.default_op[idx]
644
+ secondary_node = self.secondary_default_op[idx]
595
645
  if self.callback is not None and idx >= 0:
596
- self.callback(self.default_op[idx])
646
+ self.callback(opnode, secondary_node)
597
647
 
598
648
  def use_percent(self):
599
649
  self.context.device.setting(bool, "use_percent_for_power_display", False)
@@ -653,25 +703,28 @@ class TemplatePanel(wx.Panel):
653
703
  self.Freeze()
654
704
  if opidx < 0:
655
705
  opnode = None
706
+ secondary_node = None
656
707
  self._freecolor = True
657
708
  self.combo_images.Show(False)
658
709
  self.text_dim_1.Enable(True)
659
710
  self.text_dim_2.Enable(True)
660
711
  else:
661
712
  opnode = self.default_op[opidx]
713
+ secondary_node = self.secondary_default_op[opidx]
662
714
  self._freecolor = self.color_scheme_free[opidx]
663
715
  self.combo_images.Show(self.use_image[opidx])
664
716
  self.text_dim_1.Enable(not self.use_image[opidx])
665
717
  self.text_dim_2.Enable(not self.use_image[opidx])
718
+
666
719
  self.sizer_param_op.Layout()
667
720
  if self.callback is not None:
668
- self.callback(opnode)
721
+ self.callback(opnode, secondary_node)
669
722
  self.combo_color_1.Enable(self._freecolor)
670
723
  self.combo_color_2.Enable(self._freecolor)
671
724
  self.check_color_direction_1.Enable(self._freecolor)
672
725
  self.check_color_direction_2.Enable(self._freecolor)
673
726
 
674
- # (internal_attribute, secondary_attribute, Label, unit, keep_unit, needs_to_be_positive)
727
+ # (internal_attribute, secondary_attribute, Label, unit, keep_unit, needs_to_be_positive, choices)
675
728
  if self.use_percent():
676
729
  ppi = "%"
677
730
  else:
@@ -681,64 +734,159 @@ class TemplatePanel(wx.Panel):
681
734
  else:
682
735
  speed_unit = "mm/s"
683
736
  self.parameters = [
684
- ("speed", None, _("Speed"), speed_unit, False, True),
685
- ("power", None, _("Power"), ppi, False, True),
686
- ("passes", preset_passes, _("Passes"), "x", False, True),
737
+ ("speed", None, _("Speed"), speed_unit, False, True, None),
738
+ ("power", None, _("Power"), ppi, False, True, None),
739
+ ("passes", preset_passes, _("Passes"), "x", False, True, None),
687
740
  ]
688
741
 
689
742
  if opidx == 0:
690
743
  # Cut
691
744
  # (internal_attribute, secondary_attribute, Label, unit, keep_unit, needs_to_be_positive, type)
692
745
  self.parameters = [
693
- ("speed", None, _("Speed"), speed_unit, False, True, None),
694
- ("power", None, _("Power"), ppi, False, True, None),
695
- ("passes", preset_passes, _("Passes"), "x", False, True, int),
746
+ ("speed", None, _("Speed"), speed_unit, False, True, None, None),
747
+ ("power", None, _("Power"), ppi, False, True, None, None),
748
+ ("passes", preset_passes, _("Passes"), "x", False, True, int, None),
696
749
  ]
697
750
  elif opidx == 1:
698
751
  # Engrave
699
752
  self.parameters = [
700
- ("speed", None, _("Speed"), speed_unit, False, True, None),
701
- ("power", None, _("Power"), ppi, False, True, None),
702
- ("passes", preset_passes, _("Passes"), "x", False, True, int),
753
+ ("speed", None, _("Speed"), speed_unit, False, True, None, None),
754
+ ("power", None, _("Power"), ppi, False, True, None, None),
755
+ ("passes", preset_passes, _("Passes"), "x", False, True, int, None),
703
756
  ]
704
757
  elif opidx == 2:
705
758
  # Raster
706
759
  self.parameters = [
707
- ("speed", None, _("Speed"), speed_unit, False, True, None),
708
- ("power", None, _("Power"), ppi, False, True, None),
709
- ("passes", preset_passes, _("Passes"), "x", False, True, int),
710
- ("dpi", None, _("DPI"), "dpi", False, True, int),
711
- ("overscan", None, _("Overscan"), "mm", False, True, None),
760
+ ("speed", None, _("Speed"), speed_unit, False, True, None, None),
761
+ ("power", None, _("Power"), ppi, False, True, None, None),
762
+ ("passes", preset_passes, _("Passes"), "x", False, True, int, None),
763
+ ("dpi", None, _("DPI"), "dpi", False, True, int, None),
764
+ ("overscan", None, _("Overscan"), "mm", False, True, None, None),
712
765
  ]
713
766
  elif opidx == 3:
714
767
  # Image
715
768
  self.parameters = [
716
- ("speed", None, _("Speed"), speed_unit, False, True, None),
717
- ("power", None, _("Power"), ppi, False, True, None),
718
- ("passes", preset_passes, _("Passes"), "x", False, True, int),
719
- ("dpi", preset_image_dpi, _("DPI"), "dpi", False, True, int),
720
- ("overscan", None, _("Overscan"), "mm", False, True, None),
769
+ ("speed", None, _("Speed"), speed_unit, False, True, None, None),
770
+ ("power", None, _("Power"), ppi, False, True, None, None),
771
+ ("passes", preset_passes, _("Passes"), "x", False, True, int, None),
772
+ ("dpi", preset_image_dpi, _("DPI"), "dpi", False, True, int, None),
773
+ ("overscan", None, _("Overscan"), "mm", False, True, None, None),
721
774
  ]
722
775
  elif opidx == 4:
723
776
  # Hatch
724
777
  self.parameters = [
725
- ("speed", None, _("Speed"), speed_unit, False, True, None),
726
- ("power", None, _("Power"), ppi, False, True, None),
727
- ("passes", preset_passes, _("Passes"), "x", False, True, int),
728
- ("hatch_distance", None, _("Hatch Distance"), "mm", False, True, None),
729
- ("hatch_angle", None, _("Hatch Angle"), "deg", False, True, None),
778
+ ("speed", None, _("Speed"), speed_unit, False, True, None, None),
779
+ ("power", None, _("Power"), ppi, False, True, None, None),
780
+ ("passes", preset_passes, _("Passes"), "x", False, True, int, None),
781
+ (
782
+ "hatch_distance",
783
+ None,
784
+ _("Hatch Distance"),
785
+ "mm",
786
+ False,
787
+ True,
788
+ None,
789
+ None,
790
+ ),
791
+ ("hatch_angle", None, _("Hatch Angle"), "deg", False, True, None, None),
792
+ ]
793
+ elif opidx == 5:
794
+ # Wobble
795
+ # (internal_attribute, secondary_attribute, Label, unit, keep_unit, needs_to_be_positive, type)
796
+ wobble_choices = list(self.context.match("wobble", suffix=True))
797
+ self.parameters = [
798
+ ("speed", None, _("Speed"), speed_unit, False, True, None, None),
799
+ ("power", None, _("Power"), ppi, False, True, None, None),
800
+ ("passes", preset_passes, _("Passes"), "x", False, True, int, None),
801
+ # wobble_radius
802
+ (
803
+ "wobble_radius",
804
+ preset_balor_wobble,
805
+ _("Wobble Radius"),
806
+ "mm",
807
+ True,
808
+ True,
809
+ None,
810
+ None,
811
+ ),
812
+ (
813
+ "wobble_interval",
814
+ preset_balor_wobble,
815
+ _("Wobble Interval"),
816
+ "mm",
817
+ True,
818
+ True,
819
+ None,
820
+ None,
821
+ ),
822
+ (
823
+ "wobble_speed",
824
+ preset_balor_wobble,
825
+ _("Wobble Speed Multiplier"),
826
+ "x",
827
+ False,
828
+ True,
829
+ None,
830
+ None,
831
+ ),
730
832
  ]
833
+ if wobble_choices:
834
+ self.parameters.append(
835
+ (
836
+ "wobble_type",
837
+ preset_balor_wobble,
838
+ _("Wobble Type"),
839
+ "",
840
+ True,
841
+ True,
842
+ None,
843
+ wobble_choices,
844
+ ),
845
+ )
731
846
 
732
847
  if "balor" in self.context.device.path:
733
848
  balor_choices = [
734
- ("frequency", None, _("Frequency"), "kHz", False, True, None),
735
- ("rapid_speed", preset_balor_rapid, _("Rapid Speed"), "mm/s", False, True, None,),
736
- ("delay_laser_on", preset_balor_timings, _("Laser On Delay"), "µs", False, False, None,),
737
- ("delay_laser_off", preset_balor_timings, _("Laser Off Delay"), "µs", False, False, None,),
738
- ("delay_polygon", preset_balor_timings, _("Polygon Delay"), "µs", False, False, None,),
739
- ("wobble_radius", preset_balor_wobble, _("Wobble Radius"), "mm", True, True, None,),
740
- ("wobble_interval", preset_balor_wobble, _("Wobble Interval"), "mm", True, True, None,),
741
- ("wobble_speed", preset_balor_wobble, _("Wobble Speed Multiplier"), "x", False, True, None,),
849
+ ("frequency", None, _("Frequency"), "kHz", False, True, None, None),
850
+ (
851
+ "rapid_speed",
852
+ preset_balor_rapid,
853
+ _("Rapid Speed"),
854
+ "mm/s",
855
+ False,
856
+ True,
857
+ None,
858
+ None,
859
+ ),
860
+ (
861
+ "delay_laser_on",
862
+ preset_balor_timings,
863
+ _("Laser On Delay"),
864
+ "µs",
865
+ False,
866
+ False,
867
+ None,
868
+ None,
869
+ ),
870
+ (
871
+ "delay_laser_off",
872
+ preset_balor_timings,
873
+ _("Laser Off Delay"),
874
+ "µs",
875
+ False,
876
+ False,
877
+ None,
878
+ None,
879
+ ),
880
+ (
881
+ "delay_polygon",
882
+ preset_balor_timings,
883
+ _("Polygon Delay"),
884
+ "µs",
885
+ False,
886
+ False,
887
+ None,
888
+ None,
889
+ ),
742
890
  ]
743
891
  if self.context.device.pulse_width_enabled:
744
892
  balor_choices.append(
@@ -750,11 +898,11 @@ class TemplatePanel(wx.Panel):
750
898
  False,
751
899
  True,
752
900
  None,
901
+ None,
753
902
  )
754
903
  )
755
904
 
756
- for entry in balor_choices:
757
- self.parameters.append(entry)
905
+ self.parameters.extend(balor_choices)
758
906
  # for p in self.parameters:
759
907
  # if len(p) != 7:
760
908
  # print (f"No good: {p}")
@@ -787,11 +935,32 @@ class TemplatePanel(wx.Panel):
787
935
  # 0 = internal_attribute, 1 = secondary_attribute,
788
936
  # 2 = Label, 3 = unit,
789
937
  # 4 = keep_unit, 5 = needs_to_be_positive)
938
+ standard_items = True
939
+ choices = []
790
940
  if 0 <= idx < len(self.parameters):
791
941
  s_unit = self.parameters[idx][3]
792
942
  b_positive = self.parameters[idx][5]
943
+ if self.parameters[idx][7] is not None:
944
+ self.context.template_list1 = "|".join(
945
+ self.list_options_1.GetCheckedStrings()
946
+ )
947
+ standard_items = False
948
+ choices = self.parameters[idx][7]
949
+ self.list_options_1.Set(choices)
950
+ checked_strings = [
951
+ s for s in self.context.template_list1.split("|") if s
952
+ ]
953
+ if not checked_strings:
954
+ checked_strings = choices
955
+ self.list_options_1.SetCheckedStrings(checked_strings)
956
+
793
957
  self.unit_param_1a.SetLabel(s_unit)
794
958
  self.unit_param_1b.SetLabel(s_unit)
959
+ self.min_max_container_1.ShowItems(standard_items)
960
+ self.list_options_1.Show(not standard_items)
961
+ self.sizer_param_x.Layout()
962
+ self.Layout()
963
+
795
964
  # And now enter validation...
796
965
  self.validate_input(None)
797
966
 
@@ -801,10 +970,29 @@ class TemplatePanel(wx.Panel):
801
970
  # 0 = internal_attribute, 1 = secondary_attribute,
802
971
  # 2 = Label, 3 = unit,
803
972
  # 4 = keep_unit, 5 = needs_to_be_positive)
973
+ standard_items = True
974
+ choices = []
804
975
  if 0 <= idx < len(self.parameters):
805
976
  s_unit = self.parameters[idx][3]
977
+ if self.parameters[idx][7] is not None:
978
+ self.context.template_list2 = "|".join(
979
+ self.list_options_2.GetCheckedStrings()
980
+ )
981
+ standard_items = False
982
+ choices = self.parameters[idx][7]
983
+ self.list_options_2.Set(choices)
984
+ checked_strings = [
985
+ s for s in self.context.template_list2.split("|") if s
986
+ ]
987
+ if not checked_strings:
988
+ checked_strings = choices
989
+ self.list_options_2.SetCheckedStrings(checked_strings)
806
990
  self.unit_param_2a.SetLabel(s_unit)
807
991
  self.unit_param_2b.SetLabel(s_unit)
992
+ self.min_max_container_2.ShowItems(standard_items)
993
+ self.list_options_2.Show(not standard_items)
994
+ self.sizer_param_y.Layout()
995
+ self.Layout()
808
996
  # And now enter validation...
809
997
  self.validate_input(None)
810
998
 
@@ -820,83 +1008,97 @@ class TemplatePanel(wx.Panel):
820
1008
  result = False
821
1009
  return result
822
1010
 
823
- active = True
824
- valid_interval_1 = True
825
- valid_interval_2 = True
826
- optype = self.combo_ops.GetSelection()
827
- if optype < 0:
828
- active = False
829
- if (
830
- optype == 3 and self.combo_images.GetSelection() < 1
831
- ): # image and no valid image chosen
832
- active = False
833
- idx1 = self.combo_param_1.GetSelection()
834
- if idx1 < 0:
835
- active = False
836
- idx2 = self.combo_param_2.GetSelection()
837
- if idx2 < 0:
838
- active = False
839
- if idx1 == idx2:
840
- active = False
841
- if not valid_float(self.text_min_1):
842
- active = False
843
- valid_interval_1 = False
844
- if not valid_float(self.text_max_1):
845
- active = False
846
- valid_interval_1 = False
847
- if not valid_float(self.text_min_2):
848
- active = False
849
- valid_interval_2 = False
850
- if not valid_float(self.text_max_2):
851
- active = False
852
- valid_interval_2 = False
853
- if not valid_float(self.text_dim_1):
854
- active = False
855
- if not valid_float(self.text_delta_1):
856
- active = False
857
- if not valid_float(self.text_dim_2):
858
- active = False
859
- if not valid_float(self.text_delta_2):
860
- active = False
861
- if valid_interval_1:
862
- minv = float(self.text_min_1.GetValue())
863
- maxv = float(self.text_max_1.GetValue())
864
- count = self.spin_count_1.GetValue()
865
- delta = maxv - minv
866
- if count > 1:
867
- delta /= count - 1
868
- s_unit = ""
869
- idx = self.combo_param_1.GetSelection()
870
- # 0 = internal_attribute, 1 = secondary_attribute,
871
- # 2 = Label, 3 = unit,
872
- # 4 = keep_unit, 5 = needs_to_be_positive)
873
- if 0 <= idx < len(self.parameters):
874
- s_unit = self.parameters[idx][3]
875
- self.info_delta_1.SetLabel(
876
- _("Every {dist}").format(dist=self.shortened(delta, 3) + s_unit)
877
- )
878
- else:
879
- self.info_delta_1.SetLabel("---")
880
- if valid_interval_2:
881
- minv = float(self.text_min_2.GetValue())
882
- maxv = float(self.text_max_2.GetValue())
883
- count = self.spin_count_2.GetValue()
884
- delta = maxv - minv
885
- if count > 1:
886
- delta /= count - 1
887
- s_unit = ""
888
- idx = self.combo_param_2.GetSelection()
889
- # 0 = internal_attribute, 1 = secondary_attribute,
890
- # 2 = Label, 3 = unit,
891
- # 4 = keep_unit, 5 = needs_to_be_positive)
892
- if 0 <= idx < len(self.parameters):
893
- s_unit = self.parameters[idx][3]
894
- self.info_delta_2.SetLabel(
895
- _("Every {dist}").format(dist=self.shortened(delta, 3) + s_unit)
896
- )
897
- else:
898
- self.info_delta_2.SetLabel("---")
1011
+ def check_for_active():
1012
+ active = True
1013
+ valid_interval_1 = True
1014
+ valid_interval_2 = True
1015
+ optype = self.combo_ops.GetSelection()
1016
+ if optype < 0:
1017
+ return False
1018
+ if (
1019
+ optype == 3 and self.combo_images.GetSelection() < 1
1020
+ ): # image and no valid image chosen
1021
+ return False
1022
+ idx1 = self.combo_param_1.GetSelection()
1023
+ if idx1 < 0:
1024
+ return False
1025
+ idx2 = self.combo_param_2.GetSelection()
1026
+ if idx2 < 0:
1027
+ return False
1028
+ if idx1 == idx2:
1029
+ return False
1030
+ # Proper check for standard / non-standard parameters
1031
+ if self.parameters[idx1][7] is not None:
1032
+ if not self.list_options_1.GetCheckedStrings():
1033
+ active = False
1034
+ valid_interval_1 = True
1035
+ else:
1036
+ if not valid_float(self.text_min_1):
1037
+ active = False
1038
+ valid_interval_1 = False
1039
+ if not valid_float(self.text_max_1):
1040
+ active = False
1041
+ valid_interval_1 = False
1042
+ if self.parameters[idx2][7] is not None:
1043
+ if not self.list_options_2.GetCheckedStrings():
1044
+ active = False
1045
+ valid_interval_2 = True
1046
+ else:
1047
+ if not valid_float(self.text_min_2):
1048
+ active = False
1049
+ valid_interval_2 = False
1050
+ if not valid_float(self.text_max_2):
1051
+ active = False
1052
+ valid_interval_2 = False
1053
+ if not valid_float(self.text_dim_1):
1054
+ active = False
1055
+ if not valid_float(self.text_delta_1):
1056
+ active = False
1057
+ if not valid_float(self.text_dim_2):
1058
+ active = False
1059
+ if not valid_float(self.text_delta_2):
1060
+ active = False
1061
+ if valid_interval_1:
1062
+ minv = float(self.text_min_1.GetValue())
1063
+ maxv = float(self.text_max_1.GetValue())
1064
+ count = self.spin_count_1.GetValue()
1065
+ delta = maxv - minv
1066
+ if count > 1:
1067
+ delta /= count - 1
1068
+ s_unit = ""
1069
+ idx = self.combo_param_1.GetSelection()
1070
+ # 0 = internal_attribute, 1 = secondary_attribute,
1071
+ # 2 = Label, 3 = unit,
1072
+ # 4 = keep_unit, 5 = needs_to_be_positive)
1073
+ if 0 <= idx < len(self.parameters):
1074
+ s_unit = self.parameters[idx][3]
1075
+ self.info_delta_1.SetLabel(
1076
+ _("Every {dist}").format(dist=self.shortened(delta, 3) + s_unit)
1077
+ )
1078
+ else:
1079
+ self.info_delta_1.SetLabel("---")
1080
+ if valid_interval_2:
1081
+ minv = float(self.text_min_2.GetValue())
1082
+ maxv = float(self.text_max_2.GetValue())
1083
+ count = self.spin_count_2.GetValue()
1084
+ delta = maxv - minv
1085
+ if count > 1:
1086
+ delta /= count - 1
1087
+ s_unit = ""
1088
+ idx = self.combo_param_2.GetSelection()
1089
+ # 0 = internal_attribute, 1 = secondary_attribute,
1090
+ # 2 = Label, 3 = unit,
1091
+ # 4 = keep_unit, 5 = needs_to_be_positive)
1092
+ if 0 <= idx < len(self.parameters):
1093
+ s_unit = self.parameters[idx][3]
1094
+ self.info_delta_2.SetLabel(
1095
+ _("Every {dist}").format(dist=self.shortened(delta, 3) + s_unit)
1096
+ )
1097
+ else:
1098
+ self.info_delta_2.SetLabel("---")
1099
+ return active
899
1100
 
1101
+ active = check_for_active()
900
1102
  self.button_create.Enable(active)
901
1103
 
902
1104
  def on_device_update(self):
@@ -950,8 +1152,38 @@ class TemplatePanel(wx.Panel):
950
1152
  self.context.elements.clear_operations(fast=True)
951
1153
  self.context.elements.clear_elements(fast=True)
952
1154
 
953
- def create_operations():
1155
+ def create_operations(range1, range2):
954
1156
  # opchoices = [_("Cut"), _("Engrave"), _("Raster"), _("Image"), _("Hatch")]
1157
+ count_1 = len(range1)
1158
+ count_2 = len(range2)
1159
+ try:
1160
+ dimension_1 = float(self.text_dim_1.GetValue())
1161
+ except ValueError:
1162
+ dimension_1 = -1
1163
+ try:
1164
+ dimension_2 = float(self.text_dim_2.GetValue())
1165
+ except ValueError:
1166
+ dimension_2 = -1
1167
+ if dimension_1 <= 0:
1168
+ dimension_1 = 5
1169
+ if dimension_2 <= 0:
1170
+ dimension_2 = 5
1171
+
1172
+ try:
1173
+ gap_1 = float(self.text_delta_1.GetValue())
1174
+ except ValueError:
1175
+ gap_1 = -1
1176
+ try:
1177
+ gap_2 = float(self.text_delta_2.GetValue())
1178
+ except ValueError:
1179
+ gap_2 = -1
1180
+
1181
+ if gap_1 < 0:
1182
+ gap_1 = 0
1183
+ if gap_2 < 0:
1184
+ gap_2 = 5
1185
+
1186
+ # print (f"Creating operations for {len(range1)} x {len(range2)}")
955
1187
  display_labels = self.check_labels.GetValue()
956
1188
  display_values = self.check_values.GetValue()
957
1189
  color_aspect_1 = max(0, self.combo_color_1.GetSelection())
@@ -959,8 +1191,6 @@ class TemplatePanel(wx.Panel):
959
1191
  color_growing_1 = self.check_color_direction_1.GetValue()
960
1192
  color_growing_2 = self.check_color_direction_2.GetValue()
961
1193
 
962
- if optype < 0 or optype > 4:
963
- return
964
1194
  if optype == 3:
965
1195
  shapetype = "image"
966
1196
  else:
@@ -997,8 +1227,9 @@ class TemplatePanel(wx.Panel):
997
1227
  if display_labels:
998
1228
  text_x = start_x + expected_width / 2
999
1229
  text_y = start_y - min(float(Length("10mm")), 3 * gap_y)
1000
- node = self.context.elements.elem_branch.add(
1001
- text=f"{param_name_1} [{param_unit_1}]",
1230
+ unit_str = f" [{param_unit_1}]" if param_unit_1 else ""
1231
+ node = element_branch.add(
1232
+ text=f"{param_name_1}{unit_str}",
1002
1233
  matrix=Matrix(
1003
1234
  f"translate({text_x}, {text_y}) scale({2 * max(text_scale_x, text_scale_y) * UNITS_PER_PIXEL})"
1004
1235
  ),
@@ -1010,8 +1241,9 @@ class TemplatePanel(wx.Panel):
1010
1241
 
1011
1242
  text_x = start_x - min(float(Length("10mm")), 3 * gap_x)
1012
1243
  text_y = start_y + expected_height / 2
1013
- node = self.context.elements.elem_branch.add(
1014
- text=f"{param_name_2} [{param_unit_2}]",
1244
+ unit_str = f" [{param_unit_2}]" if param_unit_2 else ""
1245
+ node = element_branch.add(
1246
+ text=f"{param_name_2}{unit_str}",
1015
1247
  matrix=Matrix(
1016
1248
  f"translate({text_x}, {text_y}) scale({2 * max(text_scale_x, text_scale_y) * UNITS_PER_PIXEL})"
1017
1249
  ),
@@ -1023,10 +1255,9 @@ class TemplatePanel(wx.Panel):
1023
1255
  node.modified()
1024
1256
  text_op_y.add_reference(node, 0)
1025
1257
 
1026
- _p_value_1 = min_value_1
1027
-
1028
1258
  xx = start_x
1029
- for idx1 in range(count_1):
1259
+ for idx1, _p_value_1 in enumerate(range1):
1260
+ # print (f"Creating row {idx1} of {len(range1)} with value {_p_value_1}")
1030
1261
  p_value_1 = _p_value_1
1031
1262
  if param_value_type_1 is not None:
1032
1263
  try:
@@ -1034,16 +1265,18 @@ class TemplatePanel(wx.Panel):
1034
1265
  p_value_1 = _pp
1035
1266
  except ValueError:
1036
1267
  pass
1037
- pval1 = self.shortened(p_value_1, 3)
1268
+ if isinstance(p_value_1, str):
1269
+ pval1 = p_value_1
1270
+ else:
1271
+ pval1 = self.shortened(p_value_1, 3)
1038
1272
 
1039
- _p_value_2 = min_value_2
1040
1273
  yy = start_y
1041
1274
 
1042
1275
  if display_values:
1043
1276
  # Add a text above for each column
1044
1277
  text_x = xx + 0.5 * size_x
1045
1278
  text_y = yy - min(float(Length("5mm")), 1.5 * gap_y)
1046
- node = self.context.elements.elem_branch.add(
1279
+ node = element_branch.add(
1047
1280
  text=f"{pval1}",
1048
1281
  matrix=Matrix(
1049
1282
  f"translate({text_x}, {text_y}) scale({text_scale_x * UNITS_PER_PIXEL})"
@@ -1056,7 +1289,8 @@ class TemplatePanel(wx.Panel):
1056
1289
  node.modified()
1057
1290
  text_op_x.add_reference(node, 0)
1058
1291
 
1059
- for idx2 in range(count_2):
1292
+ for idx2, _p_value_2 in enumerate(range2):
1293
+ # print (f"Creating column {idx2} of {len(range2)} with value {_p_value_2}")
1060
1294
  p_value_2 = _p_value_2
1061
1295
  if param_value_type_2 is not None:
1062
1296
  try:
@@ -1064,14 +1298,16 @@ class TemplatePanel(wx.Panel):
1064
1298
  p_value_2 = _pp
1065
1299
  except ValueError:
1066
1300
  pass
1067
-
1068
- pval2 = self.shortened(p_value_2, 3)
1301
+ if isinstance(p_value_2, str):
1302
+ pval2 = p_value_2
1303
+ else:
1304
+ pval2 = self.shortened(p_value_2, 3)
1069
1305
  s_lbl = f"{param_type_1}={pval1}{param_unit_1}"
1070
1306
  s_lbl += f"- {param_type_2}={pval2}{param_unit_2}"
1071
1307
  if display_values and idx1 == 0: # first row, so add a text above
1072
1308
  text_x = xx - min(float(Length("5mm")), 1.5 * gap_x)
1073
1309
  text_y = yy + 0.5 * size_y
1074
- node = self.context.elements.elem_branch.add(
1310
+ node = element_branch.add(
1075
1311
  text=f"{pval2}",
1076
1312
  matrix=Matrix(
1077
1313
  f"translate({text_x}, {text_y}) scale({text_scale_y * UNITS_PER_PIXEL})"
@@ -1100,18 +1336,25 @@ class TemplatePanel(wx.Panel):
1100
1336
  usefill = False
1101
1337
  elif optype == 4: # Hatch
1102
1338
  master_op = copy(self.default_op[optype])
1103
- this_op = HatchEffectNode()
1339
+ this_op = copy(self.secondary_default_op[optype])
1104
1340
  master_op.add_node(this_op)
1105
1341
 
1106
1342
  # We need to add a hatch node and make this the target for parameter application
1107
1343
  usefill = False
1344
+ elif optype == 5: # Wobble
1345
+ # Wobble is a special case, we need to create a master op and a secondary op
1346
+ # We need to add a wobble node and make this the target for parameter application
1347
+ master_op = copy(self.default_op[optype])
1348
+ this_op = copy(self.secondary_default_op[optype])
1349
+ master_op.add_node(this_op)
1350
+ usefill = False
1108
1351
  else:
1109
1352
  return
1110
1353
  this_op.label = s_lbl
1111
1354
 
1112
1355
  # Do we need to prep the op?
1113
1356
  if param_prepper_1 is not None:
1114
- param_prepper_1(this_op)
1357
+ param_prepper_1(master_op)
1115
1358
 
1116
1359
  if param_keep_unit_1:
1117
1360
  value = str(p_value_1) + param_unit_1
@@ -1125,9 +1368,10 @@ class TemplatePanel(wx.Panel):
1125
1368
  # quick and dirty
1126
1369
  if param_type_1 == "passes":
1127
1370
  value = int(value)
1128
- if param_type_1 == "hatch_distance":
1129
- if not str(value).endswith("mm"):
1130
- value = f"{value}mm"
1371
+ if param_type_1 == "hatch_distance" and not str(value).endswith(
1372
+ "mm"
1373
+ ):
1374
+ value = f"{value}mm"
1131
1375
  setattr(master_op, param_type_1, value)
1132
1376
  # else: # Try setting
1133
1377
  # master_op.settings[param_type_1] = value
@@ -1135,16 +1379,21 @@ class TemplatePanel(wx.Panel):
1135
1379
  # quick and dirty
1136
1380
  if param_type_1 == "passes":
1137
1381
  value = int(value)
1138
- if param_type_1 == "hatch_distance":
1139
- if not str(value).endswith("mm"):
1140
- value = f"{value}mm"
1382
+ elif param_type_1 == "hatch_distance" and not str(
1383
+ value
1384
+ ).endswith("mm"):
1385
+ value = f"{value}mm"
1386
+ elif param_type_1 == "hatch_angle" and not str(value).endswith(
1387
+ "deg"
1388
+ ):
1389
+ value = f"{value}deg"
1141
1390
  setattr(this_op, param_type_1, value)
1142
1391
  elif hasattr(this_op, "settings"): # Try setting
1143
1392
  this_op.settings[param_type_1] = value
1144
1393
 
1145
1394
  # Do we need to prep the op?
1146
1395
  if param_prepper_2 is not None:
1147
- param_prepper_2(this_op)
1396
+ param_prepper_2(master_op)
1148
1397
 
1149
1398
  if param_keep_unit_2:
1150
1399
  value = str(p_value_2) + param_unit_2
@@ -1158,25 +1407,31 @@ class TemplatePanel(wx.Panel):
1158
1407
  # quick and dirty
1159
1408
  if param_type_2 == "passes":
1160
1409
  value = int(value)
1161
- if param_type_2 == "hatch_distance":
1162
- if not str(value).endswith("mm"):
1163
- value = f"{value}mm"
1410
+ if param_type_2 == "hatch_distance" and not str(value).endswith(
1411
+ "mm"
1412
+ ):
1413
+ value = f"{value}mm"
1164
1414
  setattr(master_op, param_type_2, value)
1165
1415
  if hasattr(this_op, param_type_2):
1166
1416
  if param_type_2 == "passes":
1167
1417
  value = int(value)
1168
- if param_type_2 == "hatch_distance":
1169
- if not str(value).endswith("mm"):
1170
- value = f"{value}mm"
1418
+ elif param_type_2 == "hatch_distance" and not str(
1419
+ value
1420
+ ).endswith("mm"):
1421
+ value = f"{value}mm"
1422
+ elif param_type_2 == "hatch_angle" and not str(value).endswith(
1423
+ "deg"
1424
+ ):
1425
+ value = f"{value}deg"
1171
1426
  setattr(this_op, param_type_2, value)
1172
1427
  elif hasattr(this_op, "settings"): # Try setting
1173
1428
  this_op.settings[param_type_2] = value
1174
1429
 
1175
1430
  set_color = make_color(
1176
1431
  idx1,
1177
- count_1,
1432
+ len(range1),
1178
1433
  idx2,
1179
- count_2,
1434
+ len(range2),
1180
1435
  color_aspect_1,
1181
1436
  color_growing_1,
1182
1437
  color_aspect_2,
@@ -1186,19 +1441,17 @@ class TemplatePanel(wx.Panel):
1186
1441
  # Add op to tree.
1187
1442
  operation_branch.add_node(master_op)
1188
1443
  # Now add a rectangle to the scene and assign it to the newly created op
1189
- if usefill:
1190
- fill_color = set_color
1191
- else:
1192
- fill_color = None
1444
+ fill_color = set_color if usefill else None
1445
+ elemnode = None
1193
1446
  if shapetype == "image":
1194
1447
  idx = self.combo_images.GetSelection() - 1
1195
1448
  if 0 <= idx < len(self.images):
1196
1449
  elemnode = copy(self.images[idx])
1197
1450
  elemnode.matrix.post_translate(xx, yy)
1198
1451
  elemnode.modified()
1199
- self.context.elements.elem_branch.add_node(elemnode)
1452
+ element_branch.add_node(elemnode)
1200
1453
  elif shapetype == "rect":
1201
- elemnode = self.context.elements.elem_branch.add(
1454
+ elemnode = element_branch.add(
1202
1455
  x=xx,
1203
1456
  y=yy,
1204
1457
  width=size_x,
@@ -1208,7 +1461,7 @@ class TemplatePanel(wx.Panel):
1208
1461
  type="elem rect",
1209
1462
  )
1210
1463
  elif shapetype == "circle":
1211
- elemnode = self.context.elements.elem_branch.add(
1464
+ elemnode = element_branch.add(
1212
1465
  cx=xx + size_x / 2,
1213
1466
  cy=yy + size_y / 2,
1214
1467
  rx=size_x / 2,
@@ -1217,138 +1470,106 @@ class TemplatePanel(wx.Panel):
1217
1470
  fill=fill_color,
1218
1471
  type="elem ellipse",
1219
1472
  )
1220
- elemnode.label = s_lbl
1221
- this_op.add_reference(elemnode, 0)
1222
- _p_value_2 += delta_2
1473
+ if elemnode is not None:
1474
+ elemnode.label = s_lbl
1475
+ this_op.add_reference(elemnode, 0)
1223
1476
  yy = yy + gap_y + size_y
1224
- _p_value_1 += delta_1
1225
1477
  xx = xx + gap_x + size_x
1226
1478
 
1227
1479
  # Read the parameters and user input
1228
1480
  optype = self.combo_ops.GetSelection()
1229
1481
  if optype < 0:
1230
1482
  return
1231
- idx = self.combo_param_1.GetSelection()
1232
- if idx < 0:
1483
+ idx1 = self.combo_param_1.GetSelection()
1484
+ if idx1 < 0:
1233
1485
  return
1234
1486
  # 0 = internal_attribute, 1 = secondary_attribute,
1235
1487
  # 2 = Label, 3 = unit,
1236
1488
  # 4 = keep_unit, 5 = needs_to_be_positive)
1237
- param_name_1 = self.parameters[idx][2]
1238
- param_type_1 = self.parameters[idx][0]
1239
- param_value_type_1 = self.parameters[idx][6]
1240
- param_prepper_1 = self.parameters[idx][1]
1489
+ param_name_1 = self.parameters[idx1][2]
1490
+ param_type_1 = self.parameters[idx1][0]
1491
+ param_value_type_1 = self.parameters[idx1][6]
1492
+ param_prepper_1 = self.parameters[idx1][1]
1241
1493
  if param_prepper_1 == "":
1242
1494
  param_prepper_1 = None
1243
- param_unit_1 = self.parameters[idx][3]
1244
- param_keep_unit_1 = self.parameters[idx][4]
1245
- param_positive_1 = self.parameters[idx][5]
1495
+ param_unit_1 = self.parameters[idx1][3]
1496
+ param_keep_unit_1 = self.parameters[idx1][4]
1246
1497
 
1247
- idx = self.combo_param_2.GetSelection()
1248
- if idx < 0:
1498
+ idx2 = self.combo_param_2.GetSelection()
1499
+ if idx2 < 0:
1249
1500
  return
1250
- param_name_2 = self.parameters[idx][2]
1251
- param_type_2 = self.parameters[idx][0]
1252
- param_value_type_2 = self.parameters[idx][6]
1253
- param_prepper_2 = self.parameters[idx][1]
1501
+ param_name_2 = self.parameters[idx2][2]
1502
+ param_type_2 = self.parameters[idx2][0]
1503
+ param_value_type_2 = self.parameters[idx2][6]
1504
+ param_prepper_2 = self.parameters[idx2][1]
1254
1505
  if param_prepper_2 == "":
1255
1506
  param_prepper_2 = None
1256
- param_unit_2 = self.parameters[idx][3]
1257
- param_keep_unit_2 = self.parameters[idx][4]
1258
- param_positive_2 = self.parameters[idx][5]
1507
+ param_unit_2 = self.parameters[idx2][3]
1508
+ param_keep_unit_2 = self.parameters[idx2][4]
1259
1509
  if param_type_1 == param_type_2:
1260
1510
  return
1261
- if self.text_min_1.GetValue() == "":
1262
- return
1263
- try:
1264
- min_value_1 = float(self.text_min_1.GetValue())
1265
- except ValueError:
1266
- return
1267
- if self.text_min_2.GetValue() == "":
1268
- return
1269
- try:
1270
- min_value_2 = float(self.text_min_2.GetValue())
1271
- except ValueError:
1272
- return
1273
- if self.text_max_1.GetValue() == "":
1274
- return
1275
- try:
1276
- max_value_1 = float(self.text_max_1.GetValue())
1277
- except ValueError:
1278
- return
1279
- if self.text_max_2.GetValue() == "":
1280
- return
1281
- try:
1282
- max_value_2 = float(self.text_max_2.GetValue())
1283
- except ValueError:
1284
- return
1285
1511
 
1286
- if param_unit_1 == "deg":
1287
- min_value_1 = Angle(self.text_min_1.GetValue()).degrees
1288
- max_value_1 = Angle(self.text_max_1.GetValue()).degrees
1289
- elif param_unit_1 == "ppi":
1290
- min_value_1 = max(min_value_1, 0)
1291
- max_value_1 = min(max_value_1, 1000)
1292
- elif param_unit_1 == "%":
1293
- min_value_1 = max(min_value_1, 0)
1294
- max_value_1 = min(max_value_1, 100)
1295
- else:
1296
- # > 0
1297
- if param_positive_1:
1298
- min_value_1 = max(min_value_1, 0)
1299
- max_value_1 = max(max_value_1, 0)
1300
-
1301
- if param_unit_2 == "deg":
1302
- min_value_2 = Angle(self.text_min_2.GetValue()).degrees
1303
- max_value_2 = Angle(self.text_max_2.GetValue()).degrees
1304
- elif param_unit_2 == "ppi":
1305
- min_value_2 = max(min_value_2, 0)
1306
- max_value_2 = min(max_value_2, 1000)
1307
- elif param_unit_2 == "%":
1308
- min_value_2 = max(min_value_2, 0)
1309
- max_value_2 = min(max_value_2, 100)
1310
- else:
1311
- # > 0
1312
- if param_positive_2:
1313
- min_value_2 = max(min_value_2, 0)
1314
- max_value_2 = max(max_value_2, 0)
1315
-
1316
- count_1 = int(self.spin_count_1.GetValue())
1317
- count_2 = int(self.spin_count_2.GetValue())
1318
- if count_1 > 1:
1319
- delta_1 = (max_value_1 - min_value_1) / (count_1 - 1)
1320
- else:
1321
- delta_1 = 0
1322
- if count_2 > 1:
1323
- delta_2 = (max_value_2 - min_value_2) / (count_2 - 1)
1324
- else:
1325
- delta_2 = 0
1326
- try:
1327
- dimension_1 = float(self.text_dim_1.GetValue())
1328
- except ValueError:
1329
- dimension_1 = -1
1330
- try:
1331
- dimension_2 = float(self.text_dim_2.GetValue())
1332
- except ValueError:
1333
- dimension_2 = -1
1334
- if dimension_1 <= 0:
1335
- dimension_1 = 5
1336
- if dimension_2 <= 0:
1337
- dimension_2 = 5
1512
+ def get_range(isx: bool, idx: int) -> list:
1513
+ value_range = []
1514
+ if idx < 0 or idx >= len(self.parameters):
1515
+ return value_range
1516
+ if self.parameters[idx][7] is not None:
1517
+ # Non-standard parameter, so we need to get the checked strings
1518
+ value_range = (
1519
+ self.list_options_1.GetCheckedStrings()
1520
+ if isx
1521
+ else self.list_options_2.GetCheckedStrings()
1522
+ )
1523
+ if not value_range:
1524
+ return []
1525
+ else:
1526
+ param_unit = self.parameters[idx][3]
1527
+ param_positive = self.parameters[idx][5]
1528
+ if isx:
1529
+ text_min = self.text_min_1.GetValue()
1530
+ text_max = self.text_max_1.GetValue()
1531
+ text_count = self.spin_count_1.GetValue()
1532
+ else:
1533
+ text_min = self.text_min_2.GetValue()
1534
+ text_max = self.text_max_2.GetValue()
1535
+ text_count = self.spin_count_2.GetValue()
1536
+ if text_min == "" or text_max == "" or text_count <= 0:
1537
+ return value_range
1538
+ try:
1539
+ min_value = float(text_min)
1540
+ max_value = float(text_max)
1541
+ count = int(text_count)
1542
+ except ValueError:
1543
+ return value_range
1544
+ if param_unit == "deg":
1545
+ min_value = float(text_min)
1546
+ max_value = float(text_max)
1547
+ elif param_unit == "ppi":
1548
+ min_value = max(min_value, 0)
1549
+ max_value = min(max_value, 1000)
1550
+ elif param_unit == "%":
1551
+ min_value = max(min_value, 0)
1552
+ max_value = min(max_value, 100)
1553
+ else:
1554
+ # > 0
1555
+ if param_positive:
1556
+ min_value = max(min_value, 0)
1557
+ max_value = max(max_value, 0)
1558
+ delta = (max_value - min_value) / (count - 1) if count > 1 else 0
1559
+ if delta == 0:
1560
+ value_range = [min_value]
1561
+ else:
1562
+ value_range = [min_value + i * delta for i in range(count)]
1338
1563
 
1339
- try:
1340
- gap_1 = float(self.text_delta_1.GetValue())
1341
- except ValueError:
1342
- gap_1 = -1
1343
- try:
1344
- gap_2 = float(self.text_delta_2.GetValue())
1345
- except ValueError:
1346
- gap_2 = -1
1564
+ return value_range
1565
+
1566
+ valid_range_1 = get_range(True, idx1)
1567
+ valid_range_2 = get_range(False, idx2)
1568
+ # print (valid_range_1)
1569
+ # print (valid_range_2)
1347
1570
 
1348
- if gap_1 < 0:
1349
- gap_1 = 0
1350
- if gap_2 < 0:
1351
- gap_2 = 5
1571
+ if len(valid_range_1) == 0 or len(valid_range_2) == 0:
1572
+ return
1352
1573
 
1353
1574
  message = _("This will delete all existing operations and elements") + "\n"
1354
1575
  message += (
@@ -1369,7 +1590,7 @@ class TemplatePanel(wx.Panel):
1369
1590
  elif result == wx.ID_CANCEL:
1370
1591
  return
1371
1592
 
1372
- create_operations()
1593
+ create_operations(range1=valid_range_1, range2=valid_range_2)
1373
1594
 
1374
1595
  self.context.signal("rebuild_tree")
1375
1596
  self.context.signal("refresh_scene", "Scene")
@@ -1395,6 +1616,8 @@ class TemplatePanel(wx.Panel):
1395
1616
  self.context.setting(int, "template_color2", 2)
1396
1617
  self.context.setting(bool, "template_coldir1", False)
1397
1618
  self.context.setting(bool, "template_coldir2", False)
1619
+ self.context.setting(str, "template_list1", "")
1620
+ self.context.setting(str, "template_list2", "")
1398
1621
 
1399
1622
  def _set_settings(self, templatename):
1400
1623
  info_field = (
@@ -1417,6 +1640,8 @@ class TemplatePanel(wx.Panel):
1417
1640
  self.context.template_color2,
1418
1641
  self.context.template_coldir1,
1419
1642
  self.context.template_coldir2,
1643
+ self.context.template_list1,
1644
+ self.context.template_list2,
1420
1645
  )
1421
1646
  # print (f"Save data to {templatename}, infofield-len={len(info_field)}")
1422
1647
  key = f"{templatename}"
@@ -1429,20 +1654,27 @@ class TemplatePanel(wx.Panel):
1429
1654
  if (
1430
1655
  info_field is not None
1431
1656
  and isinstance(info_field, (tuple, list))
1432
- and len(info_field) == 19
1657
+ and len(info_field) >= 19
1433
1658
  ):
1659
+
1660
+ def get_setting(idx, default):
1661
+ try:
1662
+ return info_field[idx]
1663
+ except IndexError:
1664
+ return default
1665
+
1434
1666
  # print (f"Load data from {templatename}")
1435
- self.context.template_show_values = info_field[0]
1436
- self.context.template_show_labels = info_field[1]
1667
+ self.context.template_show_values = get_setting(0, True)
1668
+ self.context.template_show_labels = get_setting(1, True)
1437
1669
  self.context.template_optype = info_field[2]
1438
1670
  self.context.template_param1 = info_field[3]
1439
1671
  self.context.template_param2 = info_field[4]
1440
- self.context.template_min1 = info_field[5]
1441
- self.context.template_max1 = info_field[6]
1442
- self.context.template_min2 = info_field[7]
1443
- self.context.template_max2 = info_field[8]
1444
- self.context.template_count1 = info_field[9]
1445
- self.context.template_count2 = info_field[10]
1672
+ self.context.template_min1 = get_setting(5, 0)
1673
+ self.context.template_max1 = get_setting(6, 100)
1674
+ self.context.template_min2 = get_setting(7, 0)
1675
+ self.context.template_max2 = get_setting(8, 100)
1676
+ self.context.template_count1 = get_setting(9, 5)
1677
+ self.context.template_count2 = get_setting(10, 5)
1446
1678
  self.context.template_dim_1 = info_field[11]
1447
1679
  self.context.template_dim_2 = info_field[12]
1448
1680
  self.context.template_gap_1 = info_field[13]
@@ -1451,6 +1683,8 @@ class TemplatePanel(wx.Panel):
1451
1683
  self.context.template_color2 = info_field[16]
1452
1684
  self.context.template_coldir1 = info_field[17]
1453
1685
  self.context.template_coldir2 = info_field[18]
1686
+ self.context.template_list1 = get_setting(19, "")
1687
+ self.context.template_list2 = get_setting(20, "")
1454
1688
 
1455
1689
  def save_settings(self, templatename=None):
1456
1690
  self.context.template_show_values = self.check_values.GetValue()
@@ -1472,6 +1706,8 @@ class TemplatePanel(wx.Panel):
1472
1706
  self.context.template_color2 = self.combo_color_2.GetSelection()
1473
1707
  self.context.template_coldir1 = self.check_color_direction_1.GetValue()
1474
1708
  self.context.template_coldir2 = self.check_color_direction_2.GetValue()
1709
+ self.context.template_list1 = "|".join(self.list_options_1.GetCheckedStrings())
1710
+ self.context.template_list2 = "|".join(self.list_options_2.GetCheckedStrings())
1475
1711
  if templatename:
1476
1712
  # let's try to restore the settings
1477
1713
  self._set_settings(templatename)
@@ -1510,6 +1746,12 @@ class TemplatePanel(wx.Panel):
1510
1746
  self.text_dim_2.SetValue(self.context.template_dim_2)
1511
1747
  self.text_delta_1.SetValue(self.context.template_gap_1)
1512
1748
  self.text_delta_2.SetValue(self.context.template_gap_2)
1749
+ self.list_options_1.SetCheckedStrings(
1750
+ self.context.template_list1.split("|")
1751
+ )
1752
+ self.list_options_2.SetCheckedStrings(
1753
+ self.context.template_list2.split("|")
1754
+ )
1513
1755
  except (AttributeError, ValueError):
1514
1756
  pass
1515
1757
 
@@ -1543,7 +1785,8 @@ class TemplateTool(MWindow):
1543
1785
 
1544
1786
  self.storage = Settings(self.context.kernel.name, "templates.cfg")
1545
1787
  self.storage.read_configuration()
1546
- self.panel_instances = list()
1788
+ self.panel_instances = []
1789
+ self.primary_prop_panels = []
1547
1790
  self.panel_template = TemplatePanel(
1548
1791
  self,
1549
1792
  wx.ID_ANY,
@@ -1614,7 +1857,7 @@ class TemplateTool(MWindow):
1614
1857
 
1615
1858
  return None
1616
1859
 
1617
- def set_node(self, node):
1860
+ def set_node(self, primary_node, secondary_node=None):
1618
1861
  def sort_priority(prop):
1619
1862
  prop_sheet, node = prop
1620
1863
  return (
@@ -1623,25 +1866,26 @@ class TemplateTool(MWindow):
1623
1866
  else 0
1624
1867
  )
1625
1868
 
1626
- if node is None:
1869
+ if primary_node is None:
1627
1870
  return
1628
1871
  busy = wx.BusyCursor()
1629
1872
  self.Freeze()
1630
- pages_to_instance = []
1631
- pages_in_node = []
1632
- found = False
1873
+ primary_panels = []
1874
+ secondary_panels = []
1633
1875
  for property_sheet in self.context.lookup_all(
1634
- f"property/{node.__class__.__name__}/.*"
1876
+ f"property/{primary_node.__class__.__name__}/.*"
1635
1877
  ):
1636
- if not hasattr(property_sheet, "accepts") or property_sheet.accepts(node):
1637
- pages_in_node.append((property_sheet, node))
1638
- found = True
1878
+ if not hasattr(property_sheet, "accepts") or property_sheet.accepts(
1879
+ primary_node
1880
+ ):
1881
+ primary_panels.append((property_sheet, primary_node))
1882
+ found = len(primary_panels) > 0
1639
1883
  # If we did not have any hits and the node is a reference
1640
1884
  # then we fall back to the master. So if in the future we
1641
1885
  # would have a property panel dealing with reference-nodes
1642
1886
  # then this would no longer apply.
1643
- if node.type == "reference" and not found:
1644
- snode = node.node
1887
+ if primary_node.type == "reference" and not found:
1888
+ snode = primary_node.node
1645
1889
  found = False
1646
1890
  for property_sheet in self.context.lookup_all(
1647
1891
  f"property/{snode.__class__.__name__}/.*"
@@ -1649,11 +1893,19 @@ class TemplateTool(MWindow):
1649
1893
  if not hasattr(property_sheet, "accepts") or property_sheet.accepts(
1650
1894
  snode
1651
1895
  ):
1652
- pages_in_node.append((property_sheet, snode))
1653
- found = True
1896
+ primary_panels.append((property_sheet, snode))
1897
+ if secondary_node is not None:
1898
+ for property_sheet in self.context.lookup_all(
1899
+ f"property/{secondary_node.__class__.__name__}/.*"
1900
+ ):
1901
+ if not hasattr(property_sheet, "accepts") or property_sheet.accepts(
1902
+ secondary_node
1903
+ ):
1904
+ secondary_panels.append((property_sheet, secondary_node))
1654
1905
 
1655
- pages_in_node.sort(key=sort_priority, reverse=True)
1656
- pages_to_instance.extend(pages_in_node)
1906
+ primary_panels.sort(key=sort_priority, reverse=True)
1907
+ secondary_panels.sort(key=sort_priority, reverse=True)
1908
+ pages_to_instance = primary_panels + secondary_panels
1657
1909
 
1658
1910
  for p in self.panel_instances:
1659
1911
  try:
@@ -1661,10 +1913,15 @@ class TemplateTool(MWindow):
1661
1913
  except AttributeError:
1662
1914
  pass
1663
1915
  self.remove_module_delegate(p)
1916
+ self.panel_instances.clear()
1664
1917
 
1665
1918
  # Delete all but the first and last page...
1666
1919
  while self.notebook_main.GetPageCount() > 2:
1667
1920
  self.notebook_main.DeletePage(1)
1921
+ # print(
1922
+ # f"Adding {len(pages_to_instance)} pages to the notebook, remaining {self.notebook_main.GetPageCount()} pages: content={self.notebook_main.GetPageText(0)} and {self.notebook_main.GetPageText(1)}"
1923
+ # )
1924
+ # Add the primary property panels
1668
1925
  for prop_sheet, instance in pages_to_instance:
1669
1926
  page_panel = prop_sheet(
1670
1927
  self.notebook_main, wx.ID_ANY, context=self.context, node=instance
@@ -1693,6 +1950,8 @@ class TemplateTool(MWindow):
1693
1950
 
1694
1951
  self.Layout()
1695
1952
  self.Thaw()
1953
+ self.notebook_main.SetSelection(1)
1954
+ self.notebook_main.SetSelection(0)
1696
1955
  del busy
1697
1956
 
1698
1957
  def window_open(self):