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
@@ -0,0 +1,113 @@
1
+ from copy import copy
2
+
3
+ from meerk40t.core.node.node import Node
4
+ from meerk40t.svgelements import Matrix
5
+
6
+
7
+ class ImageRasterNode(Node):
8
+ """
9
+ ImageRasterNode is a basic image type. Its information is backed by a raster.
10
+ """
11
+
12
+ def __init__(self, **kwargs):
13
+ self.image = None
14
+ self.matrix = None
15
+ super().__init__(type="image raster", **kwargs)
16
+ self.__formatter = "{element_type} {id} {width}x{height}"
17
+ if self.matrix is None:
18
+ self.matrix = Matrix()
19
+ self._can_rotate = False
20
+ self._can_skew = False
21
+
22
+ def __copy__(self):
23
+ nd = self.node_dict
24
+ nd["matrix"] = copy(self.matrix)
25
+ nd["image"] = copy(self.image)
26
+ return ImageRasterNode(**nd)
27
+
28
+ def __repr__(self):
29
+ return f"{self.__class__.__name__}('{self.type}', {str(self.image)}, {str(self._parent)})"
30
+
31
+ def as_image(self):
32
+ return self.image, self.bbox()
33
+
34
+ def preprocess(self, context, matrix, plan):
35
+ """
36
+ Preprocess step during the cut planning stages.
37
+
38
+ We require a context to calculate the correct step values relative to the device
39
+ """
40
+ self.matrix *= matrix
41
+ self.set_dirty_bounds()
42
+
43
+ def bbox(self, transformed=True, with_stroke=False):
44
+ image_width, image_height = self.image.size
45
+ matrix = self.matrix
46
+ x0, y0 = matrix.point_in_matrix_space((0, 0))
47
+ x1, y1 = matrix.point_in_matrix_space((image_width, image_height))
48
+ x2, y2 = matrix.point_in_matrix_space((0, image_height))
49
+ x3, y3 = matrix.point_in_matrix_space((image_width, 0))
50
+ return (
51
+ min(x0, x1, x2, x3),
52
+ min(y0, y1, y2, y3),
53
+ max(x0, x1, x2, x3),
54
+ max(y0, y1, y2, y3),
55
+ )
56
+
57
+ def default_map(self, default_map=None):
58
+ default_map = super().default_map(default_map=default_map)
59
+ default_map.update(self.__dict__)
60
+ image = self.image
61
+ default_map["width"] = image.width
62
+ default_map["height"] = image.height
63
+ default_map["element_type"] = "Image"
64
+ return default_map
65
+
66
+ def drop(self, drag_node, modify=True):
67
+ # Dragging element into element.
68
+ if drag_node.type.startswith("elem"):
69
+ if modify:
70
+ self.insert_sibling(drag_node)
71
+ return True
72
+ elif drag_node.type.startswith("op"):
73
+ # If we drag an operation to this node,
74
+ # then we will reverse the game
75
+ return drag_node.drop(self, modify=modify)
76
+ return False
77
+
78
+ def revalidate_points(self):
79
+ bounds = self.bounds
80
+ if bounds is None:
81
+ return
82
+ if len(self._points) < 9:
83
+ self._points.extend([None] * (9 - len(self._points)))
84
+ self._points[0] = [bounds[0], bounds[1], "bounds top_left"]
85
+ self._points[1] = [bounds[2], bounds[1], "bounds top_right"]
86
+ self._points[2] = [bounds[0], bounds[3], "bounds bottom_left"]
87
+ self._points[3] = [bounds[2], bounds[3], "bounds bottom_right"]
88
+ cx = (bounds[0] + bounds[2]) / 2
89
+ cy = (bounds[1] + bounds[3]) / 2
90
+ self._points[4] = [cx, cy, "bounds center_center"]
91
+ self._points[5] = [cx, bounds[1], "bounds top_center"]
92
+ self._points[6] = [cx, bounds[3], "bounds bottom_center"]
93
+ self._points[7] = [bounds[0], cy, "bounds center_left"]
94
+ self._points[8] = [bounds[2], cy, "bounds center_right"]
95
+
96
+ def update_point(self, index, point):
97
+ return False
98
+
99
+ def add_point(self, point, index=None):
100
+ return False
101
+
102
+ @property
103
+ def opaque_image(self):
104
+ from PIL import Image
105
+
106
+ img = self.image
107
+ if img is not None:
108
+ if img.mode == "RGBA":
109
+ r, g, b, a = img.split()
110
+ background = Image.new("RGB", img.size, "white")
111
+ background.paste(img, mask=a)
112
+ img = background
113
+ return img
@@ -512,6 +512,38 @@ class Node:
512
512
  def valid_node_for_reference(self, node):
513
513
  return True
514
514
 
515
+ def copy_children_as_references(self, obj):
516
+ """
517
+ Copy the children of the given object as direct references to those children.
518
+ @param obj:
519
+ @return:
520
+ """
521
+ for element in obj.children:
522
+ self.add_reference(element)
523
+
524
+ def copy_with_reified_tree(self):
525
+ """
526
+ Make a copy of the current node, and a copy of the sub-nodes dereferencing any reference nodes
527
+ @return:
528
+ """
529
+ copy_c = copy(self)
530
+ copy_c.copy_children_as_real(self)
531
+ return copy_c
532
+
533
+ def copy_children_as_real(self, copy_node):
534
+ """
535
+ Copy the children of copy_node to the current node, dereferencing any reference nodes.
536
+ @param copy_node:
537
+ @return:
538
+ """
539
+ for child in copy_node.children:
540
+ child = child
541
+ if child.type == "reference":
542
+ child = child.node
543
+ copy_child = copy(child)
544
+ self.add_node(copy_child)
545
+ copy_child.copy_children_as_real(child)
546
+
515
547
  def is_draggable(self):
516
548
  return True
517
549
 
@@ -912,7 +944,7 @@ class Node:
912
944
  if node._parent is not None:
913
945
  raise ValueError("Cannot reparent node on add.")
914
946
  node._parent = self
915
- node._root = self._root
947
+ node.set_root(self._root)
916
948
  if pos is None:
917
949
  self._children.append(node)
918
950
  else:
@@ -957,6 +989,17 @@ class Node:
957
989
  print(f"Did not produce a valid node for type '{type}'")
958
990
  return node
959
991
 
992
+ def set_root(self, root):
993
+ """
994
+ Set the root for this and all descendant to the provided root
995
+
996
+ @param root:
997
+ @return:
998
+ """
999
+ self._root = root
1000
+ for c in self._children:
1001
+ c.set_root(root)
1002
+
960
1003
  def _flatten(self, node):
961
1004
  """
962
1005
  Yield this node and all descendants in a flat generation.
@@ -1097,6 +1140,63 @@ class Node:
1097
1140
  self.unregister()
1098
1141
  return node
1099
1142
 
1143
+ def swap_node(self, node):
1144
+ """
1145
+ Swap nodes swaps the current node with the provided node in the other position in the same tree. All children
1146
+ during a swap are kept in place structurally. This permits swapping nodes between two positions that may be
1147
+ nested, without creating a loop.
1148
+
1149
+ Special care is taken for both swaps being children of the same parent.
1150
+
1151
+ @param node: Node already in the tree that should be swapped with the current node.
1152
+ @return:
1153
+ """
1154
+ # Remove self from tree.
1155
+ parent = self._parent
1156
+ n_parent = node._parent
1157
+
1158
+ index = parent._children.index(self)
1159
+ n_index = n_parent._children.index(node)
1160
+
1161
+ if index < n_index:
1162
+ # N_index is greater.
1163
+ del n_parent._children[n_index]
1164
+ del parent._children[index]
1165
+
1166
+ parent._children.insert(index, node)
1167
+ n_parent._children.insert(n_index, self)
1168
+ else:
1169
+ # N_index is lesser, equal
1170
+ del parent._children[index]
1171
+ del n_parent._children[n_index]
1172
+
1173
+ n_parent._children.insert(n_index, self)
1174
+ parent._children.insert(index, node)
1175
+
1176
+ node._parent = parent
1177
+ self._parent = n_parent
1178
+
1179
+ # Make a copy of children
1180
+ n_children = list(node._children)
1181
+ children = list(self._children)
1182
+
1183
+ # Delete children.
1184
+ node._children.clear()
1185
+ self._children.clear()
1186
+
1187
+ # Move children without call attach / detach.
1188
+ node._children.extend(children)
1189
+ self._children.extend(n_children)
1190
+
1191
+ # Correct parent for all children.
1192
+ for n in list(n_children):
1193
+ n._parent = self
1194
+ for n in list(children):
1195
+ n._parent = node
1196
+
1197
+ # self._root._validate_tree()
1198
+ self._root.notify_reorder()
1199
+
1100
1200
  def remove_node(self, children=True, references=True, fast=False, destroy=True):
1101
1201
  """
1102
1202
  Remove the current node from the tree.
@@ -1132,6 +1232,25 @@ class Node:
1132
1232
  child.remove_all_children(fast=fast, destroy=destroy)
1133
1233
  child.remove_node(fast=fast, destroy=destroy)
1134
1234
 
1235
+ def has_ancestor(self, type):
1236
+ """
1237
+ Return whether this node has an ancestor node that matches the given type, or matches the major type.
1238
+
1239
+ @param type:
1240
+ @return:
1241
+ """
1242
+ if self.parent is None:
1243
+ return False
1244
+
1245
+ if self.parent.type == type:
1246
+ return True
1247
+
1248
+ if " " not in type:
1249
+ if self.parent.type.startswith(type):
1250
+ return True
1251
+
1252
+ return self.parent.has_ancestor(type=type)
1253
+
1135
1254
  def get(self, type=None):
1136
1255
  """
1137
1256
  Recursive call for get to find first sub-nodes with the given type.
@@ -35,6 +35,7 @@ class CutOpNode(Node, Parameters):
35
35
  "elem rect",
36
36
  "elem line",
37
37
  "effect hatch",
38
+ "effect wobble",
38
39
  )
39
40
  # Which elements do we consider for automatic classification?
40
41
  self._allowed_elements = (
@@ -44,6 +45,7 @@ class CutOpNode(Node, Parameters):
44
45
  "elem rect",
45
46
  "elem line",
46
47
  "effect hatch",
48
+ "effect wobble",
47
49
  )
48
50
  # To which attributes responds the classification color check
49
51
  self.allowed_attributes = [
@@ -235,14 +237,6 @@ class CutOpNode(Node, Parameters):
235
237
  settings.write_persistent(section, "hex_color", self.color.hexa)
236
238
  settings.write_persistent_dict(section, self.settings)
237
239
 
238
- def copy_children(self, obj):
239
- for element in obj.children:
240
- self.add_reference(element)
241
-
242
- def copy_children_as_real(self, copy_node):
243
- for node in copy_node.children:
244
- self.add_node(copy(node.node))
245
-
246
240
  def time_estimate(self):
247
241
  estimate = 0
248
242
  for node in self.children:
@@ -198,14 +198,6 @@ class DotsOpNode(Node, Parameters):
198
198
  settings.write_persistent(section, "hex_color", self.color.hexa)
199
199
  settings.write_persistent_dict(section, self.settings)
200
200
 
201
- def copy_children(self, obj):
202
- for element in obj.children:
203
- self.add_reference(element)
204
-
205
- def copy_children_as_real(self, copy_node):
206
- for node in copy_node.children:
207
- self.add_node(copy(node.node))
208
-
209
201
  def time_estimate(self):
210
202
  estimate = 0
211
203
  for e in self.children:
@@ -34,6 +34,7 @@ class EngraveOpNode(Node, Parameters):
34
34
  "elem rect",
35
35
  "elem line",
36
36
  "effect hatch",
37
+ "effect wobble",
37
38
  )
38
39
  # Which elements do we consider for automatic classification?
39
40
  self._allowed_elements = (
@@ -43,6 +44,7 @@ class EngraveOpNode(Node, Parameters):
43
44
  "elem rect",
44
45
  "elem line",
45
46
  "effect hatch",
47
+ "effect wobble",
46
48
  )
47
49
 
48
50
  # To which attributes does the classification color check respond
@@ -224,24 +226,6 @@ class EngraveOpNode(Node, Parameters):
224
226
  settings.write_persistent(section, "hex_color", self.color.hexa)
225
227
  settings.write_persistent_dict(section, self.settings)
226
228
 
227
- def copy_children(self, obj):
228
- for element in obj.children:
229
- self.add_reference(element)
230
-
231
- def copy_children_as_real(self, copy_node):
232
- context = self
233
- for node in copy_node.children:
234
- if node.type.startswith("effect"):
235
- n = copy(node)
236
- context.add_node(n)
237
- context = n
238
- for node in copy_node.children:
239
- if node.type == "reference":
240
- context.add_node(copy(node.node))
241
- for node in self.children:
242
- if node.type.startswith("effect"):
243
- node.effect = True
244
-
245
229
  def time_estimate(self):
246
230
  estimate = 0
247
231
  for node in self.children:
@@ -21,11 +21,6 @@ class ImageOpNode(Node, Parameters):
21
21
  settings = dict(settings)
22
22
  Parameters.__init__(self, settings, **kwargs)
23
23
 
24
- # Which elements can be added to an operation (manually via DND)?
25
- self._allowed_elements_dnd = ("elem image",)
26
- # Which elements do we consider for automatic classification?
27
- self._allowed_elements = ("elem image",)
28
-
29
24
  # Is this op out of useful bounds?
30
25
  self.dangerous = False
31
26
  self.stopop = True
@@ -90,34 +85,32 @@ class ImageOpNode(Node, Parameters):
90
85
 
91
86
  def drop(self, drag_node, modify=True):
92
87
  # Default routine for drag + drop for an op node - irrelevant for others...
93
- if drag_node.type.startswith("elem"):
94
- if (
95
- drag_node.type not in self._allowed_elements_dnd
96
- or drag_node._parent.type == "branch reg"
97
- ):
88
+ if hasattr(drag_node, "as_image"):
89
+ if drag_node._parent.type == "branch reg":
90
+ # We do not accept reg nodes.
98
91
  return False
99
92
  # Dragging element onto operation adds that element to the op.
100
93
  if modify:
101
94
  self.add_reference(drag_node, pos=0)
102
95
  return True
103
- elif drag_node.type == "reference":
96
+ if drag_node.type == "reference":
104
97
  # Disallow drop of image refelems onto a Dot op.
105
- if not drag_node.node.type in self._allowed_elements_dnd:
98
+ if not hasattr(drag_node.node, "as_image"):
106
99
  return False
107
100
  # Move a refelem to end of op.
108
101
  if modify:
109
102
  self.append_child(drag_node)
110
103
  return True
111
- elif drag_node.type in op_nodes:
104
+ if drag_node.type in op_nodes:
112
105
  # Move operation to a different position.
113
106
  if modify:
114
107
  self.insert_sibling(drag_node)
115
108
  return True
116
- elif drag_node.type in ("file", "group"):
109
+ if drag_node.type in ("file", "group"):
117
110
  some_nodes = False
118
111
  for e in drag_node.flat(elem_nodes):
119
112
  # Add element to operation
120
- if e.type in self._allowed_elements_dnd:
113
+ if hasattr(e, "as_image"):
121
114
  if modify:
122
115
  self.add_reference(e)
123
116
  some_nodes = True
@@ -125,14 +118,14 @@ class ImageOpNode(Node, Parameters):
125
118
  return False
126
119
 
127
120
  def valid_node_for_reference(self, node):
128
- if node.type in self._allowed_elements_dnd:
121
+ if hasattr(node, "as_image"):
129
122
  return True
130
123
  else:
131
124
  return False
132
125
 
133
126
  def classify(self, node, fuzzy=False, fuzzydistance=100, usedefault=False):
134
127
  feedback = []
135
- if node.type in self._allowed_elements:
128
+ if hasattr(node, "as_image"):
136
129
  self.add_reference(node)
137
130
  # Have classified and no more classification are needed
138
131
  feedback.append("stroke")
@@ -159,14 +152,6 @@ class ImageOpNode(Node, Parameters):
159
152
  settings.write_persistent(section, "hex_color", self.color.hexa)
160
153
  settings.write_persistent_dict(section, self.settings)
161
154
 
162
- def copy_children(self, obj):
163
- for element in obj.children:
164
- self.add_reference(element)
165
-
166
- def copy_children_as_real(self, copy_node):
167
- for node in copy_node.children:
168
- self.add_node(copy(node.node))
169
-
170
155
  def time_estimate(self):
171
156
  """
172
157
  The scanlines would equal "(e.height * 1000) / dpi" but our images are pre-actualized.
@@ -267,7 +252,7 @@ class ImageOpNode(Node, Parameters):
267
252
  for image_node in self.children:
268
253
  # Process each child. All settings are different for each child.
269
254
 
270
- if image_node.type != "elem image":
255
+ if not hasattr(image_node, "as_image"):
271
256
  continue
272
257
  settings = self.derive()
273
258
 
@@ -277,7 +262,7 @@ class ImageOpNode(Node, Parameters):
277
262
  overscan = float(Length(overscan))
278
263
 
279
264
  # Set variables by direction
280
- if image_node.direction is not None:
265
+ if hasattr(image_node, "direction") and image_node.direction is not None:
281
266
  direction = image_node.direction
282
267
  else:
283
268
  direction = self.raster_direction
@@ -298,9 +283,17 @@ class ImageOpNode(Node, Parameters):
298
283
  start_on_left = True
299
284
  bidirectional = self.bidirectional
300
285
 
286
+ # Set variables
287
+ pil_image, bounds = image_node.as_image()
288
+ offset_x = bounds[0]
289
+ offset_y = bounds[1]
290
+
301
291
  # Get steps from individual images
302
- step_x = image_node.step_x
303
- step_y = image_node.step_y
292
+ image_width, image_height = pil_image.size
293
+ expected_width = bounds[2] - bounds[0]
294
+ expected_height = bounds[3] - bounds[1]
295
+ step_x = expected_width / image_width
296
+ step_y = expected_height / image_height
304
297
 
305
298
  if horizontal:
306
299
  # Raster step is only along y for horizontal raster
@@ -311,12 +304,6 @@ class ImageOpNode(Node, Parameters):
311
304
  settings["raster_step_x"] = step_x
312
305
  settings["raster_step_y"] = 0
313
306
 
314
- # Set variables
315
- matrix = image_node.active_matrix
316
- pil_image = image_node.active_image
317
- offset_x = matrix.value_trans_x()
318
- offset_y = matrix.value_trans_y()
319
-
320
307
  # Establish path
321
308
  min_x = offset_x
322
309
  min_y = offset_y
@@ -40,7 +40,6 @@ class RasterOpNode(Node, Parameters):
40
40
  "elem rect",
41
41
  "elem line",
42
42
  "elem text",
43
- # "elem image",
44
43
  )
45
44
 
46
45
  # self.allowed_attributes.append("fill")
@@ -276,14 +275,6 @@ class RasterOpNode(Node, Parameters):
276
275
  settings.write_persistent(section, "hex_color", self.color.hexa)
277
276
  settings.write_persistent_dict(section, self.settings)
278
277
 
279
- def copy_children(self, obj):
280
- for element in obj.children:
281
- self.add_reference(element)
282
-
283
- def copy_children_as_real(self, copy_node):
284
- for node in copy_node.children:
285
- self.add_node(copy(node.node))
286
-
287
278
  def time_estimate(self):
288
279
  estimate = 0
289
280
  dpi = self.dpi
meerk40t/core/planner.py CHANGED
@@ -45,6 +45,36 @@ def plugin(kernel, lifecycle=None):
45
45
  kernel.register_choices("planner", choices)
46
46
 
47
47
  choices = [
48
+ {
49
+ "attr": "opt_raster_optimisation",
50
+ "object": context,
51
+ "default": True,
52
+ "type": bool,
53
+ "label": _("Cluster raster objects"),
54
+ "tip": _(
55
+ "Separate non-overlapping raster objects.\n"
56
+ "Active: this will raster close (ie overlapping) objects as one,\n"
57
+ "but will separately process objects lying apart from each other.\n"
58
+ "Inactive: all objects will be lasered as one single unit."
59
+ ),
60
+ "page": "Optimisations",
61
+ "section": "_20_Reducing Movements",
62
+ "subsection": "Splitting rasters",
63
+ },
64
+ {
65
+ "attr": "opt_raster_opt_margin",
66
+ "object": context,
67
+ "default": "1mm",
68
+ "type": Length,
69
+ "label": _("Margin:"),
70
+ "tip": _(
71
+ "Allowed gap between rasterable objects, to still be counted as one."
72
+ ),
73
+ "page": "Optimisations",
74
+ "section": "_20_Reducing Movements",
75
+ "subsection": "Splitting rasters",
76
+ "conditional": (context, "opt_raster_optimisation"),
77
+ },
48
78
  {
49
79
  "attr": "opt_reduce_travel",
50
80
  "object": context,
@@ -190,7 +220,7 @@ def plugin(kernel, lifecycle=None):
190
220
  "type": bool,
191
221
  "label": _("Group Inner Burns"),
192
222
  "tip": _(
193
- "Try to complete a set of inner burns and the associated outer cut before moving onto other elements."
223
+ "Try to complete a set of inner burns and the associated outer cut before moving onto other elements.\n"
194
224
  + "This option only does something if Burn Inner First is also selected. "
195
225
  + "If your design has multiple separate pieces on it, "
196
226
  + "this should mostly cause each piece to be burned in entirety "
@@ -227,7 +257,6 @@ def plugin(kernel, lifecycle=None):
227
257
  },
228
258
  ]
229
259
  kernel.register_choices("optimize", choices)
230
-
231
260
  context.setting(bool, "opt_2opt", False)
232
261
  context.setting(bool, "opt_nearest_neighbor", True)
233
262
  context.setting(bool, "opt_reduce_directions", False)
@@ -259,6 +288,7 @@ class Planner(Service):
259
288
  Service.__init__(self, kernel, "planner")
260
289
  self._plan = dict()
261
290
  self._default_plan = "0"
291
+ self.do_optimization = True
262
292
 
263
293
  def length(self, v):
264
294
  return float(Length(v))