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
@@ -1,5 +1,5 @@
1
1
  """
2
- This is a large number of flagged tree operations. The details of how these are registered is availible in the treeop.py
2
+ This is a large number of flagged tree operations. The details of how these are registered is available in the treeop.py
3
3
  file. These define the right-click node menu operations. That menu is dynamically created based on various context
4
4
  cues.
5
5
  """
@@ -9,7 +9,6 @@ import math
9
9
  import os.path
10
10
  from copy import copy
11
11
 
12
- from meerk40t.core.cutcode.cutcode import CutCode
13
12
  from meerk40t.core.node.elem_image import ImageNode
14
13
  from meerk40t.core.node.node import Node
15
14
  from meerk40t.core.treeop import (
@@ -29,6 +28,7 @@ from meerk40t.core.treeop import (
29
28
  from meerk40t.core.units import UNITS_PER_INCH, Length
30
29
  from meerk40t.kernel import CommandSyntaxError
31
30
  from meerk40t.svgelements import Matrix, Point, Polygon
31
+ from meerk40t.tools.geomstr import Geomstr
32
32
 
33
33
  from .element_types import *
34
34
 
@@ -49,13 +49,7 @@ def init_tree(kernel):
49
49
  # --------------------------- TREE OPERATIONS ---------------------------
50
50
 
51
51
  def is_regmark(node):
52
- result = False
53
- try:
54
- if node._parent.type == "branch reg":
55
- result = True
56
- except AttributeError:
57
- pass
58
- return result
52
+ return node.has_ancestor("branch reg")
59
53
 
60
54
  def has_changes(node):
61
55
  result = False
@@ -67,6 +61,10 @@ def init_tree(kernel):
67
61
  pass
68
62
  return result
69
63
 
64
+ def is_developer_mode():
65
+ flag = getattr(self.kernel.root, "developer_mode", False)
66
+ return flag
67
+
70
68
  @tree_separator_after()
71
69
  @tree_conditional(lambda node: len(list(self.ops(selected=True))) == 1)
72
70
  @tree_operation(_("Operation properties"), node_type=op_nodes, help="")
@@ -121,6 +119,17 @@ def init_tree(kernel):
121
119
  if activate is not None:
122
120
  activate(node)
123
121
 
122
+ # @tree_operation(_("Debug group"), node_type=("group", "file"), help="")
123
+ # def debug_group(node, **kwargs):
124
+ # if node is None:
125
+ # return
126
+ # info = ""
127
+ # for idx, e in enumerate(list(node.children)):
128
+ # if info:
129
+ # info += "\n"
130
+ # info += f"{idx}#: {e.type}, identical to parent: {e is node}"
131
+ # print (info)
132
+
124
133
  @tree_conditional(lambda node: not is_regmark(node))
125
134
  @tree_operation(_("Ungroup elements"), node_type=("group", "file"), help="")
126
135
  def ungroup_elements(node, **kwargs):
@@ -190,8 +199,31 @@ def init_tree(kernel):
190
199
  @tree_conditional(lambda node: len(list(self.elems(emphasized=True))) > 1)
191
200
  @tree_operation(_("Group elements"), node_type=elem_nodes, help="")
192
201
  def group_elements(node, **kwargs):
193
- group_node = node.parent.add(type="group", label="Group")
194
- for e in list(self.elems(emphasized=True)):
202
+ def minimal_parent(data):
203
+ result = None
204
+ root = self.elem_branch
205
+ curr_level = None
206
+ for node in data:
207
+ plevel = 0
208
+ candidate = node.parent
209
+ while candidate is not None and candidate.parent is not root:
210
+ candidate = candidate.parent
211
+ plevel += 1
212
+ if curr_level is None or plevel < curr_level:
213
+ curr_level = plevel
214
+ result = node.parent
215
+ if plevel == 0:
216
+ # No need to continue
217
+ break
218
+ if result is None:
219
+ result = root
220
+ return result
221
+
222
+ raw_data = list(self.elems(emphasized=True))
223
+ data = self.condense_elements(raw_data, expand_at_end=False)
224
+ parent_node = minimal_parent(data)
225
+ group_node = parent_node.add(type="group", label="Group")
226
+ for e in data:
195
227
  group_node.append_child(e)
196
228
 
197
229
  @tree_conditional(
@@ -982,42 +1014,24 @@ def init_tree(kernel):
982
1014
  # REMOVE SINGLE (Tree Selected - ELEMENT)
983
1015
  # ==========
984
1016
 
985
- @tree_conditional(lambda node: hasattr(node, "effect") and not node.effect)
986
- @tree_operation(
987
- _("Effect: On"),
988
- node_type=effect_nodes,
989
- help="",
990
- )
991
- def effect_on(node, **kwargs):
992
- node.effect = not node.effect
993
-
994
- @tree_conditional(lambda node: hasattr(node, "effect") and node.effect)
995
- @tree_operation(
996
- _("Effect: Off"),
997
- node_type=effect_nodes,
998
- help="",
999
- )
1000
- def effect_off(node, **kwargs):
1001
- node.effect = not node.effect
1002
-
1003
- @tree_conditional(lambda node: node.can_remove)
1004
- @tree_conditional(
1005
- lambda cond: len(
1006
- list(self.flat(selected=True, cascade=False, types=elem_nodes))
1007
- )
1008
- == 1
1009
- )
1010
- @tree_operation(
1011
- _("Delete element '{name}' fully"),
1012
- node_type=elem_nodes,
1013
- help="",
1014
- )
1015
- def remove_type_elem(node, **kwargs):
1016
- if hasattr(node, "can_remove") and not node.can_remove:
1017
- pass
1018
- else:
1019
- self.set_emphasis(None)
1020
- node.remove_node()
1017
+ # @tree_conditional(lambda node: node.can_remove)
1018
+ # @tree_conditional(
1019
+ # lambda cond: len(
1020
+ # list(self.flat(selected=True, cascade=False, types=elem_nodes))
1021
+ # )
1022
+ # == 1
1023
+ # )
1024
+ # @tree_operation(
1025
+ # _("Delete element '{name}' fully"),
1026
+ # node_type=elem_nodes,
1027
+ # help="",
1028
+ # )
1029
+ # def remove_type_elem(node, **kwargs):
1030
+ # if hasattr(node, "can_remove") and not node.can_remove:
1031
+ # pass
1032
+ # else:
1033
+ # self.set_emphasis(None)
1034
+ # node.remove_node()
1021
1035
 
1022
1036
  @tree_conditional(
1023
1037
  lambda cond: len(list(self.flat(selected=True, cascade=False, types=op_nodes)))
@@ -1202,7 +1216,7 @@ def init_tree(kernel):
1202
1216
  # REMOVE ELEMENTS
1203
1217
  # ==========
1204
1218
  # More than one, special case == 1 already dealt with
1205
- @tree_conditional(lambda node: len(list(self.elems(emphasized=True))) > 1)
1219
+ @tree_conditional(lambda node: len(list(self.elems(emphasized=True))) >= 1)
1206
1220
  @tree_calc("ecount", lambda i: len(list(self.elems(emphasized=True))))
1207
1221
  @tree_operation(
1208
1222
  _("Delete {ecount} elements, as selected in scene"),
@@ -1221,7 +1235,7 @@ def init_tree(kernel):
1221
1235
  self.signal("make_reference", node)
1222
1236
 
1223
1237
  @tree_conditional(
1224
- lambda node: isinstance(node.shape, Polygon) and len(node.shape.points) >= 3
1238
+ lambda node: node.closed and len(list(node.geometry.as_points())) >= 3
1225
1239
  )
1226
1240
  @tree_operation(
1227
1241
  _("Make Polygon regular"),
@@ -1229,74 +1243,28 @@ def init_tree(kernel):
1229
1243
  help="",
1230
1244
  )
1231
1245
  def make_polygon_regular(node, **kwargs):
1232
- # from .units import Length
1233
- # from ..svgelements import Color
1234
-
1235
- # def mk_debug_point(x, y, col):
1236
- # circ = Ellipse(cx=x, cy=y, r=float(Length("1mm")))
1237
- # circ.transform = copy(node.shape.transform)
1238
- # self.elem_branch.add(
1239
- # shape=circ,
1240
- # type="elem ellipse",
1241
- # stroke=Color(col),
1242
- # fill=Color(col),
1243
- # matrix=copy(node.matrix),
1244
- # )
1245
-
1246
1246
  if node is None or node.type != "elem polyline":
1247
1247
  return
1248
- number_points = len(node.shape.points)
1249
- # print (f"Number of points: {number_points}")
1250
- # for prop in dir(node):
1251
- # print (f"{prop} - {getattr(node, 'prop', '')}")
1252
- # for idx, pt in enumerate(node.shape.points):
1253
- # print (f"{idx}: {pt.x:.1f}, {pt.y:.1f}")
1254
- if number_points < 3 or not isinstance(node.shape, Polygon):
1255
- return
1256
- pts = node.shape.points
1257
- dx = pts[1].x - pts[0].x
1258
- dy = pts[1].y - pts[0].y
1259
- baseline = math.sqrt(dx * dx + dy * dy)
1260
- apothem = baseline / (2 * math.tan(math.tau / (2 * number_points)))
1261
- circumradius = baseline / (2 * math.sin(math.tau / (2 * number_points)))
1262
- midpoint = Point(pts[0].x + 0.5 * dx, pts[0].y + 0.5 * dy)
1263
- # mk_debug_point(midpoint.x, midpoint.y, "black")
1264
- ax = 0
1265
- ay = 0
1266
- for idx, pt in enumerate(pts):
1267
- ax += pt.x
1268
- ay += pt.y
1269
- ax /= number_points
1270
- ay /= number_points
1248
+ pts = list(node.geometry.as_points())
1249
+ vertex_count = len(pts) - 1
1250
+ baseline = abs(pts[1] - pts[0])
1251
+ circumradius = baseline / (2 * math.sin(math.tau / (2 * vertex_count)))
1252
+ # apothem = baseline / (2 * math.tan(math.tau / (2 * vertex_count)))
1253
+ # midpoint = (pts[0] - pts[1]) / 2
1254
+
1271
1255
  # The arithmetic center (ax, ay) indicates to which
1272
1256
  # 'side' of the baseline the polygon needs to be constructed
1273
- arithmetic_center = Point(ax, ay)
1274
- # mk_debug_point(ax, ay, "green")
1275
- angle = pts[0].angle_to(pts[1])
1276
- midangle = midpoint.angle_to(arithmetic_center)
1277
- angle += math.tau / 4
1278
- first_point = Point.polar(midpoint, angle, apothem)
1279
- second_point = Point.polar(midpoint, angle + math.tau / 2, apothem)
1280
- # mk_debug_point(first_point.x, first_point.y, "yellow")
1281
- # mk_debug_point(second_point.x, second_point.y, "cyan")
1282
- deltaangle = math.tau / number_points
1283
- d1 = arithmetic_center.distance_to(first_point)
1284
- d2 = arithmetic_center.distance_to(second_point)
1285
- if d1 < d2:
1286
- center_point = copy(first_point)
1287
- else:
1288
- center_point = copy(second_point)
1289
- # mk_debug_point(center_point.x, center_point.y, "red")
1290
-
1291
- if center_point.angle_to(pts[0]) > center_point.angle_to(pts[1]):
1292
- deltaangle *= -1
1293
- angle = center_point.angle_to(pts[0])
1294
- for idx in range(number_points):
1295
- # if idx > 1:
1296
- pt = Point.polar(center_point, angle, circumradius)
1297
- pts[idx].x = pt.x
1298
- pts[idx].y = pt.y
1299
- angle += deltaangle
1257
+ arithmetic_center = sum(pts[:-1]) / vertex_count
1258
+
1259
+ start_angle = Geomstr.angle(None, arithmetic_center, pts[0])
1260
+
1261
+ node.geometry = Geomstr.regular_polygon(
1262
+ vertex_count,
1263
+ arithmetic_center,
1264
+ radius=circumradius,
1265
+ radius_inner=circumradius,
1266
+ start_angle=start_angle,
1267
+ )
1300
1268
  node.altered()
1301
1269
  self.signal("refresh_scene", "Scene")
1302
1270
 
@@ -1422,48 +1390,89 @@ def init_tree(kernel):
1422
1390
  node.reverse()
1423
1391
  self.signal("refresh_tree", list(self.flat(types="reference")))
1424
1392
 
1425
- @tree_separator_after()
1426
- @tree_conditional(lambda node: self.classify_autogenerate)
1393
+ @tree_submenu(_("Classification"))
1427
1394
  @tree_operation(
1428
- _("Refresh classification"),
1429
- node_type="branch ops",
1395
+ _("Generate operations if needed"),
1396
+ node_type=("branch ops", "branch elems"),
1397
+ help="",
1398
+ enable=False,
1399
+ )
1400
+ def do_classification_comment_1(node, **kwargs):
1401
+ return
1402
+
1403
+ @tree_submenu(_("Classification"))
1404
+ @tree_operation(
1405
+ _("Refresh classification for all"),
1406
+ node_type=("branch ops", "branch elems"),
1430
1407
  help=_("Reclassify elements and create operations if necessary"),
1431
1408
  )
1432
- def refresh_clasifications_1(node, **kwargs):
1409
+ def refresh_classification_for_all_std(node, **kwargs):
1410
+ previous = self.classify_autogenerate
1411
+ self.classify_autogenerate = True
1433
1412
  self.remove_elements_from_operations(list(self.elems()))
1434
1413
  self.classify(list(self.elems()))
1414
+ self.classify_autogenerate = previous
1435
1415
  self.signal("refresh_tree", list(self.flat(types="reference")))
1436
1416
 
1437
- @tree_conditional(lambda node: not self.classify_autogenerate)
1417
+ @tree_conditional(lambda node: self.have_unassigned_elements())
1418
+ @tree_submenu(_("Classification"))
1438
1419
  @tree_operation(
1439
- _("Refresh classification"),
1440
- node_type="branch ops",
1441
- help=_("Reclassify elements and use only existing operations"),
1420
+ _("Classification for unassigned"),
1421
+ node_type=("branch ops", "branch elems"),
1422
+ help=_("Classify unassigned elements and create operations if necessary"),
1442
1423
  )
1443
- def refresh_clasifications_2(node, **kwargs):
1444
- self.remove_elements_from_operations(list(self.elems()))
1445
- self.classify(list(self.elems()))
1424
+ def do_classification_for_unassigned_std(node, **kwargs):
1425
+ previous = self.classify_autogenerate
1426
+ self.classify_autogenerate = True
1427
+ target_list = list(self.unassigned_elements())
1428
+ self.classify(target_list)
1429
+ self.classify_autogenerate = previous
1446
1430
  self.signal("refresh_tree", list(self.flat(types="reference")))
1447
1431
 
1448
- @tree_separator_after()
1449
- @tree_conditional(lambda node: not self.classify_autogenerate)
1432
+ @tree_submenu(_("Classification"))
1433
+ @tree_separator_before()
1450
1434
  @tree_operation(
1451
- _("Refresh ... (incl autogeneration)"),
1452
- node_type="branch ops",
1453
- help=_("Reclassify elements and create operations if necessary"),
1435
+ _("Use only existing operations"),
1436
+ node_type=("branch ops", "branch elems"),
1437
+ help="",
1438
+ enable=False,
1454
1439
  )
1455
- def refresh_clasifications_3(node, **kwargs):
1440
+ def do_classification_comment_2(node, **kwargs):
1441
+ return
1442
+
1443
+ @tree_submenu(_("Classification"))
1444
+ @tree_operation(
1445
+ _("Refresh classification for all"),
1446
+ node_type=("branch ops", "branch elems"),
1447
+ help=_("Reclassify all elements and use only existing operations"),
1448
+ )
1449
+ def refresh_classification_for_all_existing_only(node, **kwargs):
1456
1450
  previous = self.classify_autogenerate
1457
- self.classify_autogenerate = True
1451
+ self.classify_autogenerate = False
1458
1452
  self.remove_elements_from_operations(list(self.elems()))
1459
1453
  self.classify(list(self.elems()))
1460
1454
  self.classify_autogenerate = previous
1461
1455
  self.signal("refresh_tree", list(self.flat(types="reference")))
1462
1456
 
1457
+ @tree_submenu(_("Classification"))
1458
+ @tree_conditional(lambda node: self.have_unassigned_elements())
1459
+ @tree_operation(
1460
+ _("Classification for unassigned"),
1461
+ node_type=("branch ops", "branch elems"),
1462
+ help=_("Classify unassigned elements and use only existing operations"),
1463
+ )
1464
+ def do_classification_for_unassigned_existing_only(node, **kwargs):
1465
+ previous = self.classify_autogenerate
1466
+ self.classify_autogenerate = False
1467
+ target_list = list(self.unassigned_elements())
1468
+ self.classify(target_list)
1469
+ self.classify_autogenerate = previous
1470
+ self.signal("refresh_tree", list(self.flat(types="reference")))
1471
+
1463
1472
  @tree_conditional(lambda cond: self.have_unassigned_elements())
1464
1473
  @tree_operation(
1465
1474
  _("Select unassigned elements"),
1466
- node_type="branch ops",
1475
+ node_type=("branch ops", "branch elems"),
1467
1476
  help=_("Select all elements that won't be burned"),
1468
1477
  )
1469
1478
  def select_unassigned(node, **kwargs):
@@ -1505,6 +1514,7 @@ def init_tree(kernel):
1505
1514
  difference = [m for m in materials if m not in secs]
1506
1515
  return difference
1507
1516
 
1517
+ @tree_separator_before()
1508
1518
  @tree_submenu(_("Load"))
1509
1519
  @tree_values("opname", values=self.op_data.section_set)
1510
1520
  @tree_operation("{opname}", node_type="branch ops", help="")
@@ -1699,13 +1709,6 @@ def init_tree(kernel):
1699
1709
  )
1700
1710
  self.signal("updateop_tree")
1701
1711
 
1702
- @tree_operation(_("Reclassify operations"), node_type="branch elems", help="")
1703
- def reclassify_operations(node, **kwargs):
1704
- elems = list(self.elems())
1705
- self.remove_elements_from_operations(elems)
1706
- self.classify(list(self.elems()))
1707
- self.signal("refresh_tree")
1708
-
1709
1712
  @tree_operation(
1710
1713
  _("Remove all assignments from operations"),
1711
1714
  node_type="branch elems",
@@ -1718,62 +1721,153 @@ def init_tree(kernel):
1718
1721
  ref.remove_node()
1719
1722
  self.signal("refresh_tree")
1720
1723
 
1721
- @tree_submenu(_("Append special effect"))
1722
- @tree_operation(_("Append Line-fill 0.1mm"), node_type="branch elems", help="")
1723
- def append_element_effect_eulerian(
1724
- node, node_type="branch elems", pos=None, **kwargs
1725
- ):
1726
- self.elem_branch.add(
1724
+ hatchable_elems = (
1725
+ "elem path",
1726
+ "elem rect",
1727
+ "elem circle",
1728
+ "elem ellipse",
1729
+ "elem polyline",
1730
+ )
1731
+
1732
+ @tree_submenu(_("Apply special effect"))
1733
+ @tree_operation(_("Append Line-fill 0.1mm"), node_type=hatchable_elems, help="")
1734
+ def append_element_effect_eulerian(node, pos=None, **kwargs):
1735
+ group_node = node.parent.add(
1727
1736
  type="effect hatch",
1728
1737
  hatch_type="scanline",
1729
1738
  hatch_distance="0.1mm",
1730
1739
  hatch_angle="0deg",
1731
1740
  pos=pos,
1732
1741
  )
1742
+ for e in list(self.elems(emphasized=True)):
1743
+ group_node.append_child(e)
1744
+ if self.classify_new:
1745
+ self.classify([group_node])
1746
+
1733
1747
  self.signal("updateelem_tree")
1734
1748
 
1735
- @tree_submenu(_("Append special effect"))
1749
+ @tree_submenu(_("Apply special effect"))
1736
1750
  @tree_operation(
1737
- _("Append diagonal Line-fill 0.1mm"), node_type="branch elems", help=""
1751
+ _("Append diagonal Line-fill 0.1mm"), node_type=hatchable_elems, help=""
1738
1752
  )
1739
- def append_element_effect_eulerian_45(
1740
- node, node_type="branch elems", pos=None, **kwargs
1741
- ):
1742
- self.elem_branch.add(
1753
+ def append_element_effect_eulerian_45(node, pos=None, **kwargs):
1754
+ group_node = node.parent.add(
1743
1755
  type="effect hatch",
1744
1756
  hatch_type="scanline", # scanline / eulerian
1745
1757
  hatch_distance="0.1mm",
1746
1758
  hatch_angle="45deg",
1747
1759
  pos=pos,
1748
1760
  )
1761
+ for e in list(self.elems(emphasized=True)):
1762
+ group_node.append_child(e)
1763
+ if self.classify_new:
1764
+ self.classify([group_node])
1765
+
1749
1766
  self.signal("updateelem_tree")
1750
1767
 
1751
- @tree_submenu(_("Append special effect"))
1752
- @tree_operation(_("Append Line-Fill 1mm"), node_type="branch elems", help="")
1753
- def append_element_effect_line(node, node_type="branch elems", pos=None, **kwargs):
1754
- self.elem_branch.add(
1768
+ @tree_submenu(_("Apply special effect"))
1769
+ @tree_operation(_("Append Line-Fill 1mm"), node_type=hatchable_elems, help="")
1770
+ def append_element_effect_line(node, pos=None, **kwargs):
1771
+ group_node = node.parent.add(
1755
1772
  type="effect hatch",
1756
1773
  hatch_type="scanline",
1757
1774
  hatch_distance="1mm",
1758
1775
  hatch_angle="0deg",
1759
1776
  pos=pos,
1760
1777
  )
1778
+ for e in list(self.elems(emphasized=True)):
1779
+ group_node.append_child(e)
1780
+ if self.classify_new:
1781
+ self.classify([group_node])
1782
+
1761
1783
  self.signal("updateelem_tree")
1762
1784
 
1763
- @tree_submenu(_("Append special effect"))
1785
+ @tree_submenu(_("Apply special effect"))
1764
1786
  @tree_operation(
1765
- _("Append diagonal Line-Fill 1mm"), node_type="branch elems", help=""
1787
+ _("Append diagonal Line-Fill 1mm"), node_type=hatchable_elems, help=""
1766
1788
  )
1767
- def append_element_effect_line_45(
1768
- node, node_type="branch elems", pos=None, **kwargs
1769
- ):
1770
- self.elem_branch.add(
1789
+ def append_element_effect_line_45(node, pos=None, **kwargs):
1790
+ group_node = node.parent.add(
1771
1791
  type="effect hatch",
1772
1792
  hatch_type="scanline",
1773
1793
  hatch_distance="1mm",
1774
1794
  hatch_angle="45deg",
1775
1795
  pos=pos,
1776
1796
  )
1797
+ for e in list(self.elems(emphasized=True)):
1798
+ group_node.append_child(e)
1799
+ if self.classify_new:
1800
+ self.classify([group_node])
1801
+
1802
+ self.signal("updateelem_tree")
1803
+
1804
+ @tree_submenu(_("Apply special effect"))
1805
+ @tree_operation(
1806
+ _("Append wobble {type} {radius} @{interval}").format(
1807
+ type="Circle", radius="0.5mm", interval="0.05mm"
1808
+ ),
1809
+ node_type=hatchable_elems,
1810
+ help="",
1811
+ )
1812
+ def append_element_effect_wobble_c05(node, pos=None, **kwargs):
1813
+ group_node = node.parent.add(
1814
+ type="effect wobble",
1815
+ wobble_type="circle",
1816
+ wobble_radius="0.5mm",
1817
+ wobble_interval="0.05mm",
1818
+ pos=pos,
1819
+ )
1820
+ for e in list(self.elems(emphasized=True)):
1821
+ group_node.append_child(e)
1822
+ if self.classify_new:
1823
+ self.classify([group_node])
1824
+
1825
+ self.signal("updateelem_tree")
1826
+
1827
+ @tree_submenu(_("Apply special effect"))
1828
+ @tree_operation(
1829
+ _("Append wobble {type} {radius} @{interval}").format(
1830
+ type="Circle", radius="1mm", interval="0.1mm"
1831
+ ),
1832
+ node_type=hatchable_elems,
1833
+ help="",
1834
+ )
1835
+ def append_element_effect_wobble_c1(node, pos=None, **kwargs):
1836
+ group_node = node.parent.add(
1837
+ type="effect wobble",
1838
+ wobble_type="circle",
1839
+ wobble_radius="1mm",
1840
+ wobble_interval="0.1mm",
1841
+ pos=pos,
1842
+ )
1843
+ for e in list(self.elems(emphasized=True)):
1844
+ group_node.append_child(e)
1845
+ if self.classify_new:
1846
+ self.classify([group_node])
1847
+
1848
+ self.signal("updateelem_tree")
1849
+
1850
+ @tree_submenu(_("Apply special effect"))
1851
+ @tree_operation(
1852
+ _("Append wobble {type} {radius} @{interval}").format(
1853
+ type="Circle", radius="3mm", interval="0.1mm"
1854
+ ),
1855
+ node_type=hatchable_elems,
1856
+ help="",
1857
+ )
1858
+ def append_element_effect_wobble_c3(node, pos=None, **kwargs):
1859
+ group_node = node.parent.add(
1860
+ type="effect wobble",
1861
+ wobble_type="circle_right",
1862
+ wobble_radius="3mm",
1863
+ wobble_interval="0.1mm",
1864
+ pos=pos,
1865
+ )
1866
+ for e in list(self.elems(emphasized=True)):
1867
+ group_node.append_child(e)
1868
+ if self.classify_new:
1869
+ self.classify([group_node])
1870
+
1777
1871
  self.signal("updateelem_tree")
1778
1872
 
1779
1873
  @tree_operation(
@@ -2176,19 +2270,26 @@ def init_tree(kernel):
2176
2270
  had_optional = False
2177
2271
  # Need to add stroke and fill, as copy will take the
2178
2272
  # default values for these attributes
2179
- for optional in ("fill", "stroke"):
2180
- if hasattr(orgnode, optional):
2273
+ options = ["fill", "stroke", "wxfont"]
2274
+ for optional in options:
2275
+ if hasattr(e, optional):
2181
2276
  setattr(copy_node, optional, getattr(orgnode, optional))
2182
- for optional in ("wxfont", "mktext", "mkfont", "mkfontsize"):
2183
- if hasattr(orgnode, optional):
2184
- had_optional = True
2277
+ hadoptional = False
2278
+ options = []
2279
+ for prop in dir(e):
2280
+ if prop.startswith("mk"):
2281
+ options.append(prop)
2282
+ for optional in options:
2283
+ if hasattr(e, optional):
2185
2284
  setattr(copy_node, optional, getattr(orgnode, optional))
2285
+ hadoptional = True
2286
+
2186
2287
  if self.copy_increases_wordlist_references and hasattr(orgnode, "text"):
2187
2288
  copy_node.text = self.wordlist_delta(orgnode.text, delta_wordlist)
2188
2289
  elif self.copy_increases_wordlist_references and hasattr(e, "mktext"):
2189
2290
  copy_node.mktext = self.wordlist_delta(e.mktext, delta_wordlist)
2190
2291
  orgparent.add_node(copy_node)
2191
- if had_optional:
2292
+ if hadoptional:
2192
2293
  for property_op in self.kernel.lookup_all("path_updater/.*"):
2193
2294
  property_op(self.kernel.root, copy_node)
2194
2295
 
@@ -2335,6 +2436,33 @@ def init_tree(kernel):
2335
2436
  setattr(newnode, item[0], item[1])
2336
2437
  newnode.altered()
2337
2438
 
2439
+ @tree_conditional(
2440
+ lambda node: hasattr(node, "as_geometry") and node.has_ancestor("branch elems")
2441
+ )
2442
+ @tree_operation(
2443
+ _("Convert to path"),
2444
+ node_type=effect_nodes,
2445
+ help="Convert effect to path",
2446
+ )
2447
+ def convert_to_path(singlenode, **kwargs):
2448
+ elements = self.elem_branch
2449
+ for node in list(elements.flat(types=effect_nodes, emphasized=True)):
2450
+ if not hasattr(node, "as_geometry"):
2451
+ continue
2452
+ node_attributes = []
2453
+ for attrib in ("stroke", "fill", "stroke_width", "stroke_scaled"):
2454
+ if hasattr(node, attrib):
2455
+ oldval = getattr(node, attrib, None)
2456
+ node_attributes.append([attrib, oldval])
2457
+ geometry = node.as_geometry()
2458
+ node.remove_all_children()
2459
+ if not len(geometry):
2460
+ return
2461
+ newnode = node.replace_node(geometry=geometry, type="elem path")
2462
+ for item in node_attributes:
2463
+ setattr(newnode, item[0], item[1])
2464
+ newnode.altered()
2465
+
2338
2466
  @tree_submenu(_("Flip"))
2339
2467
  @tree_separator_before()
2340
2468
  @tree_conditional(lambda node: not is_regmark(node))
@@ -2527,8 +2655,6 @@ def init_tree(kernel):
2527
2655
  @tree_separator_before()
2528
2656
  @tree_operation(_("Create placement"), node_type=elem_nodes, help="")
2529
2657
  def regmark_as_placement(node, **kwargs):
2530
- if node is None:
2531
- return
2532
2658
  if hasattr(node, "path"):
2533
2659
  bb = node.path.bbox(transformed=False)
2534
2660
  elif hasattr(node, "shape"):
@@ -2556,8 +2682,6 @@ def init_tree(kernel):
2556
2682
  @tree_submenu(_("Toggle Magnet-Lines"))
2557
2683
  @tree_operation(_("Around border"), node_type=elem_group_nodes, help="")
2558
2684
  def regmark_to_magnet_1(node, **kwargs):
2559
- if node is None:
2560
- return
2561
2685
  if not hasattr(node, "bounds"):
2562
2686
  return
2563
2687
  self.signal("magnet_gen", ("outer", node))
@@ -2566,18 +2690,10 @@ def init_tree(kernel):
2566
2690
  @tree_submenu(_("Toggle Magnet-Lines"))
2567
2691
  @tree_operation(_("At center"), node_type=elem_group_nodes, help="")
2568
2692
  def regmark_to_magnet_2(node, **kwargs):
2569
- if node is None:
2570
- return
2571
2693
  if not hasattr(node, "bounds"):
2572
2694
  return
2573
2695
  self.signal("magnet_gen", ("center", node))
2574
2696
 
2575
- # @tree_conditional(lambda node: not node.lock)
2576
- # @tree_conditional_try(lambda node: not node.lock)
2577
- # @tree_operation(_("Actualize pixels"), node_type="elem image", help="")
2578
- # def image_actualize_pixels(node, **kwargs):
2579
- # self("image resample\n")
2580
-
2581
2697
  @tree_conditional(lambda node: not node.lock)
2582
2698
  @tree_submenu(_("Z-depth divide"))
2583
2699
  @tree_iterate("divide", 2, 10)
@@ -2639,7 +2755,21 @@ def init_tree(kernel):
2639
2755
  def image_ccw(node, **kwargs):
2640
2756
  self("image ccw\n")
2641
2757
 
2758
+ @tree_conditional(lambda node: not node.lock)
2759
+ @tree_separator_before()
2760
+ @tree_submenu(_("Image"))
2761
+ @tree_operation(_("Identify inner white areas"), node_type="elem image", help="")
2762
+ def image_white_area(node, **kwargs):
2763
+ self("image innerwhite -l -o -m 2\n")
2764
+
2765
+ @tree_conditional(lambda node: not node.lock)
2642
2766
  @tree_submenu(_("Image"))
2767
+ @tree_operation(_("Split image along white areas"), node_type="elem image", help="")
2768
+ def image_white_area_split(node, **kwargs):
2769
+ self("image innerwhite -w -o -m 2\n")
2770
+
2771
+ @tree_submenu(_("Image"))
2772
+ @tree_separator_before()
2643
2773
  @tree_operation(
2644
2774
  _("Save original image to output.png"), node_type="elem image", help=""
2645
2775
  )
@@ -2653,6 +2783,16 @@ def init_tree(kernel):
2653
2783
  def image_save_processed(node, **kwargs):
2654
2784
  self("image save output.png --processed\n")
2655
2785
 
2786
+ @tree_conditional(lambda node: not node.lock and is_developer_mode())
2787
+ @tree_submenu(_("Convert"))
2788
+ @tree_operation(_("Raw Image"), node_type="elem image", help="")
2789
+ def image_convert_raw(node, **kwargs):
2790
+ node.replace_node(
2791
+ image=node.image,
2792
+ matrix=node.matrix,
2793
+ type="image raster",
2794
+ )
2795
+
2656
2796
  @tree_conditional(lambda node: len(node.children) > 0)
2657
2797
  @tree_separator_before()
2658
2798
  @tree_operation(