meerk40t 0.9.2000__py2.py3-none-any.whl → 0.9.3001__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 (187) hide show
  1. meerk40t/balormk/balor_params.py +1 -43
  2. meerk40t/balormk/controller.py +1 -41
  3. meerk40t/balormk/device.py +16 -22
  4. meerk40t/balormk/driver.py +4 -4
  5. meerk40t/balormk/gui/balorconfig.py +2 -2
  6. meerk40t/balormk/gui/balorcontroller.py +13 -5
  7. meerk40t/balormk/gui/baloroperationproperties.py +0 -46
  8. meerk40t/balormk/gui/gui.py +17 -17
  9. meerk40t/camera/gui/camerapanel.py +18 -11
  10. meerk40t/core/cutcode/rastercut.py +3 -1
  11. meerk40t/core/cutplan.py +145 -14
  12. meerk40t/core/elements/clipboard.py +18 -9
  13. meerk40t/core/elements/element_treeops.py +320 -180
  14. meerk40t/core/elements/element_types.py +7 -2
  15. meerk40t/core/elements/elements.py +53 -27
  16. meerk40t/core/elements/geometry.py +8 -0
  17. meerk40t/core/elements/offset_clpr.py +129 -4
  18. meerk40t/core/elements/offset_mk.py +3 -1
  19. meerk40t/core/elements/shapes.py +28 -25
  20. meerk40t/core/laserjob.py +7 -0
  21. meerk40t/core/node/bootstrap.py +4 -0
  22. meerk40t/core/node/effect_hatch.py +85 -96
  23. meerk40t/core/node/effect_wobble.py +309 -0
  24. meerk40t/core/node/elem_image.py +49 -19
  25. meerk40t/core/node/elem_line.py +60 -0
  26. meerk40t/core/node/elem_rect.py +5 -3
  27. meerk40t/core/node/image_processed.py +766 -0
  28. meerk40t/core/node/image_raster.py +113 -0
  29. meerk40t/core/node/node.py +120 -1
  30. meerk40t/core/node/op_cut.py +2 -8
  31. meerk40t/core/node/op_dots.py +0 -8
  32. meerk40t/core/node/op_engrave.py +2 -18
  33. meerk40t/core/node/op_image.py +22 -35
  34. meerk40t/core/node/op_raster.py +0 -9
  35. meerk40t/core/planner.py +32 -2
  36. meerk40t/core/svg_io.py +699 -461
  37. meerk40t/core/treeop.py +191 -0
  38. meerk40t/core/undos.py +15 -1
  39. meerk40t/core/units.py +14 -4
  40. meerk40t/device/dummydevice.py +3 -2
  41. meerk40t/device/gui/defaultactions.py +43 -55
  42. meerk40t/device/gui/formatterpanel.py +58 -49
  43. meerk40t/device/gui/warningpanel.py +12 -12
  44. meerk40t/device/mixins.py +13 -0
  45. meerk40t/dxf/dxf_io.py +9 -5
  46. meerk40t/extra/ezd.py +28 -26
  47. meerk40t/extra/imageactions.py +300 -308
  48. meerk40t/extra/lbrn.py +19 -2
  49. meerk40t/fill/fills.py +6 -6
  50. meerk40t/fill/patternfill.py +1061 -1061
  51. meerk40t/fill/patterns.py +2 -6
  52. meerk40t/grbl/controller.py +168 -52
  53. meerk40t/grbl/device.py +23 -18
  54. meerk40t/grbl/driver.py +39 -0
  55. meerk40t/grbl/emulator.py +79 -19
  56. meerk40t/grbl/gcodejob.py +10 -0
  57. meerk40t/grbl/gui/grblconfiguration.py +2 -2
  58. meerk40t/grbl/gui/grblcontroller.py +24 -8
  59. meerk40t/grbl/gui/grblhardwareconfig.py +153 -0
  60. meerk40t/grbl/gui/gui.py +17 -14
  61. meerk40t/grbl/mock_connection.py +15 -34
  62. meerk40t/grbl/plugin.py +0 -4
  63. meerk40t/grbl/serial_connection.py +2 -1
  64. meerk40t/gui/about.py +8 -5
  65. meerk40t/gui/alignment.py +10 -6
  66. meerk40t/gui/basicops.py +27 -17
  67. meerk40t/gui/bufferview.py +2 -2
  68. meerk40t/gui/choicepropertypanel.py +101 -13
  69. meerk40t/gui/consolepanel.py +12 -9
  70. meerk40t/gui/devicepanel.py +38 -25
  71. meerk40t/gui/executejob.py +6 -4
  72. meerk40t/gui/help_assets/help_assets.py +13 -10
  73. meerk40t/gui/hersheymanager.py +8 -6
  74. meerk40t/gui/icons.py +1951 -3065
  75. meerk40t/gui/imagesplitter.py +14 -7
  76. meerk40t/gui/keymap.py +3 -3
  77. meerk40t/gui/laserpanel.py +151 -84
  78. meerk40t/gui/laserrender.py +61 -70
  79. meerk40t/gui/lasertoolpanel.py +8 -7
  80. meerk40t/gui/materialtest.py +3 -3
  81. meerk40t/gui/mkdebug.py +254 -1
  82. meerk40t/gui/navigationpanels.py +321 -180
  83. meerk40t/gui/notes.py +3 -3
  84. meerk40t/gui/opassignment.py +12 -12
  85. meerk40t/gui/operation_info.py +13 -13
  86. meerk40t/gui/plugin.py +5 -0
  87. meerk40t/gui/position.py +20 -18
  88. meerk40t/gui/preferences.py +21 -6
  89. meerk40t/gui/propertypanels/attributes.py +70 -22
  90. meerk40t/gui/propertypanels/blobproperty.py +2 -2
  91. meerk40t/gui/propertypanels/consoleproperty.py +2 -2
  92. meerk40t/gui/propertypanels/groupproperties.py +3 -3
  93. meerk40t/gui/propertypanels/hatchproperty.py +11 -18
  94. meerk40t/gui/propertypanels/imageproperty.py +4 -3
  95. meerk40t/gui/propertypanels/opbranchproperties.py +1 -1
  96. meerk40t/gui/propertypanels/pathproperty.py +2 -2
  97. meerk40t/gui/propertypanels/pointproperty.py +2 -2
  98. meerk40t/gui/propertypanels/propertywindow.py +4 -4
  99. meerk40t/gui/propertypanels/textproperty.py +3 -3
  100. meerk40t/gui/propertypanels/wobbleproperty.py +204 -0
  101. meerk40t/gui/ribbon.py +367 -259
  102. meerk40t/gui/scene/scene.py +31 -5
  103. meerk40t/gui/scenewidgets/elementswidget.py +12 -4
  104. meerk40t/gui/scenewidgets/gridwidget.py +2 -2
  105. meerk40t/gui/scenewidgets/laserpathwidget.py +7 -2
  106. meerk40t/gui/scenewidgets/machineoriginwidget.py +6 -2
  107. meerk40t/gui/scenewidgets/relocatewidget.py +1 -1
  108. meerk40t/gui/scenewidgets/reticlewidget.py +9 -0
  109. meerk40t/gui/scenewidgets/selectionwidget.py +12 -7
  110. meerk40t/gui/simpleui.py +95 -8
  111. meerk40t/gui/simulation.py +44 -36
  112. meerk40t/gui/spoolerpanel.py +124 -26
  113. meerk40t/gui/statusbarwidgets/defaultoperations.py +18 -6
  114. meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
  115. meerk40t/gui/statusbarwidgets/opassignwidget.py +12 -12
  116. meerk40t/gui/statusbarwidgets/shapepropwidget.py +45 -18
  117. meerk40t/gui/statusbarwidgets/statusbar.py +11 -4
  118. meerk40t/gui/themes.py +78 -0
  119. meerk40t/gui/toolwidgets/toolcircle.py +2 -1
  120. meerk40t/gui/toolwidgets/toolellipse.py +2 -1
  121. meerk40t/gui/toolwidgets/toolimagecut.py +132 -0
  122. meerk40t/gui/toolwidgets/toolline.py +144 -0
  123. meerk40t/gui/toolwidgets/toolnodeedit.py +72 -145
  124. meerk40t/gui/toolwidgets/toolpoint.py +1 -1
  125. meerk40t/gui/toolwidgets/toolpolygon.py +8 -55
  126. meerk40t/gui/toolwidgets/toolrect.py +2 -1
  127. meerk40t/gui/usbconnect.py +2 -2
  128. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +2 -2
  129. meerk40t/gui/utilitywidgets/harmonograph.py +7 -7
  130. meerk40t/gui/utilitywidgets/scalewidget.py +1 -1
  131. meerk40t/gui/wordlisteditor.py +33 -18
  132. meerk40t/gui/wxmeerk40t.py +166 -66
  133. meerk40t/gui/wxmmain.py +236 -157
  134. meerk40t/gui/wxmribbon.py +49 -25
  135. meerk40t/gui/wxmscene.py +49 -38
  136. meerk40t/gui/wxmtree.py +216 -85
  137. meerk40t/gui/wxutils.py +62 -4
  138. meerk40t/image/imagetools.py +443 -15
  139. meerk40t/internal_plugins.py +2 -10
  140. meerk40t/kernel/kernel.py +12 -4
  141. meerk40t/lihuiyu/controller.py +7 -7
  142. meerk40t/lihuiyu/device.py +3 -1
  143. meerk40t/lihuiyu/driver.py +3 -0
  144. meerk40t/lihuiyu/gui/gui.py +8 -8
  145. meerk40t/lihuiyu/gui/lhyaccelgui.py +2 -2
  146. meerk40t/lihuiyu/gui/lhycontrollergui.py +73 -27
  147. meerk40t/lihuiyu/gui/lhydrivergui.py +2 -2
  148. meerk40t/lihuiyu/gui/tcpcontroller.py +22 -9
  149. meerk40t/main.py +6 -1
  150. meerk40t/moshi/controller.py +5 -5
  151. meerk40t/moshi/device.py +5 -2
  152. meerk40t/moshi/driver.py +4 -0
  153. meerk40t/moshi/gui/gui.py +8 -8
  154. meerk40t/moshi/gui/moshicontrollergui.py +24 -8
  155. meerk40t/moshi/gui/moshidrivergui.py +2 -2
  156. meerk40t/newly/controller.py +2 -0
  157. meerk40t/newly/device.py +9 -2
  158. meerk40t/newly/driver.py +4 -0
  159. meerk40t/newly/gui/gui.py +16 -17
  160. meerk40t/newly/gui/newlyconfig.py +2 -2
  161. meerk40t/newly/gui/newlycontroller.py +13 -5
  162. meerk40t/rotary/gui/gui.py +2 -2
  163. meerk40t/rotary/gui/rotarysettings.py +2 -2
  164. meerk40t/ruida/device.py +3 -0
  165. meerk40t/ruida/driver.py +4 -0
  166. meerk40t/ruida/gui/gui.py +6 -6
  167. meerk40t/ruida/gui/ruidaconfig.py +2 -2
  168. meerk40t/ruida/gui/ruidacontroller.py +13 -5
  169. meerk40t/svgelements.py +9 -9
  170. meerk40t/tools/geomstr.py +849 -153
  171. meerk40t/tools/kerftest.py +8 -4
  172. meerk40t/tools/livinghinges.py +15 -8
  173. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/METADATA +21 -16
  174. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/RECORD +185 -177
  175. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/entry_points.txt +0 -1
  176. test/test_core_elements.py +8 -24
  177. test/test_file_svg.py +88 -0
  178. test/test_fill.py +9 -9
  179. test/test_geomstr.py +258 -8
  180. test/test_kernel.py +4 -0
  181. test/test_tools_rasterplotter.py +29 -0
  182. meerk40t/extra/embroider.py +0 -56
  183. meerk40t/extra/pathoptimize.py +0 -249
  184. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/LICENSE +0 -0
  185. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/WHEEL +0 -0
  186. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/top_level.txt +0 -0
  187. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/zip-safe +0 -0
@@ -13,6 +13,7 @@ non_structural_nodes = (
13
13
  "op engrave",
14
14
  "op dots",
15
15
  "effect hatch",
16
+ "effect wobble",
16
17
  "util console",
17
18
  "util wait",
18
19
  "util home",
@@ -32,6 +33,7 @@ non_structural_nodes = (
32
33
  "elem rect",
33
34
  "elem line",
34
35
  "elem text",
36
+ "image raster",
35
37
  "file",
36
38
  "group",
37
39
  )
@@ -61,7 +63,8 @@ place_nodes = (
61
63
  "place point",
62
64
  "place current",
63
65
  )
64
- effect_nodes = ("effect hatch",)
66
+ effect_nodes = ("effect hatch", "effect wobble")
67
+ image_nodes = ("image raster", "image processed", "elem image")
65
68
  elem_nodes = (
66
69
  "elem ellipse",
67
70
  "elem image",
@@ -71,7 +74,6 @@ elem_nodes = (
71
74
  "elem rect",
72
75
  "elem line",
73
76
  "elem text",
74
- "effect hatch",
75
77
  )
76
78
  elem_group_nodes = (
77
79
  "elem ellipse",
@@ -82,7 +84,9 @@ elem_group_nodes = (
82
84
  "elem rect",
83
85
  "elem line",
84
86
  "elem text",
87
+ "image raster",
85
88
  "effect hatch",
89
+ "effect wobble",
86
90
  "group",
87
91
  "file",
88
92
  )
@@ -96,5 +100,6 @@ elem_ref_nodes = (
96
100
  "elem line",
97
101
  "elem text",
98
102
  "effect hatch",
103
+ "effect wobble",
99
104
  "reference",
100
105
  )
@@ -117,7 +117,11 @@ def plugin(kernel, lifecycle=None):
117
117
  kernel.register("format/elem text", "{element_type} {desc} {text}")
118
118
  kernel.register(
119
119
  "format/effect hatch",
120
- "{effect}{element_type} - {distance} {angle} ({children})",
120
+ "{element_type} - {distance} {angle} ({children})",
121
+ )
122
+ kernel.register(
123
+ "format/effect wobble",
124
+ "{element_type} - {type} {radius} ({children})",
121
125
  )
122
126
  kernel.register("format/reference", "*{reference}")
123
127
  kernel.register(
@@ -494,7 +498,7 @@ class Elemental(Service):
494
498
  self.points = list()
495
499
  self.segments = list()
496
500
 
497
- self.undo = Undo(self._tree)
501
+ self.undo = Undo(self, self._tree)
498
502
  self.do_undo = True
499
503
  self.suppress_updates = False
500
504
 
@@ -522,14 +526,15 @@ class Elemental(Service):
522
526
 
523
527
  direct = os.path.dirname(self.op_data._config_file)
524
528
  self.mywordlist = Wordlist(self.kernel.version, direct)
525
- self.load_persistent_operations("previous")
529
+ with self.undofree():
530
+ self.load_persistent_operations("previous")
526
531
 
527
- ops = list(self.ops())
528
- if len(ops) == 0 and not self.operation_default_empty:
529
- self.load_default(performclassify=False)
530
- if list(self.ops()):
531
- # Something was loaded for default ops. Mark that.
532
- self.undo.mark("op-loaded") # Mark defaulted
532
+ ops = list(self.ops())
533
+ if len(ops) == 0 and not self.operation_default_empty:
534
+ self.load_default(performclassify=False)
535
+ if list(self.ops()):
536
+ # Something was loaded for default ops. Mark that.
537
+ self.undo.mark("op-loaded") # Mark defaulted
533
538
 
534
539
  self._default_stroke = None
535
540
  self._default_strokewidth = None
@@ -677,13 +682,18 @@ class Elemental(Service):
677
682
  # pnode.targeted = True
678
683
  # pnode = pnode.parent
679
684
 
685
+ def unassigned_elements(self):
686
+ for e in self.elems():
687
+ if (e._references is None or len(e._references) == 0) and e.type not in (
688
+ "file",
689
+ "group",
690
+ ):
691
+ yield e
692
+
680
693
  def have_unassigned_elements(self):
681
- unassigned = False
682
- for node in self.elems():
683
- if len(node._references) == 0 and node.type not in ("file", "group"):
684
- unassigned = True
685
- break
686
- return unassigned
694
+ for node in self.unassigned_elements():
695
+ return True
696
+ return False
687
697
 
688
698
  def have_unburnable_elements(self):
689
699
  unassigned = False
@@ -713,17 +723,23 @@ class Elemental(Service):
713
723
  return float(Length(v))
714
724
 
715
725
  def length_x(self, v):
716
- return float(Length(v, relative_length=self.device.view.width))
726
+ try:
727
+ return float(Length(v, relative_length=self.device.view.width))
728
+ except AttributeError:
729
+ return 0.0
717
730
 
718
731
  def length_y(self, v):
719
- return float(Length(v, relative_length=self.device.view.height))
732
+ try:
733
+ return float(Length(v, relative_length=self.device.view.height))
734
+ except AttributeError:
735
+ return 0.0
720
736
 
721
737
  def bounds(self, x0, y0, x1, y1):
722
738
  return (
723
- float(Length(x0, relative_length=self.device.view.width)),
724
- float(Length(y0, relative_length=self.device.view.height)),
725
- float(Length(x1, relative_length=self.device.view.width)),
726
- float(Length(y1, relative_length=self.device.view.height)),
739
+ self.length_x(x0),
740
+ self.length_y(y0),
741
+ self.length_x(x1),
742
+ self.length_y(y1),
727
743
  )
728
744
 
729
745
  def area(self, v):
@@ -1439,6 +1455,7 @@ class Elemental(Service):
1439
1455
  # Restore emphasized flags
1440
1456
  for e in emph_data:
1441
1457
  e.emphasized = True
1458
+ self.signal("element_property_reload", data)
1442
1459
 
1443
1460
  def remove_unused_default_copies(self):
1444
1461
  # Let's clean non-used operations that come from defaults...
@@ -1617,6 +1634,13 @@ class Elemental(Service):
1617
1634
  continue
1618
1635
  yield item
1619
1636
 
1637
+ def op_groups(self, **kwargs):
1638
+ operations = self._tree.get(type="branch ops")
1639
+ for item in operations.flat(**kwargs):
1640
+ if item.type.startswith("branch") or item.type.startswith("ref"):
1641
+ continue
1642
+ yield item
1643
+
1620
1644
  def elems(self, **kwargs):
1621
1645
  elements = self._tree.get(type="branch elems")
1622
1646
  yield from elements.flat(types=elem_nodes, **kwargs)
@@ -1826,7 +1850,7 @@ class Elemental(Service):
1826
1850
  if drop_node.drop(drag_node, modify=False):
1827
1851
  # Is the drag node coming from the regmarks branch?
1828
1852
  # If yes then we might need to classify.
1829
- if drag_node._parent.type == "branch reg":
1853
+ if drag_node.has_ancestor("branch reg"):
1830
1854
  if drag_node.type in ("file", "group"):
1831
1855
  for e in drag_node.flat(elem_nodes):
1832
1856
  to_classify.append(e)
@@ -3471,6 +3495,7 @@ class Elemental(Service):
3471
3495
  # self.unlisten_tree(self)
3472
3496
  elemcount_then = self.count_elems()
3473
3497
  opcount_then = self.count_op()
3498
+ self._loading_cleared = False
3474
3499
  results = loader.load(
3475
3500
  self, self, filename_to_process, **kwargs
3476
3501
  )
@@ -3487,11 +3512,12 @@ class Elemental(Service):
3487
3512
  ):
3488
3513
  return True
3489
3514
  elif results:
3490
- self.signal(
3491
- "warning",
3492
- _("File is Empty"),
3493
- _("File is Malformed"),
3494
- )
3515
+ if not self._loading_cleared:
3516
+ self.signal(
3517
+ "warning",
3518
+ _("File is Empty"),
3519
+ _("File is Malformed"),
3520
+ )
3495
3521
  return True
3496
3522
 
3497
3523
  except FileNotFoundError:
@@ -74,6 +74,14 @@ def init_commands(kernel):
74
74
  path.append(e)
75
75
  return "geometry", path
76
76
 
77
+ @self.console_command("validate", input_type="geometry", output_type="geometry")
78
+ def geometry_validate(channel, _, data=None, **kwargs):
79
+ try:
80
+ data.validate()
81
+ channel(_("Geometry is valid."))
82
+ except AssertionError:
83
+ channel(_("Geometry was not valid."))
84
+
77
85
  @self.console_argument("x_pos", type=Length)
78
86
  @self.console_argument("y_pos", type=Length)
79
87
  @self.console_argument("r_pos", type=Length)
@@ -473,14 +473,27 @@ def init_commands(kernel):
473
473
  pyc_method = pyclipr.ClipType.Xor
474
474
  else:
475
475
  pyc_method = pyclipr.ClipType.Union
476
+ # Definition of enumerators has changed, so let#s try to be backwards compatible
476
477
  if filltype.startswith("no") or filltype.startswith("z"):
477
- pyc_filltype = pyclipr.FillType.NonZero
478
+ try:
479
+ pyc_filltype = pyclipr.FillRule.NonZero
480
+ except AttributeError:
481
+ pyc_filltype = pyclipr.FillType.NonZero
478
482
  elif filltype.startswith("p") or filltype.startswith("+"):
479
- pyc_filltype = pyclipr.FillType.Positive
483
+ try:
484
+ pyc_filltype = pyclipr.FillRule.Positive
485
+ except AttributeError:
486
+ pyc_filltype = pyclipr.FillType.Positive
480
487
  elif filltype.startswith("ne") or filltype.startswith("-"):
481
- pyc_filltype = pyclipr.FillType.Negative
488
+ try:
489
+ pyc_filltype = pyclipr.FillRule.Negative
490
+ except AttributeError:
491
+ pyc_filltype = pyclipr.FillType.Negative
482
492
  else:
483
- pyc_filltype = pyclipr.FillType.EvenOdd
493
+ try:
494
+ pyc_filltype = pyclipr.FillRule.EvenOdd
495
+ except AttributeError:
496
+ pyc_filltype = pyclipr.FillType.EvenOdd
484
497
 
485
498
  if self.any_open and pyc_method in (pyclipr.ClipType.Union,):
486
499
  self.newpath = self.clipr_clipper.execute(
@@ -678,6 +691,118 @@ def init_commands(kernel):
678
691
  self.signal("refresh_scene", "Scene")
679
692
  return "elements", data_out
680
693
 
694
+ # Pocketing
695
+ @self.console_argument(
696
+ "offset",
697
+ type=str,
698
+ help=_(
699
+ "offset to line mm (negative values to left/outside, positive values to right/inside)"
700
+ ),
701
+ )
702
+ @self.console_option(
703
+ "jointype", "j", type=str, help=_("join type: round, miter, square")
704
+ )
705
+ @self.console_option(
706
+ "separate",
707
+ "s",
708
+ action="store_true",
709
+ type=bool,
710
+ help=_("deal with subpaths separately"),
711
+ )
712
+ @self.console_option(
713
+ "repeats",
714
+ "r",
715
+ type=int,
716
+ help=_("amount of repetitions, 0=until area is fully filled"),
717
+ )
718
+ @self.console_option(
719
+ "interpolation", "i", type=int, help=_("interpolation points per segment")
720
+ )
721
+ @self.console_command(
722
+ "pocket",
723
+ help=_("create a pocketing path for any of the given elements"),
724
+ input_type=(None, "elements"),
725
+ output_type="elements",
726
+ )
727
+ def element_pocket_path(
728
+ command,
729
+ channel,
730
+ _,
731
+ offset=None,
732
+ jointype=None,
733
+ separate=None,
734
+ repeats=0,
735
+ interpolation=None,
736
+ data=None,
737
+ post=None,
738
+ **kwargs,
739
+ ):
740
+ if data is None:
741
+ data = list(self.elems(emphasized=True))
742
+ if len(data) == 0:
743
+ channel(_("No elements selected"))
744
+ return "elements", data
745
+ if interpolation is None:
746
+ interpolation = 500
747
+ if separate is None:
748
+ separate = False
749
+ if offset is None:
750
+ offset = 0
751
+ else:
752
+ try:
753
+ ll = Length(offset)
754
+ offset = float(ll)
755
+ except ValueError:
756
+ offset = 0
757
+ if offset == 0.0:
758
+ channel("Invalid offset, nothing to do")
759
+ return
760
+ # Our definition for an offset is different this time
761
+ offset *= -1
762
+ if repeats is None or repeats < 0:
763
+ repeats = 0
764
+ if offset > 0 and repeats == 0:
765
+ channel(
766
+ "You need to provide the -r parameter to set the amount of repetitions"
767
+ )
768
+ return
769
+
770
+ if jointype is None:
771
+ jointype = "miter"
772
+ jointype = jointype.lower()
773
+ default_stroke = None
774
+ for node in data:
775
+ if hasattr(node, "stroke"):
776
+ default_stroke = node.stroke
777
+ break
778
+ if default_stroke is None:
779
+ default_stroke = self._default_stroke
780
+ data_out = []
781
+ rep_count = 0
782
+ mydata = [e for e in data]
783
+ while (rep_count < repeats or repeats == 0) and len(mydata) > 0:
784
+ rep_count += 1
785
+ c_off = ClipperOffset(interpolation=interpolation)
786
+ c_off.add_nodes(mydata)
787
+ c_off.process_data(offset, jointype=jointype, separate=separate)
788
+ mydata.clear()
789
+ for geom in c_off.result_geometry():
790
+ if geom is not None:
791
+ newnode = self.elem_branch.add(
792
+ geometry=geom, type="elem path", stroke=default_stroke
793
+ )
794
+ newnode.stroke_width = UNITS_PER_PIXEL
795
+ newnode.linejoin = Linejoin.JOIN_ROUND
796
+ newnode.label = f"Offset: {Length(offset).length_mm}"
797
+ mydata.append(newnode)
798
+ data_out.append(newnode)
799
+
800
+ # Newly created! Classification needed?
801
+ if len(data_out) > 0:
802
+ post.append(classify_new(data_out))
803
+ self.signal("refresh_scene", "Scene")
804
+ return "elements", data_out
805
+
681
806
  # ---- Let's add some CAG commands....
682
807
  @self.console_argument(
683
808
  "method",
@@ -633,7 +633,8 @@ def path_offset(
633
633
  # print (f"Subpath {spct}")
634
634
  p = Path(subpath)
635
635
  if not linearize:
636
- p.approximate_arcs_with_cubics()
636
+ # p.approximate_arcs_with_cubics()
637
+ pass
637
638
  offset = offset_value
638
639
  # # No offset bigger than half the path size, otherwise stuff will get crazy
639
640
  # if offset > 0:
@@ -829,6 +830,7 @@ def path_offset(
829
830
  result = results[0]
830
831
  for idx in range(1, len(results)):
831
832
  result += results[idx]
833
+ # result.approximate_arcs_with_cubics()
832
834
  return result
833
835
 
834
836
 
@@ -238,37 +238,39 @@ def init_commands(kernel):
238
238
  input_type=(None, "elements"),
239
239
  )
240
240
  def effect_hatch(
241
- command, data=None, angle=None, angle_delta=None, distance=None, **kwargs
241
+ command,
242
+ data=None,
243
+ angle=None,
244
+ angle_delta=None,
245
+ distance=None,
246
+ post=None,
247
+ **kwargs,
242
248
  ):
243
249
  """
244
250
  Add an effect hatch object
245
251
  """
246
- node = self.elem_branch.add(
252
+ if data is None:
253
+ data = list(self.elems(emphasized=True))
254
+ if len(data) == 0:
255
+ return
256
+ first_node = data[0]
257
+
258
+ node = first_node.parent.add(
247
259
  type="effect hatch",
248
260
  label="Hatch Effect",
249
261
  hatch_angle=angle.as_radians,
250
262
  hatch_angle_delta=angle_delta.as_radians,
251
263
  hatch_distance=distance,
252
264
  )
265
+ for n in data:
266
+ node.append_child(n)
267
+
268
+ # Newly created! Classification needed?
269
+ post.append(classify_new([node]))
270
+
253
271
  self.set_emphasis([node])
254
- if data is not None:
255
- for n in data:
256
- node.append_child(n)
257
272
  node.focus()
258
273
 
259
- @self.console_command(
260
- "toggle",
261
- help=_("Toggles effect from being group to an effect."),
262
- input_type="elements",
263
- )
264
- def effect_toggle(command, data=None, **kwargs):
265
- """
266
- Toggles effect hatch object
267
- """
268
- for n in data:
269
- if n.type.startswith("effect "):
270
- n.effect = not n.effect
271
-
272
274
  @self.console_option(
273
275
  "size", "s", type=float, default=16, help=_("font size to for object")
274
276
  )
@@ -732,12 +734,10 @@ def init_commands(kernel):
732
734
  def element_pathd_info(command, channel, _, data, real=True, **kwargs):
733
735
  for node in data:
734
736
  try:
735
- if node.path.transform.is_identity():
736
- channel(
737
- f"{str(node)} (Identity): {node.path.d(transformed=not real)}"
738
- )
739
- else:
740
- channel(f"{str(node)}: {node.path.d(transformed=not real)}")
737
+ g = node.as_geometry()
738
+ path = g.as_path()
739
+ ident = " (Identity)" if node.matrix.is_identity() else ""
740
+ channel(f"{str(node)}{ident}: {path.d(transformed=not real)}")
741
741
  except AttributeError:
742
742
  channel(f"{str(node)}: Invalid")
743
743
 
@@ -2021,7 +2021,10 @@ def init_commands(kernel):
2021
2021
  except AttributeError:
2022
2022
  pass
2023
2023
  if e.type == "elem path":
2024
- e.path.approximate_bezier_with_circular_arcs()
2024
+ g = e.geometry
2025
+ path = g.as_path()
2026
+ path.approximate_bezier_with_circular_arcs()
2027
+ e.geometry = Geomstr.svg(path)
2025
2028
  e.altered()
2026
2029
 
2027
2030
  return "elements", data
meerk40t/core/laserjob.py CHANGED
@@ -183,6 +183,13 @@ class LaserJob:
183
183
  self.runtime += time.time() - self.time_started
184
184
  self._stopped = True
185
185
 
186
+ def stop_after_loop(self):
187
+ self.loops = self.loops_executed + 1
188
+
189
+ def add_another_loop(self):
190
+ if not isinf(self.loops):
191
+ self.loops += 1
192
+
186
193
  def elapsed_time(self):
187
194
  """
188
195
  How long is this job already running...
@@ -4,6 +4,7 @@ from meerk40t.core.node.branch_ops import BranchOperationsNode
4
4
  from meerk40t.core.node.branch_regmark import BranchRegmarkNode
5
5
  from meerk40t.core.node.cutnode import CutNode
6
6
  from meerk40t.core.node.effect_hatch import HatchEffectNode
7
+ from meerk40t.core.node.effect_wobble import WobbleEffectNode
7
8
  from meerk40t.core.node.elem_ellipse import EllipseNode
8
9
  from meerk40t.core.node.elem_image import ImageNode
9
10
  from meerk40t.core.node.elem_line import LineNode
@@ -14,6 +15,7 @@ from meerk40t.core.node.elem_rect import RectNode
14
15
  from meerk40t.core.node.elem_text import TextNode
15
16
  from meerk40t.core.node.filenode import FileNode
16
17
  from meerk40t.core.node.groupnode import GroupNode
18
+ from meerk40t.core.node.image_raster import ImageRasterNode
17
19
  from meerk40t.core.node.layernode import LayerNode
18
20
  from meerk40t.core.node.op_cut import CutOpNode
19
21
  from meerk40t.core.node.op_dots import DotsOpNode
@@ -71,6 +73,7 @@ bootstrap = {
71
73
  "op image": ImageOpNode,
72
74
  "op dots": DotsOpNode,
73
75
  "effect hatch": HatchEffectNode,
76
+ "effect wobble": WobbleEffectNode,
74
77
  "util console": ConsoleOperation,
75
78
  "util wait": WaitOperation,
76
79
  "util home": HomeOperation,
@@ -90,6 +93,7 @@ bootstrap = {
90
93
  "elem polyline": PolylineNode,
91
94
  "elem image": ImageNode,
92
95
  "elem text": TextNode,
96
+ "image raster": ImageRasterNode,
93
97
  "reference": ReferenceNode,
94
98
  "cutcode": CutNode,
95
99
  "branch ops": BranchOperationsNode,