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
meerk40t/core/svg_io.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- This modeu
2
+ This extension governs SVG loading and saving, registering both the load and the save values for SVG.
3
3
  """
4
4
 
5
5
  import ast
@@ -122,6 +122,11 @@ MEERK40T_XMLS_ID = "meerk40t"
122
122
 
123
123
 
124
124
  def capstr(linecap):
125
+ """
126
+ Given the mk enum values for linecap, returns the svg string.
127
+ @param linecap:
128
+ @return:
129
+ """
125
130
  if linecap == Linecap.CAP_BUTT:
126
131
  return "butt"
127
132
  elif linecap == Linecap.CAP_SQUARE:
@@ -131,6 +136,12 @@ def capstr(linecap):
131
136
 
132
137
 
133
138
  def joinstr(linejoin):
139
+ """
140
+ Given the mk enum value for linejoin, returns the svg string.
141
+
142
+ @param linejoin:
143
+ @return:
144
+ """
134
145
  if linejoin == Linejoin.JOIN_ARCS:
135
146
  return "arcs"
136
147
  elif linejoin == Linejoin.JOIN_BEVEL:
@@ -144,19 +155,18 @@ def joinstr(linejoin):
144
155
 
145
156
 
146
157
  def rulestr(fillrule):
158
+ """
159
+ Given the mk enum value for fillrule, returns the svg string.
160
+
161
+ @param fillrule:
162
+ @return:
163
+ """
147
164
  if fillrule == Fillrule.FILLRULE_EVENODD:
148
165
  return "evenodd"
149
166
  else:
150
167
  return "nonzero"
151
168
 
152
169
 
153
- def copy_attributes(source, target):
154
- if hasattr(source, "stroke"):
155
- target.stroke = source.stroke
156
- if hasattr(source, "fill"):
157
- target.fill = source.fill
158
-
159
-
160
170
  class SVGWriter:
161
171
  @staticmethod
162
172
  def save_types():
@@ -252,13 +262,11 @@ class SVGWriter:
252
262
  return flag
253
263
 
254
264
  if c.type == "elem ellipse":
255
- element = c.shape
256
- copy_attributes(c, element)
257
265
  subelement = SubElement(xml_tree, SVG_TAG_ELLIPSE)
258
- subelement.set(SVG_ATTR_CENTER_X, str(element.cx))
259
- subelement.set(SVG_ATTR_CENTER_Y, str(element.cy))
260
- subelement.set(SVG_ATTR_RADIUS_X, str(element.rx))
261
- subelement.set(SVG_ATTR_RADIUS_Y, str(element.ry))
266
+ subelement.set(SVG_ATTR_CENTER_X, str(c.cx))
267
+ subelement.set(SVG_ATTR_CENTER_Y, str(c.cy))
268
+ subelement.set(SVG_ATTR_RADIUS_X, str(c.rx))
269
+ subelement.set(SVG_ATTR_RADIUS_Y, str(c.ry))
262
270
  t = Matrix(c.matrix)
263
271
  if not t.is_identity():
264
272
  subelement.set(
@@ -266,7 +274,6 @@ class SVGWriter:
266
274
  f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
267
275
  )
268
276
  elif c.type == "elem image":
269
- element = c.image
270
277
  subelement = SubElement(xml_tree, SVG_TAG_IMAGE)
271
278
  stream = BytesIO()
272
279
  try:
@@ -289,23 +296,19 @@ class SVGWriter:
289
296
  f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
290
297
  )
291
298
  elif c.type == "elem line":
292
- element = c.shape
293
- copy_attributes(c, element)
294
299
  subelement = SubElement(xml_tree, SVG_TAG_LINE)
295
- subelement.set(SVG_ATTR_X1, str(element.x1))
296
- subelement.set(SVG_ATTR_Y1, str(element.y1))
297
- subelement.set(SVG_ATTR_X2, str(element.x2))
298
- subelement.set(SVG_ATTR_Y2, str(element.y2))
300
+ subelement.set(SVG_ATTR_X1, str(c.x1))
301
+ subelement.set(SVG_ATTR_Y1, str(c.y1))
302
+ subelement.set(SVG_ATTR_X2, str(c.x2))
303
+ subelement.set(SVG_ATTR_Y2, str(c.y2))
299
304
  t = c.matrix
300
305
  if not t.is_identity():
301
306
  subelement.set(
302
307
  "transform",
303
308
  f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
304
309
  )
305
-
306
310
  elif c.type == "elem path":
307
- element = c.path
308
- copy_attributes(c, element)
311
+ element = c.geometry.as_path()
309
312
  subelement = SubElement(xml_tree, SVG_TAG_PATH)
310
313
  subelement.set(SVG_ATTR_DATA, element.d(transformed=False))
311
314
  t = c.matrix
@@ -315,9 +318,6 @@ class SVGWriter:
315
318
  f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
316
319
  )
317
320
  elif c.type == "elem point":
318
- element = Point(c.point)
319
- c.x = element.x
320
- c.y = element.y
321
321
  subelement = SubElement(xml_tree, "element")
322
322
  t = c.matrix
323
323
  if not t.is_identity():
@@ -325,14 +325,14 @@ class SVGWriter:
325
325
  "transform",
326
326
  f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
327
327
  )
328
- SVGWriter._write_custom(subelement, c)
328
+ subelement.set("x", str(c.x))
329
+ subelement.set("y", str(c.y))
329
330
  elif c.type == "elem polyline":
330
- element = c.shape
331
- copy_attributes(c, element)
332
331
  subelement = SubElement(xml_tree, SVG_TAG_POLYLINE)
332
+ points = list(c.geometry.as_points())
333
333
  subelement.set(
334
334
  SVG_ATTR_POINTS,
335
- " ".join([f"{e[0]} {e[1]}" for e in element.points]),
335
+ " ".join([f"{e.real} {e.imag}" for e in points]),
336
336
  )
337
337
  t = c.matrix
338
338
  if not t.is_identity():
@@ -341,15 +341,13 @@ class SVGWriter:
341
341
  f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
342
342
  )
343
343
  elif c.type == "elem rect":
344
- element = c.shape
345
- copy_attributes(c, element)
346
344
  subelement = SubElement(xml_tree, SVG_TAG_RECT)
347
- subelement.set(SVG_ATTR_X, str(element.x))
348
- subelement.set(SVG_ATTR_Y, str(element.y))
349
- subelement.set(SVG_ATTR_RADIUS_X, str(element.rx))
350
- subelement.set(SVG_ATTR_RADIUS_Y, str(element.ry))
351
- subelement.set(SVG_ATTR_WIDTH, str(element.width))
352
- subelement.set(SVG_ATTR_HEIGHT, str(element.height))
345
+ subelement.set(SVG_ATTR_X, str(c.x))
346
+ subelement.set(SVG_ATTR_Y, str(c.y))
347
+ subelement.set(SVG_ATTR_RADIUS_X, str(c.rx))
348
+ subelement.set(SVG_ATTR_RADIUS_Y, str(c.ry))
349
+ subelement.set(SVG_ATTR_WIDTH, str(c.width))
350
+ subelement.set(SVG_ATTR_HEIGHT, str(c.height))
353
351
  t = c.matrix
354
352
  if not t.is_identity():
355
353
  subelement.set(
@@ -405,9 +403,6 @@ class SVGWriter:
405
403
  group_element = SubElement(xml_tree, SVG_TAG_GROUP)
406
404
  SVGWriter._write_custom(group_element, c)
407
405
  SVGWriter._write_elements(group_element, c, version)
408
- if hasattr(c, "_operands"):
409
- for q in c._operands:
410
- SVGWriter._write_element(group_element, q, version)
411
406
  return
412
407
  elif c.type == "file":
413
408
  # This is a structural group node of elements. Recurse call to write values.
@@ -531,15 +526,11 @@ class SVGWriter:
531
526
  Write the operations branch part of the tree to disk.
532
527
 
533
528
  @param xml_tree:
534
- @param elem_tree:
529
+ @param op_tree:
535
530
  @return:
536
531
  """
537
532
  for c in op_tree.children:
538
- if c.type.startswith("util") or c.type.startswith("place"):
539
- subelement = SubElement(xml_tree, MEERK40T_XMLS_ID + ":operation")
540
- SVGWriter._write_custom(subelement, c)
541
- else:
542
- SVGWriter._write_operation(xml_tree, c)
533
+ SVGWriter._write_operation(xml_tree, c, version)
543
534
 
544
535
  @staticmethod
545
536
  def _write_regmarks(xml_tree, reg_tree, version):
@@ -550,7 +541,7 @@ class SVGWriter:
550
541
  SVGWriter._write_elements(regmark, reg_tree, version)
551
542
 
552
543
  @staticmethod
553
- def _write_operation(xml_tree, node):
544
+ def _write_operation(xml_tree, node, version):
554
545
  """
555
546
  Write an individual operation. This is any node directly under `branch ops`
556
547
 
@@ -558,17 +549,16 @@ class SVGWriter:
558
549
  @param node:
559
550
  @return:
560
551
  """
561
- for c in node.children:
562
- if c.type.startswith("effect"):
563
- xml_tree = SubElement(xml_tree, SVG_TAG_GROUP)
564
- SVGWriter._write_custom(xml_tree, c)
565
-
566
- subelement = SubElement(xml_tree, MEERK40T_XMLS_ID + ":operation")
552
+ # All operations are groups.
553
+ subelement = SubElement(xml_tree, SVG_TAG_GROUP)
567
554
  subelement.set("type", str(node.type))
555
+
568
556
  if node.label is not None:
569
557
  subelement.set("label", str(node.label))
558
+
570
559
  if node.lock is not None:
571
560
  subelement.set("lock", str(node.lock))
561
+
572
562
  try:
573
563
  for key, value in node.settings.items():
574
564
  if not key:
@@ -583,15 +573,46 @@ class SVGWriter:
583
573
  continue
584
574
  subelement.set(key, str(value))
585
575
  except AttributeError:
586
- pass
576
+ # Node does not have settings, write object dict
577
+ for key, value in node.__dict__.items():
578
+ if not key:
579
+ # If key is None, do not save.
580
+ continue
581
+ if key.startswith("_"):
582
+ continue
583
+ if value is None:
584
+ continue
585
+ if key in (
586
+ "references",
587
+ "tag",
588
+ "type",
589
+ "draw",
590
+ "stroke_width",
591
+ "matrix",
592
+ ):
593
+ # References key from previous loaded version (filter out, rebuild)
594
+ continue
595
+ subelement.set(key, str(value))
596
+
597
+ # Store current node reference values.
598
+ SVGWriter._write_references(subelement, node)
599
+ subelement.set(SVG_ATTR_ID, str(node.id))
600
+
601
+ for c in node.children:
602
+ # Recurse all non-ref nodes
603
+ if c.type == "reference":
604
+ continue
605
+ SVGWriter._write_operation(subelement, c, version)
606
+
607
+ @staticmethod
608
+ def _write_references(subelement, node):
587
609
  contains = list()
588
610
  for c in node.children:
589
611
  if c.type == "reference":
590
612
  c = c.node # Contain direct reference not reference node reference.
591
- contains.append(str(c.id))
613
+ contains.append(c.id)
592
614
  if contains:
593
615
  subelement.set("references", " ".join(contains))
594
- subelement.set(SVG_ATTR_ID, str(node.id))
595
616
 
596
617
  @staticmethod
597
618
  def _write_custom(subelement, node):
@@ -608,15 +629,7 @@ class SVGWriter:
608
629
  # References key from previous loaded version (filter out, rebuild)
609
630
  continue
610
631
  subelement.set(key, str(value))
611
-
612
- contains = list()
613
- for c in node.children:
614
- if c.type == "reference":
615
- c = c.node # Contain direct reference not reference node reference.
616
- contains.append(c.id)
617
- if contains:
618
- subelement.set("references", " ".join(contains))
619
-
632
+ SVGWriter._write_references(subelement, node)
620
633
  subelement.set(SVG_ATTR_ID, str(node.id))
621
634
 
622
635
  @staticmethod
@@ -633,17 +646,25 @@ class SVGWriter:
633
646
 
634
647
 
635
648
  class SVGProcessor:
649
+ """
650
+ SVGProcessor is the parser for svg objects. We employ svgelements to do the actual parsing of the file and convert
651
+ the parsed objects into mk nodes, operations, elements, and regmarks.
652
+
653
+ Special care is taken to load MK specific objects like `note` and `operations`
654
+ """
655
+
636
656
  def __init__(self, elements, load_operations):
637
657
  self.elements = elements
658
+
659
+ self.operation_list = list()
638
660
  self.element_list = list()
639
661
  self.regmark_list = list()
662
+
640
663
  self.reverse = False
641
664
  self.requires_classification = True
642
665
  self.operations_replaced = False
643
666
  self.pathname = None
644
- self.regmark = None
645
667
  self.load_operations = load_operations
646
- self.operation_list = list()
647
668
 
648
669
  # Setting this is bringing as much benefit as anticipated
649
670
  # Both the time to load the file (unexpectedly) and the time
@@ -658,26 +679,39 @@ class SVGProcessor:
658
679
  self.precalc_bbox = True
659
680
 
660
681
  def process(self, svg, pathname):
682
+ """
683
+ Process sends the data to parse and deals with creating the file_node, setting the operations, classifying
684
+ either directly from the data within the file or automatically.
685
+
686
+ @param svg:
687
+ @param pathname:
688
+ @return:
689
+ """
661
690
  self.pathname = pathname
662
- context_node = self.elements.get(type="branch elems")
691
+
692
+ context_node = self.elements.elem_branch
663
693
  file_node = context_node.add(type="file", filepath=pathname)
664
- self.regmark = self.elements.reg_branch
665
694
  file_node.focus()
666
695
 
667
- self.parse(svg, file_node, self.element_list)
696
+ self.parse(svg, file_node, self.element_list, branch="elements")
697
+
668
698
  if self.load_operations and self.operations_replaced:
699
+ for child in list(self.elements.op_branch.children):
700
+ if not hasattr(child, "_ref_load"):
701
+ child.remove_all_children(fast=True, destroy=True)
702
+ child.remove_node(fast=True, destroy=True)
669
703
  self.elements.undo.mark("op-replaced")
670
- self.elements.clear_operations()
671
- for node in self.operation_list:
672
- op = self.elements.op_branch.add_node(node)
673
- for op in self.elements.ops():
674
- if not hasattr(op, "settings"):
675
- # Some special nodes might lack settings these can't have references.
704
+ for op in self.elements.op_branch.flat():
705
+ try:
706
+ refs = op._ref_load
707
+ del op._ref_load
708
+ except AttributeError:
676
709
  continue
677
- refs = op.settings.get("references")
678
710
  if refs is None:
679
711
  continue
712
+
680
713
  self.requires_classification = False
714
+
681
715
  for ref in refs.split(" "):
682
716
  for e in self.element_list:
683
717
  if e.id == ref:
@@ -687,6 +721,14 @@ class SVGProcessor:
687
721
  self.elements.classify(self.element_list)
688
722
 
689
723
  def check_for_mk_path_attributes(self, node, element):
724
+ """
725
+ Checks for some mk special parameters starting with mk. Especially mkparam, and uses this property to fill in
726
+ the functional_parameter attribute for the node.
727
+
728
+ @param node:
729
+ @param element:
730
+ @return:
731
+ """
690
732
  for prop in element.values:
691
733
  lc = element.values.get(prop)
692
734
  if prop.startswith("mk"):
@@ -701,8 +743,21 @@ class SVGProcessor:
701
743
  node.functional_parameter = value
702
744
  except (ValueError, SyntaxError):
703
745
  pass
746
+ elif prop == "mkbcparam":
747
+ try:
748
+ value = ast.literal_eval(lc)
749
+ node.mkbcparam = value
750
+ except (ValueError, SyntaxError):
751
+ pass
704
752
 
705
753
  def check_for_fill_attributes(self, node, element):
754
+ """
755
+ Called for paths and poly lines. This checks for an attribute of `fill-rule` in the SVG and sets the MK equal.
756
+
757
+ @param node:
758
+ @param element:
759
+ @return:
760
+ """
706
761
  lc = element.values.get(SVG_ATTR_FILL_RULE)
707
762
  if lc is not None:
708
763
  nlc = Fillrule.FILLRULE_NONZERO
@@ -714,6 +769,14 @@ class SVGProcessor:
714
769
  node.fillrule = nlc
715
770
 
716
771
  def check_for_line_attributes(self, node, element):
772
+ """
773
+ Called for many element types. This checks for the stroke-cap and line-join attributes in the svgelements
774
+ primitive and sets the node with the mk equal
775
+
776
+ @param node:
777
+ @param element:
778
+ @return:
779
+ """
717
780
  lc = element.values.get(SVG_ATTR_STROKE_CAP)
718
781
  if lc is not None:
719
782
  nlc = Linecap.CAP_ROUND
@@ -765,28 +828,18 @@ class SVGProcessor:
765
828
  return True, abs(Path(element)).first_point
766
829
  return False, None
767
830
 
768
- def parse(self, element, context_node, e_list, uselabel=None):
769
- def is_child(candidate, parent_node):
770
- if candidate is None:
771
- return False
772
- if candidate is parent_node:
773
- return True
774
- if candidate.parent is None:
775
- return False
776
- return is_child(candidate.parent, parent_node)
831
+ def get_tag_label(self, element):
832
+ """
833
+ Gets the tag label from the element. This is usually an inkscape label.
777
834
 
778
- if element.values.get("visibility") == "hidden":
779
- # This does not allow substructures...
780
- # Are we already underneath regmark?
781
- if not is_child(context_node, self.regmark):
782
- context_node = self.regmark
783
- e_list = self.regmark_list
784
- ident = element.id
835
+ Let's see whether we can get the label from an inkscape save
836
+ We only want the 'label' attribute from the current tag, so
837
+ we look at element.values["attributes"]
838
+
839
+ @param element:
840
+ @return:
841
+ """
785
842
 
786
- tag_label = None
787
- # Let's see whether we can get the label from an inkscape save
788
- # We only want the 'label' attribute from the current tag, so
789
- # we look at element.values["attributes"]
790
843
  if "attributes" in element.values:
791
844
  local_dict = element.values["attributes"]
792
845
  else:
@@ -802,20 +855,502 @@ class SVGProcessor:
802
855
  tag_label = local_dict.get(ink_tag)
803
856
  if tag_label == "":
804
857
  tag_label = None
805
- # print ("Found label: %s" %_label)
806
858
  except (AttributeError, KeyError):
807
859
  # Label might simply be "label"
860
+ pass
861
+ if tag_label is None:
808
862
  tag_label = local_dict.get("label")
863
+ return tag_label
864
+
865
+ def _parse_text(self, element, ident, label, lock, context_node, e_list):
866
+ """
867
+ Parses an SVGText object, into an `elem text` node.
868
+
869
+ @param element:
870
+ @param ident:
871
+ @param label:
872
+ @param lock:
873
+ @param context_node:
874
+ @param e_list:
875
+ @return:
876
+ """
877
+
878
+ if element.text is None:
879
+ return
880
+
881
+ decor = element.values.get("text-decoration", "").lower()
882
+ node = context_node.add(
883
+ id=ident,
884
+ text=element.text,
885
+ x=element.x,
886
+ y=element.y,
887
+ font=element.values.get("font"),
888
+ anchor=element.values.get(SVG_ATTR_TEXT_ANCHOR),
889
+ baseline=element.values.get(
890
+ SVG_ATTR_TEXT_ALIGNMENT_BASELINE,
891
+ element.values.get(SVG_ATTR_TEXT_DOMINANT_BASELINE, "baseline"),
892
+ ),
893
+ matrix=element.transform,
894
+ fill=element.fill,
895
+ stroke=element.stroke,
896
+ stroke_width=element.stroke_width,
897
+ stroke_scale=bool(
898
+ SVG_VALUE_NON_SCALING_STROKE
899
+ not in element.values.get(SVG_ATTR_VECTOR_EFFECT, "")
900
+ ),
901
+ underline="underline" in decor,
902
+ strikethrough="line-through" in decor,
903
+ overline="overline" in decor,
904
+ texttransform=element.values.get("text-transform"),
905
+ type="elem text",
906
+ label=label,
907
+ settings=element.values,
908
+ )
909
+ e_list.append(node)
910
+
911
+ def _parse_path(self, element, ident, label, lock, context_node, e_list):
912
+ """
913
+ Parses an SVG Path object.
914
+
915
+ There were a few versions of meerk40t where Path was used to store other save nodes. But, there is not
916
+ enough information to reconstruct those elements.
917
+
918
+ @param element:
919
+ @param ident:
920
+ @param label:
921
+ @param lock:
922
+ @param context_node:
923
+ @param e_list:
924
+ @return:
925
+ """
926
+ if len(element) < 0:
927
+ return
928
+
929
+ if element.values.get("type") == "elem polyline":
930
+ # Type is polyline we should restore the node type if we have sufficient info to do so.
931
+ pass
932
+ if element.values.get("type") == "elem ellipse":
933
+ # There is not enough info to reconstruct this.
934
+ pass
935
+ if element.values.get("type") == "elem rect":
936
+ # There is not enough info to reconstruct this.
937
+ pass
938
+ if element.values.get("type") == "elem line":
939
+ pass
940
+ element.approximate_arcs_with_cubics()
941
+ node = context_node.add(
942
+ path=element, type="elem path", id=ident, label=label, lock=lock
943
+ )
944
+ self.check_for_line_attributes(node, element)
945
+ self.check_for_fill_attributes(node, element)
946
+ self.check_for_mk_path_attributes(node, element)
947
+ e_list.append(node)
948
+
949
+ def _parse_polyline(self, element, ident, label, lock, context_node, e_list):
950
+ """
951
+ Parses svg Polyline and Polygon objects into `elem polyline` nodes.
952
+
953
+ @param element:
954
+ @param ident:
955
+ @param label:
956
+ @param lock:
957
+ @param context_node:
958
+ @param e_list:
959
+ @return:
960
+ """
961
+ if element.is_degenerate():
962
+ return
963
+ node = context_node.add(
964
+ shape=element,
965
+ type="elem polyline",
966
+ id=ident,
967
+ label=label,
968
+ lock=lock,
969
+ )
970
+ self.check_for_line_attributes(node, element)
971
+ self.check_for_fill_attributes(node, element)
972
+ self.check_for_mk_path_attributes(node, element)
973
+ if self.precalc_bbox:
974
+ # bounds will be done here, paintbounds wont...
975
+ if element.transform.is_identity():
976
+ points = element.points
977
+ else:
978
+ points = list(
979
+ map(element.transform.point_in_matrix_space, element.points)
980
+ )
981
+ xmin = min(p.x for p in points if p is not None)
982
+ ymin = min(p.y for p in points if p is not None)
983
+ xmax = max(p.x for p in points if p is not None)
984
+ ymax = max(p.y for p in points if p is not None)
985
+ node._bounds = [
986
+ xmin,
987
+ ymin,
988
+ xmax,
989
+ ymax,
990
+ ]
991
+ node._bounds_dirty = False
992
+ node.revalidate_points()
993
+ node._points_dirty = False
994
+ e_list.append(node)
995
+
996
+ def _parse_ellipse(self, element, ident, label, lock, context_node, e_list):
997
+ """
998
+ Parses the SVG Circle, and Ellipse nodes into `elem ellipse` nodes.
999
+
1000
+ @param element:
1001
+ @param ident:
1002
+ @param label:
1003
+ @param lock:
1004
+ @param context_node:
1005
+ @param e_list:
1006
+ @return:
1007
+ """
1008
+ if element.is_degenerate():
1009
+ return
1010
+ node = context_node.add(
1011
+ shape=element,
1012
+ type="elem ellipse",
1013
+ id=ident,
1014
+ label=label,
1015
+ lock=lock,
1016
+ )
1017
+ e_list.append(node)
1018
+
1019
+ def _parse_rect(self, element, ident, label, lock, context_node, e_list):
1020
+ """
1021
+ Parse SVG Rect objects into `elem rect` objects.
1022
+
1023
+ @param element:
1024
+ @param ident:
1025
+ @param label:
1026
+ @param lock:
1027
+ @param context_node:
1028
+ @param e_list:
1029
+ @return:
1030
+ """
1031
+ if element.is_degenerate():
1032
+ return
1033
+ node = context_node.add(
1034
+ shape=element, type="elem rect", id=ident, label=label, lock=lock
1035
+ )
1036
+ self.check_for_line_attributes(node, element)
1037
+ if self.precalc_bbox:
1038
+ # bounds will be done here, paintbounds wont...
1039
+ points = (
1040
+ Point(element.x, element.y),
1041
+ Point(element.x + element.width, element.y),
1042
+ Point(element.x + element.width, element.y + element.height),
1043
+ Point(element.x, element.y + element.height),
1044
+ )
1045
+ if not element.transform.is_identity():
1046
+ points = list(map(element.transform.point_in_matrix_space, points))
1047
+ xmin = min(p.x for p in points)
1048
+ ymin = min(p.y for p in points)
1049
+ xmax = max(p.x for p in points)
1050
+ ymax = max(p.y for p in points)
1051
+ node._bounds = [
1052
+ xmin,
1053
+ ymin,
1054
+ xmax,
1055
+ ymax,
1056
+ ]
1057
+ node._bounds_dirty = False
1058
+ node.revalidate_points()
1059
+ node._points_dirty = False
1060
+ e_list.append(node)
1061
+
1062
+ def _parse_line(self, element, ident, label, lock, context_node, e_list):
1063
+ """
1064
+ Parse SVG Line objects into `elem line`
1065
+
1066
+ @param element:
1067
+ @param ident:
1068
+ @param label:
1069
+ @param lock:
1070
+ @param context_node:
1071
+ @param e_list:
1072
+ @return:
1073
+ """
1074
+ if element.is_degenerate():
1075
+ return
1076
+ node = context_node.add(
1077
+ shape=element, type="elem line", id=ident, label=label, lock=lock
1078
+ )
1079
+ self.check_for_line_attributes(node, element)
1080
+ if self.precalc_bbox:
1081
+ # bounds will be done here, paintbounds wont...
1082
+ points = (
1083
+ Point(element.x1, element.y1),
1084
+ Point(element.x2, element.y2),
1085
+ )
1086
+ if not element.transform.is_identity():
1087
+ points = list(map(element.transform.point_in_matrix_space, points))
1088
+ xmin = min(p.x for p in points)
1089
+ ymin = min(p.y for p in points)
1090
+ xmax = max(p.x for p in points)
1091
+ ymax = max(p.y for p in points)
1092
+ node._bounds = [
1093
+ xmin,
1094
+ ymin,
1095
+ xmax,
1096
+ ymax,
1097
+ ]
1098
+ node._bounds_dirty = False
1099
+ node.revalidate_points()
1100
+ node._points_dirty = False
1101
+ e_list.append(node)
1102
+
1103
+ def _parse_image(self, element, ident, label, lock, context_node, e_list):
1104
+ """
1105
+ Parse SVG Image objects into either `image raster` or `elem image` objects, potentially other classes.
1106
+
1107
+ @param element:
1108
+ @param ident:
1109
+ @param label:
1110
+ @param lock:
1111
+ @param context_node:
1112
+ @param e_list:
1113
+ @return:
1114
+ """
1115
+ try:
1116
+ element.load(os.path.dirname(self.pathname))
1117
+ try:
1118
+ operations = ast.literal_eval(element.values["operations"])
1119
+ except (ValueError, SyntaxError, KeyError):
1120
+ operations = None
1121
+
1122
+ if element.image is not None:
1123
+ try:
1124
+ dpi = element.image.info["dpi"]
1125
+ except KeyError:
1126
+ dpi = None
1127
+ _dpi = 500
1128
+ if (
1129
+ isinstance(dpi, tuple)
1130
+ and len(dpi) >= 2
1131
+ and dpi[0] != 0
1132
+ and dpi[1] != 0
1133
+ ):
1134
+ _dpi = round((float(dpi[0]) + float(dpi[1])) / 2, 0)
1135
+ _overscan = None
1136
+ try:
1137
+ _overscan = str(element.values.get("overscan"))
1138
+ except (ValueError, TypeError):
1139
+ pass
1140
+ _direction = None
1141
+ try:
1142
+ _direction = int(element.values.get("direction"))
1143
+ except (ValueError, TypeError):
1144
+ pass
1145
+ _invert = None
1146
+ try:
1147
+ _invert = bool(element.values.get("invert") == "True")
1148
+ except (ValueError, TypeError):
1149
+ pass
1150
+ _dither = None
1151
+ try:
1152
+ _dither = bool(element.values.get("dither") == "True")
1153
+ except (ValueError, TypeError):
1154
+ pass
1155
+ _dither_type = None
1156
+ try:
1157
+ _dither_type = element.values.get("dither_type")
1158
+ except (ValueError, TypeError):
1159
+ pass
1160
+ _red = None
1161
+ try:
1162
+ _red = float(element.values.get("red"))
1163
+ except (ValueError, TypeError):
1164
+ pass
1165
+ _green = None
1166
+ try:
1167
+ _green = float(element.values.get("green"))
1168
+ except (ValueError, TypeError):
1169
+ pass
1170
+ _blue = None
1171
+ try:
1172
+ _blue = float(element.values.get("blue"))
1173
+ except (ValueError, TypeError):
1174
+ pass
1175
+ _lightness = None
1176
+ try:
1177
+ _lightness = float(element.values.get("lightness"))
1178
+ except (ValueError, TypeError):
1179
+ pass
1180
+ node = context_node.add(
1181
+ image=element.image,
1182
+ matrix=element.transform,
1183
+ type="elem image",
1184
+ id=ident,
1185
+ overscan=_overscan,
1186
+ direction=_direction,
1187
+ dpi=_dpi,
1188
+ invert=_invert,
1189
+ dither=_dither,
1190
+ dither_type=_dither_type,
1191
+ red=_red,
1192
+ green=_green,
1193
+ blue=_blue,
1194
+ lightness=_lightness,
1195
+ label=label,
1196
+ operations=operations,
1197
+ lock=lock,
1198
+ )
1199
+ e_list.append(node)
1200
+ except OSError:
1201
+ pass
1202
+
1203
+ def _parse_element(self, element, ident, label, lock, context_node, e_list):
1204
+ """
1205
+ SVGElement is type. Generic or unknown node type. These nodes do not have children, these are used in
1206
+ meerk40t contain notes and operations. Element type="elem point", and other points will also load with
1207
+ this code.
1208
+
1209
+ @param element:
1210
+ @param ident:
1211
+ @param label:
1212
+ @param lock:
1213
+ @param context_node:
1214
+ @param e_list:
1215
+ @return:
1216
+ """
1217
+
1218
+ # Fix: we have mixed capitalisaton in full_ns and tag --> adjust
1219
+ tag = element.values.get(SVG_ATTR_TAG).lower()
1220
+ if tag is not None:
1221
+ # We remove the name space.
1222
+ full_ns = f"{{{MEERK40T_NAMESPACE.lower()}}}"
1223
+ if full_ns in tag:
1224
+ tag = tag.replace(full_ns, "")
1225
+
1226
+ # Check if note-type
1227
+ if tag == "note":
1228
+ self.elements.note = element.values.get(SVG_TAG_TEXT)
1229
+ self.elements.signal("note", self.pathname)
1230
+ return
1231
+
1232
+ node_type = element.values.get("type")
1233
+ if node_type is None:
1234
+ # Type is not given. Abort.
1235
+ return
1236
+
1237
+ if node_type == "op":
1238
+ # Meerk40t 0.7.x fallback node types.
1239
+ op_type = element.values.get("operation")
1240
+ if op_type is None:
1241
+ return
1242
+ node_type = f"op {op_type.lower()}"
1243
+ element.values["attributes"]["type"] = node_type
1244
+
1245
+ node_id = element.values.get("id")
1246
+
1247
+ # Get node dictionary.
1248
+ try:
1249
+ attrs = element.values["attributes"]
1250
+ except KeyError:
1251
+ attrs = element.values
1252
+
1253
+ # If type exists in the dictionary, delete it to avoid double attribute issues.
1254
+ try:
1255
+ del attrs["type"]
1256
+ except KeyError:
1257
+ pass
1258
+
1259
+ # Set dictionary types with proper classes.
1260
+ if "lock" in attrs:
1261
+ attrs["lock"] = lock
1262
+ if "transform" in element.values:
1263
+ # Uses chained transforms from primary context.
1264
+ attrs["matrix"] = Matrix(element.values["transform"])
1265
+ if "fill" in attrs:
1266
+ attrs["fill"] = Color(attrs["fill"])
1267
+ if "stroke" in attrs:
1268
+ attrs["stroke"] = Color(attrs["stroke"])
1269
+
1270
+ if tag == "operation":
1271
+ # Operation type node.
1272
+ if not self.load_operations:
1273
+ # We don't do that.
1274
+ return
1275
+ if not self.operations_replaced:
1276
+ self.operations_replaced = True
1277
+
1278
+ try:
1279
+ if node_type == "op hatch":
1280
+ # Special fallback operation, op hatch is an op engrave with an effect hatch within it.
1281
+ node_type = "op engrave"
1282
+ op = self.elements.op_branch.add(type=node_type, **attrs)
1283
+ effect = op.add(type="effect hatch", **attrs)
1284
+ else:
1285
+ op = self.elements.op_branch.add(type=node_type, **attrs)
1286
+ op._ref_load = element.values.get("references")
1287
+
1288
+ if op is None or not hasattr(op, "type") or op.type is None:
1289
+ return
1290
+ if hasattr(op, "validate"):
1291
+ op.validate()
1292
+
1293
+ op.id = node_id
1294
+ self.operation_list.append(op)
1295
+ except AttributeError:
1296
+ # This operation is invalid.
1297
+ return
1298
+ except ValueError:
1299
+ # This operation type failed to bootstrap.
1300
+ return
1301
+
1302
+ elif tag == "element":
1303
+ # Check if SVGElement: element
1304
+ if "settings" in attrs:
1305
+ del attrs[
1306
+ "settings"
1307
+ ] # If settings was set, delete it, or it will mess things up
1308
+ elem = context_node.add(type=node_type, **attrs)
1309
+ try:
1310
+ elem.validate()
1311
+ except AttributeError:
1312
+ pass
1313
+ elem.id = node_id
1314
+ e_list.append(elem)
1315
+
1316
+ def parse(self, element, context_node, e_list, branch=None, uselabel=None):
1317
+ """
1318
+ Parse does the bulk of the work. Given an element, here the base case is an SVG itself, we parse such that
1319
+ any groups will call and check all children recursively, updating the context_node, and passing each element
1320
+ to this same function.
1321
+
1322
+
1323
+ @param element: Element to parse.
1324
+ @param context_node: Current context parent we're writing to.
1325
+ @param e_list: elements list of all the nodes added by this function.
1326
+ @param branch: Branch we are currently adding elements to.
1327
+ @param uselabel:
1328
+ @return:
1329
+ """
1330
+
1331
+ if element.values.get("visibility") == "hidden":
1332
+ if branch != "regmarks":
1333
+ self.parse(
1334
+ element,
1335
+ self.elements.reg_branch,
1336
+ self.regmark_list,
1337
+ branch="regmarks",
1338
+ )
1339
+ return
1340
+
1341
+ ident = element.id
1342
+
809
1343
  if uselabel:
810
1344
  _label = uselabel
811
1345
  else:
812
- _label = tag_label
1346
+ _label = self.get_tag_label(element)
813
1347
 
814
1348
  _lock = None
815
1349
  try:
816
1350
  _lock = bool(element.values.get("lock") == "True")
817
1351
  except (ValueError, TypeError):
818
1352
  pass
1353
+
819
1354
  is_dot, dot_point = SVGProcessor.is_dot(element)
820
1355
  if is_dot:
821
1356
  node = context_node.add(
@@ -829,392 +1364,95 @@ class SVGProcessor:
829
1364
  )
830
1365
  e_list.append(node)
831
1366
  elif isinstance(element, SVGText):
832
- if element.text is None:
833
- return
834
-
835
- decor = element.values.get("text-decoration", "").lower()
836
- node = context_node.add(
837
- id=ident,
838
- text=element.text,
839
- x=element.x,
840
- y=element.y,
841
- font=element.values.get("font"),
842
- anchor=element.values.get(SVG_ATTR_TEXT_ANCHOR),
843
- baseline=element.values.get(
844
- SVG_ATTR_TEXT_ALIGNMENT_BASELINE,
845
- element.values.get(SVG_ATTR_TEXT_DOMINANT_BASELINE, "baseline"),
846
- ),
847
- matrix=element.transform,
848
- fill=element.fill,
849
- stroke=element.stroke,
850
- stroke_width=element.stroke_width,
851
- stroke_scale=bool(
852
- SVG_VALUE_NON_SCALING_STROKE
853
- not in element.values.get(SVG_ATTR_VECTOR_EFFECT, "")
854
- ),
855
- underline="underline" in decor,
856
- strikethrough="line-through" in decor,
857
- overline="overline" in decor,
858
- texttransform=element.values.get("text-transform"),
859
- type="elem text",
860
- label=_label,
861
- settings=element.values,
862
- )
863
- e_list.append(node)
1367
+ self._parse_text(element, ident, _label, _lock, context_node, e_list)
864
1368
  elif isinstance(element, Path):
865
- if len(element) >= 0:
866
- if element.values.get("type") == "elem polyline":
867
- # Type is polyline we should restore the node type if we have sufficient info to do so.
868
- pass
869
- if element.values.get("type") == "elem ellipse":
870
- # There is not enough info to reconstruct this.
871
- pass
872
- if element.values.get("type") == "elem rect":
873
- # There is not enough info to reconstruct this.
874
- pass
875
- if element.values.get("type") == "elem line":
876
- pass
877
- element.approximate_arcs_with_cubics()
878
- node = context_node.add(
879
- path=element, type="elem path", id=ident, label=_label, lock=_lock
880
- )
881
- self.check_for_line_attributes(node, element)
882
- self.check_for_fill_attributes(node, element)
883
- self.check_for_mk_path_attributes(node, element)
884
- e_list.append(node)
1369
+ self._parse_path(element, ident, _label, _lock, context_node, e_list)
885
1370
  elif isinstance(element, (Polygon, Polyline)):
886
- if element.is_degenerate():
887
- return
888
- node = context_node.add(
889
- shape=element,
890
- type="elem polyline",
891
- id=ident,
892
- label=_label,
893
- lock=_lock,
894
- )
895
- self.check_for_line_attributes(node, element)
896
- self.check_for_fill_attributes(node, element)
897
- self.check_for_mk_path_attributes(node, element)
898
- if self.precalc_bbox:
899
- # bounds will be done here, paintbounds wont...
900
- if element.transform.is_identity():
901
- points = element.points
902
- else:
903
- points = list(
904
- map(element.transform.point_in_matrix_space, element.points)
905
- )
906
- xmin = min(p.x for p in points if p is not None)
907
- ymin = min(p.y for p in points if p is not None)
908
- xmax = max(p.x for p in points if p is not None)
909
- ymax = max(p.y for p in points if p is not None)
910
- node._bounds = [
911
- xmin,
912
- ymin,
913
- xmax,
914
- ymax,
915
- ]
916
- node._bounds_dirty = False
917
- node.revalidate_points()
918
- node._points_dirty = False
919
- e_list.append(node)
1371
+ self._parse_polyline(element, ident, _label, _lock, context_node, e_list)
920
1372
  elif isinstance(element, (Circle, Ellipse)):
921
- if element.is_degenerate():
922
- return
923
- node = context_node.add(
924
- shape=element,
925
- type="elem ellipse",
926
- id=ident,
927
- label=_label,
928
- lock=_lock,
929
- )
930
- e_list.append(node)
1373
+ self._parse_ellipse(element, ident, _label, _lock, context_node, e_list)
931
1374
  elif isinstance(element, Rect):
932
- if element.is_degenerate():
933
- return
934
- node = context_node.add(
935
- shape=element, type="elem rect", id=ident, label=_label, lock=_lock
936
- )
937
- self.check_for_line_attributes(node, element)
938
- if self.precalc_bbox:
939
- # bounds will be done here, paintbounds wont...
940
- points = (
941
- Point(element.x, element.y),
942
- Point(element.x + element.width, element.y),
943
- Point(element.x + element.width, element.y + element.height),
944
- Point(element.x, element.y + element.height),
945
- )
946
- if not element.transform.is_identity():
947
- points = list(map(element.transform.point_in_matrix_space, points))
948
- xmin = min(p.x for p in points)
949
- ymin = min(p.y for p in points)
950
- xmax = max(p.x for p in points)
951
- ymax = max(p.y for p in points)
952
- node._bounds = [
953
- xmin,
954
- ymin,
955
- xmax,
956
- ymax,
957
- ]
958
- node._bounds_dirty = False
959
- node.revalidate_points()
960
- node._points_dirty = False
961
- e_list.append(node)
1375
+ self._parse_rect(element, ident, _label, _lock, context_node, e_list)
962
1376
  elif isinstance(element, SimpleLine):
963
- if element.is_degenerate():
964
- return
965
- node = context_node.add(
966
- shape=element, type="elem line", id=ident, label=_label, lock=_lock
967
- )
968
- self.check_for_line_attributes(node, element)
969
- if self.precalc_bbox:
970
- # bounds will be done here, paintbounds wont...
971
- points = (
972
- Point(element.x1, element.y1),
973
- Point(element.x2, element.y2),
974
- )
975
- if not element.transform.is_identity():
976
- points = list(map(element.transform.point_in_matrix_space, points))
977
- xmin = min(p.x for p in points)
978
- ymin = min(p.y for p in points)
979
- xmax = max(p.x for p in points)
980
- ymax = max(p.y for p in points)
981
- node._bounds = [
982
- xmin,
983
- ymin,
984
- xmax,
985
- ymax,
986
- ]
987
- node._bounds_dirty = False
988
- node.revalidate_points()
989
- node._points_dirty = False
990
- e_list.append(node)
1377
+ self._parse_line(element, ident, _label, _lock, context_node, e_list)
991
1378
  elif isinstance(element, SVGImage):
992
- try:
993
- element.load(os.path.dirname(self.pathname))
994
- try:
995
- operations = ast.literal_eval(element.values["operations"])
996
- except (ValueError, SyntaxError, KeyError):
997
- operations = None
998
-
999
- if element.image is not None:
1000
- try:
1001
- dpi = element.image.info["dpi"]
1002
- except KeyError:
1003
- dpi = None
1004
- _dpi = 500
1005
- if (
1006
- isinstance(dpi, tuple)
1007
- and len(dpi) >= 2
1008
- and dpi[0] != 0
1009
- and dpi[1] != 0
1010
- ):
1011
- _dpi = round((float(dpi[0]) + float(dpi[1])) / 2, 0)
1012
- _overscan = None
1013
- try:
1014
- _overscan = str(element.values.get("overscan"))
1015
- except (ValueError, TypeError):
1016
- pass
1017
- _direction = None
1018
- try:
1019
- _direction = int(element.values.get("direction"))
1020
- except (ValueError, TypeError):
1021
- pass
1022
- _invert = None
1023
- try:
1024
- _invert = bool(element.values.get("invert") == "True")
1025
- except (ValueError, TypeError):
1026
- pass
1027
- _dither = None
1028
- try:
1029
- _dither = bool(element.values.get("dither") == "True")
1030
- except (ValueError, TypeError):
1031
- pass
1032
- _dither_type = None
1033
- try:
1034
- _dither_type = element.values.get("dither_type")
1035
- except (ValueError, TypeError):
1036
- pass
1037
- _red = None
1038
- try:
1039
- _red = float(element.values.get("red"))
1040
- except (ValueError, TypeError):
1041
- pass
1042
- _green = None
1043
- try:
1044
- _green = float(element.values.get("green"))
1045
- except (ValueError, TypeError):
1046
- pass
1047
- _blue = None
1048
- try:
1049
- _blue = float(element.values.get("blue"))
1050
- except (ValueError, TypeError):
1051
- pass
1052
- _lightness = None
1053
- try:
1054
- _lightness = float(element.values.get("lightness"))
1055
- except (ValueError, TypeError):
1056
- pass
1057
- node = context_node.add(
1058
- image=element.image,
1059
- matrix=element.transform,
1060
- type="elem image",
1061
- id=ident,
1062
- overscan=_overscan,
1063
- direction=_direction,
1064
- dpi=_dpi,
1065
- invert=_invert,
1066
- dither=_dither,
1067
- dither_type=_dither_type,
1068
- red=_red,
1069
- green=_green,
1070
- blue=_blue,
1071
- lightness=_lightness,
1072
- label=_label,
1073
- operations=operations,
1074
- lock=_lock,
1075
- )
1076
- e_list.append(node)
1077
- except OSError:
1078
- pass
1379
+ self._parse_image(element, ident, _label, _lock, context_node, e_list)
1079
1380
  elif isinstance(element, SVG):
1080
- # SVG is type of group, must go first
1381
+ # SVG is type of group, it must be processed before Group. Nothing special is done with the type.
1081
1382
  if self.reverse:
1082
1383
  for child in reversed(element):
1083
- self.parse(child, context_node, e_list)
1384
+ self.parse(child, context_node, e_list, branch=branch)
1084
1385
  else:
1085
1386
  for child in element:
1086
- self.parse(child, context_node, e_list)
1387
+ self.parse(child, context_node, e_list, branch=branch)
1087
1388
  elif isinstance(element, Group):
1088
- if _label == "regmarks" or ident == "regmarks":
1089
- # We don't need a top-level group here, the regmarks node is a group...
1090
- context_node = self.regmark
1091
- e_list = self.regmark_list
1092
- else:
1093
- # Load group with specific group attributes (if needed)
1094
- e_dict = dict(element.values["attributes"])
1095
- e_type = e_dict.get("type", "group")
1096
- stroke = e_dict.get("stroke")
1097
- for attr in ("type", "id", "label", "stroke"):
1098
- if attr in e_dict:
1099
- del e_dict[attr]
1100
- if stroke is None:
1101
- context_node = context_node.add(
1102
- type=e_type, id=ident, label=_label, **e_dict
1103
- )
1104
- else:
1105
- context_node = context_node.add(
1106
- type=e_type,
1107
- id=ident,
1108
- label=_label,
1109
- stroke=Color(stroke),
1110
- **e_dict,
1111
- )
1112
- if hasattr(context_node, "effect"):
1113
- context_node.effect = False
1389
+ if branch != "regmarks" and (_label == "regmarks" or ident == "regmarks"):
1390
+ # Recurse at same level within regmarks.
1391
+ self.parse(
1392
+ element,
1393
+ self.elements.reg_branch,
1394
+ self.regmark_list,
1395
+ branch="regmarks",
1396
+ )
1397
+ return
1398
+
1399
+ # Load group with specific group attributes (if needed)
1400
+ e_dict = dict(element.values["attributes"])
1401
+ e_type = e_dict.get("type", "group")
1402
+ if branch != "operations" and (
1403
+ e_type.startswith("op ")
1404
+ or e_type.startswith("place ")
1405
+ or e_type.startswith("util ")
1406
+ ):
1407
+ # This is an operations but we are not in operations context.
1408
+ if not self.load_operations:
1409
+ # We don't do that.
1410
+ return
1411
+ self.operations_replaced = True
1412
+ self.parse(
1413
+ element,
1414
+ self.elements.op_branch,
1415
+ self.operation_list,
1416
+ branch="operations",
1417
+ )
1418
+ return
1419
+ if "stroke" in e_dict:
1420
+ e_dict["stroke"] = Color(e_dict.get("stroke"))
1421
+ if "fill" in e_dict:
1422
+ e_dict["fill"] = Color(e_dict.get("fill"))
1423
+ for attr in ("type", "id", "label"):
1424
+ if attr in e_dict:
1425
+ del e_dict[attr]
1426
+ context_node = context_node.add(
1427
+ type=e_type, id=ident, label=_label, **e_dict
1428
+ )
1429
+ context_node._ref_load = element.values.get("references")
1430
+ e_list.append(context_node)
1431
+ if hasattr(context_node, "validate"):
1432
+ context_node.validate()
1114
1433
 
1115
1434
  # recurse to children
1116
1435
  if self.reverse:
1117
1436
  for child in reversed(element):
1118
- self.parse(child, context_node, e_list)
1437
+ self.parse(child, context_node, e_list, branch=branch)
1119
1438
  else:
1120
1439
  for child in element:
1121
- self.parse(child, context_node, e_list)
1440
+ self.parse(child, context_node, e_list, branch=branch)
1122
1441
  elif isinstance(element, Use):
1123
1442
  # recurse to children, but do not subgroup elements.
1124
1443
  # We still use the original label
1125
-
1126
1444
  if self.reverse:
1127
1445
  for child in reversed(element):
1128
- self.parse(child, context_node, e_list, uselabel=_label)
1446
+ self.parse(
1447
+ child, context_node, e_list, branch=branch, uselabel=_label
1448
+ )
1129
1449
  else:
1130
1450
  for child in element:
1131
- self.parse(child, context_node, e_list, uselabel=_label)
1451
+ self.parse(
1452
+ child, context_node, e_list, branch=branch, uselabel=_label
1453
+ )
1132
1454
  else:
1133
- # SVGElement is type. Generic or unknown node type.
1134
- # Fix: we have mixed capitalisaton in full_ns and tag --> adjust
1135
- tag = element.values.get(SVG_ATTR_TAG).lower()
1136
- if tag is not None:
1137
- # We remove the name space.
1138
- full_ns = f"{{{MEERK40T_NAMESPACE.lower()}}}"
1139
- if full_ns in tag:
1140
- tag = tag.replace(full_ns, "")
1141
- # Check if note-type
1142
- if tag == "note":
1143
- self.elements.note = element.values.get(SVG_TAG_TEXT)
1144
- self.elements.signal("note", self.pathname)
1145
- return
1146
- node_type = element.values.get("type")
1147
- if node_type == "op":
1148
- # Meerk40t 0.7.x fallback node types.
1149
- op_type = element.values.get("operation")
1150
- if op_type is None:
1151
- return
1152
- node_type = f"op {op_type.lower()}"
1153
- element.values["attributes"]["type"] = node_type
1154
-
1155
- if node_type is None:
1156
- # Type is not given. Abort.
1157
- return
1158
-
1159
- node_id = element.values.get("id")
1160
- try:
1161
- attrs = element.values["attributes"]
1162
- except KeyError:
1163
- attrs = element.values
1164
- try:
1165
- del attrs["type"]
1166
- except KeyError:
1167
- pass
1168
- if "lock" in attrs:
1169
- attrs["lock"] = _lock
1170
- if "transform" in element.values:
1171
- # Uses chained transforms from primary context.
1172
- attrs["matrix"] = Matrix(element.values["transform"])
1173
- if "fill" in attrs:
1174
- attrs["fill"] = Color(attrs["fill"])
1175
- if "stroke" in attrs:
1176
- attrs["stroke"] = Color(attrs["stroke"])
1177
-
1178
- if self.load_operations and tag == "operation":
1179
- # Check if SVGElement: operation
1180
- if not self.operations_replaced:
1181
- self.operations_replaced = True
1182
-
1183
- try:
1184
- if node_type == "op hatch":
1185
- # Special fallback operation, op hatch is an op engrave with an effect hatch within it.
1186
- node_type = "op engrave"
1187
- op = self.elements.op_branch.create(type=node_type, **attrs)
1188
- op.add(type="effect hatch", **attrs)
1189
- else:
1190
- op = self.elements.op_branch.create(type=node_type, **attrs)
1191
- if op is None or not hasattr(op, "type") or op.type is None:
1192
- return
1193
- if hasattr(op, "validate"):
1194
- op.validate()
1195
- op.id = node_id
1196
- if context_node.type.startswith("effect"):
1197
- op.append_child(context_node)
1198
- self.operation_list.append(op)
1199
- except AttributeError:
1200
- # This operation is invalid.
1201
- return
1202
- except ValueError:
1203
- # This operation type failed to bootstrap.
1204
- return
1205
- elif tag == "element":
1206
- # Check if SVGElement: element
1207
- if "settings" in attrs:
1208
- del attrs[
1209
- "settings"
1210
- ] # If settings was set, delete it, or it will mess things up
1211
- elem = context_node.add(type=node_type, **attrs)
1212
- try:
1213
- elem.validate()
1214
- except AttributeError:
1215
- pass
1216
- elem.id = node_id
1217
- e_list.append(elem)
1455
+ self._parse_element(element, ident, _label, _lock, context_node, e_list)
1218
1456
 
1219
1457
 
1220
1458
  class SVGLoader: