meerk40t 0.9.7020__py2.py3-none-any.whl → 0.9.7040__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 (98) hide show
  1. meerk40t/balormk/clone_loader.py +3 -2
  2. meerk40t/balormk/controller.py +28 -11
  3. meerk40t/balormk/cylindermod.py +1 -0
  4. meerk40t/balormk/device.py +13 -9
  5. meerk40t/balormk/driver.py +9 -2
  6. meerk40t/balormk/galvo_commands.py +3 -1
  7. meerk40t/balormk/gui/gui.py +6 -0
  8. meerk40t/balormk/livelightjob.py +338 -321
  9. meerk40t/balormk/mock_connection.py +4 -3
  10. meerk40t/balormk/usb_connection.py +11 -2
  11. meerk40t/camera/camera.py +19 -14
  12. meerk40t/camera/gui/camerapanel.py +6 -0
  13. meerk40t/core/cutcode/cutcode.py +1 -1
  14. meerk40t/core/cutplan.py +169 -43
  15. meerk40t/core/elements/element_treeops.py +444 -147
  16. meerk40t/core/elements/elements.py +100 -9
  17. meerk40t/core/elements/grid.py +8 -1
  18. meerk40t/core/elements/offset_mk.py +2 -1
  19. meerk40t/core/elements/shapes.py +618 -279
  20. meerk40t/core/elements/tree_commands.py +10 -5
  21. meerk40t/core/node/elem_ellipse.py +18 -8
  22. meerk40t/core/node/elem_image.py +51 -19
  23. meerk40t/core/node/elem_line.py +18 -8
  24. meerk40t/core/node/elem_path.py +18 -8
  25. meerk40t/core/node/elem_point.py +10 -4
  26. meerk40t/core/node/elem_polyline.py +19 -11
  27. meerk40t/core/node/elem_rect.py +18 -8
  28. meerk40t/core/node/elem_text.py +11 -5
  29. meerk40t/core/node/filenode.py +2 -8
  30. meerk40t/core/node/groupnode.py +11 -11
  31. meerk40t/core/node/image_processed.py +11 -5
  32. meerk40t/core/node/image_raster.py +11 -5
  33. meerk40t/core/node/node.py +70 -19
  34. meerk40t/core/node/refnode.py +2 -1
  35. meerk40t/core/planner.py +23 -0
  36. meerk40t/core/svg_io.py +91 -34
  37. meerk40t/core/undos.py +1 -1
  38. meerk40t/core/wordlist.py +1 -0
  39. meerk40t/device/dummydevice.py +7 -1
  40. meerk40t/dxf/dxf_io.py +6 -0
  41. meerk40t/extra/mk_potrace.py +1959 -0
  42. meerk40t/extra/param_functions.py +1 -1
  43. meerk40t/extra/potrace.py +14 -10
  44. meerk40t/extra/vtracer.py +222 -0
  45. meerk40t/grbl/device.py +81 -8
  46. meerk40t/grbl/interpreter.py +1 -1
  47. meerk40t/gui/about.py +21 -3
  48. meerk40t/gui/basicops.py +3 -3
  49. meerk40t/gui/choicepropertypanel.py +1 -4
  50. meerk40t/gui/devicepanel.py +20 -16
  51. meerk40t/gui/gui_mixins.py +8 -1
  52. meerk40t/gui/icons.py +330 -253
  53. meerk40t/gui/laserpanel.py +8 -3
  54. meerk40t/gui/laserrender.py +41 -21
  55. meerk40t/gui/magnetoptions.py +158 -65
  56. meerk40t/gui/materialtest.py +229 -39
  57. meerk40t/gui/navigationpanels.py +229 -24
  58. meerk40t/gui/propertypanels/hatchproperty.py +2 -0
  59. meerk40t/gui/propertypanels/imageproperty.py +160 -106
  60. meerk40t/gui/ribbon.py +6 -1
  61. meerk40t/gui/scenewidgets/gridwidget.py +29 -32
  62. meerk40t/gui/scenewidgets/rectselectwidget.py +190 -192
  63. meerk40t/gui/simulation.py +75 -77
  64. meerk40t/gui/spoolerpanel.py +6 -9
  65. meerk40t/gui/statusbarwidgets/defaultoperations.py +84 -48
  66. meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
  67. meerk40t/gui/themes.py +7 -1
  68. meerk40t/gui/tips.py +15 -1
  69. meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
  70. meerk40t/gui/wxmeerk40t.py +26 -0
  71. meerk40t/gui/wxmmain.py +242 -114
  72. meerk40t/gui/wxmscene.py +180 -4
  73. meerk40t/gui/wxmtree.py +4 -2
  74. meerk40t/gui/wxutils.py +60 -15
  75. meerk40t/image/imagetools.py +130 -66
  76. meerk40t/internal_plugins.py +4 -0
  77. meerk40t/kernel/kernel.py +49 -22
  78. meerk40t/kernel/settings.py +29 -8
  79. meerk40t/lihuiyu/device.py +30 -12
  80. meerk40t/main.py +22 -5
  81. meerk40t/moshi/device.py +20 -6
  82. meerk40t/network/console_server.py +22 -6
  83. meerk40t/newly/device.py +10 -3
  84. meerk40t/newly/gui/gui.py +10 -0
  85. meerk40t/ruida/device.py +22 -2
  86. meerk40t/ruida/gui/gui.py +6 -6
  87. meerk40t/ruida/gui/ruidaoperationproperties.py +1 -10
  88. meerk40t/ruida/loader.py +6 -3
  89. meerk40t/ruida/rdjob.py +3 -3
  90. meerk40t/tools/geomstr.py +195 -39
  91. meerk40t/tools/rasterplotter.py +179 -93
  92. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/METADATA +1 -1
  93. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/RECORD +98 -96
  94. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/WHEEL +1 -1
  95. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/LICENSE +0 -0
  96. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/entry_points.txt +0 -0
  97. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/top_level.txt +0 -0
  98. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/zip-safe +0 -0
@@ -163,19 +163,25 @@ class ImageProcessedNode(Node):
163
163
  return default_map
164
164
 
165
165
  def can_drop(self, drag_node):
166
+ if self.is_a_child_of(drag_node):
167
+ return False
166
168
  # Dragging element into element.
167
169
  return bool(
168
- hasattr(drag_node, "as_geometry") or
169
- hasattr(drag_node, "as_image") or
170
- (drag_node.type.startswith("op ") and drag_node.type != "op dots") or
171
- drag_node.type in ("file", "group")
170
+ hasattr(drag_node, "as_geometry")
171
+ or hasattr(drag_node, "as_image")
172
+ or (drag_node.type.startswith("op ") and drag_node.type != "op dots")
173
+ or drag_node.type in ("file", "group")
172
174
  )
173
175
 
174
176
  def drop(self, drag_node, modify=True, flag=False):
175
177
  # Dragging element into element.
176
178
  if not self.can_drop(drag_node):
177
179
  return False
178
- if hasattr(drag_node, "as_geometry") or hasattr(drag_node, "as_image") or drag_node.type in ("file", "group"):
180
+ if (
181
+ hasattr(drag_node, "as_geometry")
182
+ or hasattr(drag_node, "as_image")
183
+ or drag_node.type in ("file", "group")
184
+ ):
179
185
  if modify:
180
186
  self.insert_sibling(drag_node)
181
187
  return True
@@ -92,19 +92,25 @@ class ImageRasterNode(Node):
92
92
  return default_map
93
93
 
94
94
  def can_drop(self, drag_node):
95
+ if self.is_a_child_of(drag_node):
96
+ return False
95
97
  # Dragging element into element.
96
98
  return bool(
97
- hasattr(drag_node, "as_geometry") or
98
- hasattr(drag_node, "as_image") or
99
- (drag_node.type.startswith("op ") and drag_node.type != "op dots") or
100
- drag_node.type in ("file", "group")
99
+ hasattr(drag_node, "as_geometry")
100
+ or hasattr(drag_node, "as_image")
101
+ or (drag_node.type.startswith("op ") and drag_node.type != "op dots")
102
+ or drag_node.type in ("file", "group")
101
103
  )
102
104
 
103
105
  def drop(self, drag_node, modify=True, flag=False):
104
106
  # Dragging element into element.
105
107
  if not self.can_drop(drag_node):
106
108
  return False
107
- if hasattr(drag_node, "as_geometry") or hasattr(drag_node, "as_image") or drag_node.type in ("file", "group"):
109
+ if (
110
+ hasattr(drag_node, "as_geometry")
111
+ or hasattr(drag_node, "as_image")
112
+ or drag_node.type in ("file", "group")
113
+ ):
108
114
  if modify:
109
115
  self.insert_sibling(drag_node)
110
116
  return True
@@ -193,7 +193,7 @@ class Node:
193
193
  def targeted(self, value):
194
194
  self._target = value
195
195
  self.notify_targeted(self)
196
-
196
+
197
197
  @property
198
198
  def expanded(self):
199
199
  return self._expanded
@@ -457,17 +457,23 @@ class Node:
457
457
 
458
458
  def restore_tree(self, tree_data):
459
459
  # Takes a backup and reapplies it again to the tree
460
- # Caveat: we can't just simply take the backup and load it into the tree,
461
- # although it is already a perfectly independent copy.
460
+ # Caveat: we can't just simply take the backup and load it into the tree,
461
+ # although it is already a perfectly independent copy.
462
462
  # self._children.extend(tree_data)
463
- # If loaded directly as above then this stored state will be used
464
- # as the basis for further modifications consequently changing the
463
+ # If loaded directly as above then this stored state will be used
464
+ # as the basis for further modifications consequently changing the
465
465
  # original data (as it is still the original structure) used in the undostack.
466
466
  # tree_data contains the copied branch nodes
467
-
467
+
468
468
  self._children.clear()
469
469
  links = {id(self): (self, None)}
470
- attrib_list = ("_selected", "_emphasized", "_emphasized_time", "_highlighted", "_expanded")
470
+ attrib_list = (
471
+ "_selected",
472
+ "_emphasized",
473
+ "_emphasized_time",
474
+ "_highlighted",
475
+ "_expanded",
476
+ )
471
477
  for c in tree_data:
472
478
  c._build_copy_nodes(links=links)
473
479
  node_copy = copy(c)
@@ -499,9 +505,12 @@ class Node:
499
505
  node_copy._parent = copied_parent
500
506
  copied_parent._children.append(node_copy)
501
507
  if node.type == "reference":
502
- original_referenced, copied_referenced = links[id(node.node)]
503
- node_copy.node = copied_referenced
504
- copied_referenced._references.append(node_copy)
508
+ try:
509
+ original_referenced, copied_referenced = links[id(node.node)]
510
+ node_copy.node = copied_referenced
511
+ copied_referenced._references.append(node_copy)
512
+ except KeyError:
513
+ pass
505
514
 
506
515
  def _validate_tree(self):
507
516
  for c in self._children:
@@ -525,12 +534,23 @@ class Node:
525
534
  """
526
535
  if links is None:
527
536
  links = {id(self): (self, None)}
528
- attrib_list = ("_selected", "_emphasized", "_emphasized_time", "_highlighted", "_expanded")
537
+ attrib_list = (
538
+ "_selected",
539
+ "_emphasized",
540
+ "_emphasized_time",
541
+ "_highlighted",
542
+ "_expanded",
543
+ "_translated_text",
544
+ )
529
545
  for c in self._children:
530
546
  c._build_copy_nodes(links=links)
531
547
  node_copy = copy(c)
532
548
  for att in attrib_list:
533
- if getattr(node_copy, att) != getattr(c, att):
549
+ if not hasattr(c, att):
550
+ continue
551
+ if not hasattr(node_copy, att) or getattr(node_copy, att) != getattr(
552
+ c, att
553
+ ):
534
554
  # print (f"Strange {att} not identical, fixing")
535
555
  setattr(node_copy, att, getattr(c, att))
536
556
  node_copy._root = self._root
@@ -580,7 +600,7 @@ class Node:
580
600
  result = "<invalid pattern>"
581
601
  return result
582
602
 
583
- def default_map(self, default_map=None): # , skip_label=False
603
+ def default_map(self, default_map=None): # , skip_label=False
584
604
  if default_map is None:
585
605
  default_map = self._default_map
586
606
  default_map["id"] = str(self.id) if self.id is not None else "-"
@@ -814,7 +834,9 @@ class Node:
814
834
  node = self
815
835
  self._parent.notify_modified(node=node, **kwargs)
816
836
 
817
- def notify_translated(self, node=None, dx=0, dy=0, invalidate=False, interim=False, **kwargs):
837
+ def notify_translated(
838
+ self, node=None, dx=0, dy=0, invalidate=False, interim=False, **kwargs
839
+ ):
818
840
  if invalidate:
819
841
  self.set_dirty_bounds()
820
842
  if self._parent is not None:
@@ -826,7 +848,15 @@ class Node:
826
848
  )
827
849
 
828
850
  def notify_scaled(
829
- self, node=None, sx=1, sy=1, ox=0, oy=0, invalidate=False, interim=False, **kwargs
851
+ self,
852
+ node=None,
853
+ sx=1,
854
+ sy=1,
855
+ ox=0,
856
+ oy=0,
857
+ invalidate=False,
858
+ interim=False,
859
+ **kwargs,
830
860
  ):
831
861
  if invalidate:
832
862
  self.set_dirty_bounds()
@@ -835,7 +865,14 @@ class Node:
835
865
  node = self
836
866
  # Any change to position / size needs a recalculation of the bounds
837
867
  self._parent.notify_scaled(
838
- node=node, sx=sx, sy=sy, ox=ox, oy=oy, invalidate=True, interim=interim, **kwargs
868
+ node=node,
869
+ sx=sx,
870
+ sy=sy,
871
+ ox=ox,
872
+ oy=oy,
873
+ invalidate=True,
874
+ interim=interim,
875
+ **kwargs,
839
876
  )
840
877
 
841
878
  def notify_altered(self, node=None, **kwargs):
@@ -950,6 +987,7 @@ class Node:
950
987
  This is a special case of the modified call, we are scaling
951
988
  the node without fundamentally altering its properties
952
989
  """
990
+
953
991
  def apply_it(box):
954
992
  x0, y0, x1, y1 = box
955
993
  if sx != 1.0:
@@ -1217,7 +1255,9 @@ class Node:
1217
1255
  If the node exists elsewhere in the tree it will be removed from that location.
1218
1256
  """
1219
1257
  reference_sibling = self
1220
- source_siblings = None if new_sibling.parent is None else new_sibling.parent.children
1258
+ source_siblings = (
1259
+ None if new_sibling.parent is None else new_sibling.parent.children
1260
+ )
1221
1261
  destination_siblings = reference_sibling.parent.children
1222
1262
 
1223
1263
  if source_siblings:
@@ -1331,7 +1371,8 @@ class Node:
1331
1371
  if children:
1332
1372
  self.remove_all_children(fast=fast)
1333
1373
  if self._parent:
1334
- self._parent._children.remove(self)
1374
+ if self in self._parent._children:
1375
+ self._parent._children.remove(self)
1335
1376
  self._parent.set_dirty_bounds()
1336
1377
  if not fast:
1337
1378
  self.notify_detached(self)
@@ -1353,6 +1394,14 @@ class Node:
1353
1394
  child.remove_all_children(fast=fast, destroy=destroy)
1354
1395
  child.remove_node(fast=fast, destroy=destroy)
1355
1396
 
1397
+ def is_a_child_of(self, node):
1398
+ candidate = self
1399
+ while candidate is not None:
1400
+ if candidate is node:
1401
+ return True
1402
+ candidate = candidate.parent
1403
+ return False
1404
+
1356
1405
  def has_ancestor(self, type):
1357
1406
  """
1358
1407
  Return whether this node has an ancestor node that matches the given type, or matches the major type.
@@ -1391,7 +1440,9 @@ class Node:
1391
1440
  dest.insert_node(self, pos=pos)
1392
1441
 
1393
1442
  @staticmethod
1394
- def union_bounds(nodes, bounds=None, attr="bounds", ignore_locked=True, ignore_hidden=False):
1443
+ def union_bounds(
1444
+ nodes, bounds=None, attr="bounds", ignore_locked=True, ignore_hidden=False
1445
+ ):
1395
1446
  """
1396
1447
  Returns the union of the node list given, optionally unioned the given bounds value
1397
1448
 
@@ -54,5 +54,6 @@ class ReferenceNode(Node):
54
54
  return False
55
55
 
56
56
  def notify_destroyed(self, node=None, **kwargs):
57
- self.node._references.remove(self)
57
+ if self.node is not None and self in self.node._references:
58
+ self.node._references.remove(self)
58
59
  super().notify_destroyed()
meerk40t/core/planner.py CHANGED
@@ -181,6 +181,29 @@ def plugin(kernel, lifecycle=None):
181
181
  "section": "_20_Reducing Movements",
182
182
  "conditional": (context, "opt_reduce_travel"),
183
183
  },
184
+ {
185
+ "attr": "opt_stitching",
186
+ "object": context,
187
+ "default": False,
188
+ "type": bool,
189
+ "label": _("Combine path segments"),
190
+ "tip":
191
+ _("Stitch segments together that are very close (ideally having joint start/end points).") + "\n" +
192
+ _("Only inside a single cut/engrave operation."),
193
+ "page": "Optimisations",
194
+ "section": "_05_Stitching",
195
+ },
196
+ {
197
+ "attr": "opt_stitch_tolerance",
198
+ "object": context,
199
+ "default": "0",
200
+ "type": Length,
201
+ "label": _("Tolerance"),
202
+ "tip": _("Tolerance to decide whether two path segments should be joined."),
203
+ "page": "Optimisations",
204
+ "section": "_05_Stitching",
205
+ "conditional": (context, "opt_stitching"),
206
+ },
184
207
  {
185
208
  "attr": "opt_inner_first",
186
209
  "object": context,
meerk40t/core/svg_io.py CHANGED
@@ -124,7 +124,6 @@ def plugin(kernel, lifecycle=None):
124
124
  "page": "Input/Output",
125
125
  "section": "Input",
126
126
  },
127
-
128
127
  ]
129
128
  kernel.register_choices("preferences", choices)
130
129
  # The order is relevant as both loaders support SVG
@@ -237,7 +236,9 @@ class SVGWriter:
237
236
  if elements.last_file_autoexec is not None:
238
237
  subelement = SubElement(root, "autoexec")
239
238
  subelement.set("autoexec", str(elements.last_file_autoexec))
240
- subelement.set("autoexec-active", str(elements.last_file_autoexec_active))
239
+ subelement.set(
240
+ "autoexec-active", str(elements.last_file_autoexec_active)
241
+ )
241
242
 
242
243
  SVGWriter._write_tree(root, elements._tree, version)
243
244
 
@@ -279,7 +280,7 @@ class SVGWriter:
279
280
  if len(c.children) > 1:
280
281
  flag = False
281
282
  return flag
282
-
283
+
283
284
  if c.type == "elem ellipse":
284
285
  subelement = SubElement(xml_tree, SVG_TAG_ELLIPSE)
285
286
  subelement.set(SVG_ATTR_CENTER_X, str(c.cx))
@@ -632,7 +633,12 @@ class SVGWriter:
632
633
  pass
633
634
  # Node does not have settings, write object dict
634
635
  for key, value in node.__dict__.items():
635
- if not key or key.startswith("_") or key in saved_attributes or value is None:
636
+ if (
637
+ not key
638
+ or key.startswith("_")
639
+ or key in saved_attributes
640
+ or value is None
641
+ ):
636
642
  continue
637
643
  if key in (
638
644
  "references",
@@ -663,7 +669,8 @@ class SVGWriter:
663
669
  for c in node.children:
664
670
  if c.type == "reference":
665
671
  c = c.node # Contain direct reference not reference node reference.
666
- contains.append(c.id)
672
+ if c.id is not None: # Something strange happened here...
673
+ contains.append(c.id)
667
674
  if contains:
668
675
  subelement.set("references", " ".join(contains))
669
676
 
@@ -706,7 +713,13 @@ class SVGProcessor:
706
713
  Special care is taken to load MK specific objects like `note` and `operations`
707
714
  """
708
715
 
709
- def __init__(self, elements, load_operations, load_hidden_to_regmarks = True, reuse_operations=True):
716
+ def __init__(
717
+ self,
718
+ elements,
719
+ load_operations,
720
+ load_hidden_to_regmarks=True,
721
+ reuse_operations=True,
722
+ ):
710
723
  self.elements = elements
711
724
 
712
725
  self.operation_list = list()
@@ -789,7 +802,7 @@ class SVGProcessor:
789
802
  self.elements.classify(self.element_list)
790
803
 
791
804
  def check_for_bound_information(self, node, element):
792
- # Do we have existing boundary information?
805
+ # Do we have existing boundary information?
793
806
  if "bounds" not in element.values:
794
807
  return False
795
808
  bbstr = element.values["bounds"]
@@ -816,15 +829,16 @@ class SVGProcessor:
816
829
  bbox[idx] = val
817
830
  except Exception:
818
831
  # Whatever it was, we don't continue...
819
- pass
832
+ pass
820
833
  node._paint_bounds = list(bbox)
821
834
  node._paint_bounds_dirty = False
822
835
  return True
823
836
 
824
- def check_for_mk_path_attributes(self, node, element):
837
+ def check_for_mk_path_attributes(self, node, element, skip=None):
825
838
  """
826
839
  Checks for some mk special parameters starting with mk. Especially mkparam, and uses this property to fill in
827
- the functional_parameter attribute for the node.
840
+ the functional_parameter attribute for the node. This can be skipped if needed (eg. for basic shapes) as this
841
+ is not needed for them.
828
842
 
829
843
  @param node:
830
844
  @param element:
@@ -833,7 +847,8 @@ class SVGProcessor:
833
847
  for prop in element.values:
834
848
  lc = element.values.get(prop)
835
849
  if prop.startswith("mk"):
836
- # print (f"Property: {prop} = [{type(lc).__name__}] {lc}")
850
+ if skip and prop in skip:
851
+ continue
837
852
  if lc is not None:
838
853
  setattr(node, prop, lc)
839
854
  # This needs to be done as some node types are not based on Parameters
@@ -975,7 +990,9 @@ class SVGProcessor:
975
990
  return tag_label
976
991
  return local_dict.get("label")
977
992
 
978
- def _parse_text(self, element, ident, label, lock, context_node, e_list, set_hidden):
993
+ def _parse_text(
994
+ self, element, ident, label, lock, context_node, e_list, set_hidden
995
+ ):
979
996
  """
980
997
  Parses an SVGText object, into an `elem text` node.
981
998
 
@@ -1024,7 +1041,9 @@ class SVGProcessor:
1024
1041
  self.check_for_bound_information(node, element)
1025
1042
  e_list.append(node)
1026
1043
 
1027
- def _parse_path(self, element, ident, label, lock, context_node, e_list, set_hidden):
1044
+ def _parse_path(
1045
+ self, element, ident, label, lock, context_node, e_list, set_hidden
1046
+ ):
1028
1047
  """
1029
1048
  Parses an SVG Path object.
1030
1049
 
@@ -1055,7 +1074,12 @@ class SVGProcessor:
1055
1074
  pass
1056
1075
  element.approximate_arcs_with_cubics()
1057
1076
  node = context_node.add(
1058
- path=element, type="elem path", id=ident, label=label, lock=lock, hidden=set_hidden
1077
+ path=element,
1078
+ type="elem path",
1079
+ id=ident,
1080
+ label=label,
1081
+ lock=lock,
1082
+ hidden=set_hidden,
1059
1083
  )
1060
1084
  self.check_for_label_display(node, element)
1061
1085
  self.check_for_line_attributes(node, element)
@@ -1064,7 +1088,9 @@ class SVGProcessor:
1064
1088
  self.check_for_bound_information(node, element)
1065
1089
  e_list.append(node)
1066
1090
 
1067
- def _parse_polyline(self, element, ident, label, lock, context_node, e_list, set_hidden):
1091
+ def _parse_polyline(
1092
+ self, element, ident, label, lock, context_node, e_list, set_hidden
1093
+ ):
1068
1094
  """
1069
1095
  Parses svg Polyline and Polygon objects into `elem polyline` nodes.
1070
1096
 
@@ -1089,7 +1115,7 @@ class SVGProcessor:
1089
1115
  self.check_for_label_display(node, element)
1090
1116
  self.check_for_line_attributes(node, element)
1091
1117
  self.check_for_fill_attributes(node, element)
1092
- self.check_for_mk_path_attributes(node, element)
1118
+ self.check_for_mk_path_attributes(node, element, skip=("mkparam",))
1093
1119
  if not self.check_for_bound_information(node, element) and self.precalc_bbox:
1094
1120
  # bounds will be done here, paintbounds won't...
1095
1121
  if element.transform.is_identity():
@@ -1113,7 +1139,9 @@ class SVGProcessor:
1113
1139
  node._points_dirty = False
1114
1140
  e_list.append(node)
1115
1141
 
1116
- def _parse_ellipse(self, element, ident, label, lock, context_node, e_list, set_hidden):
1142
+ def _parse_ellipse(
1143
+ self, element, ident, label, lock, context_node, e_list, set_hidden
1144
+ ):
1117
1145
  """
1118
1146
  Parses the SVG Circle, and Ellipse nodes into `elem ellipse` nodes.
1119
1147
 
@@ -1137,11 +1165,13 @@ class SVGProcessor:
1137
1165
  )
1138
1166
  self.check_for_label_display(node, element)
1139
1167
  self.check_for_line_attributes(node, element)
1140
- self.check_for_mk_path_attributes(node, element)
1168
+ self.check_for_mk_path_attributes(node, element, skip=("mkparam",))
1141
1169
  self.check_for_bound_information(node, element)
1142
1170
  e_list.append(node)
1143
1171
 
1144
- def _parse_rect(self, element, ident, label, lock, context_node, e_list, set_hidden):
1172
+ def _parse_rect(
1173
+ self, element, ident, label, lock, context_node, e_list, set_hidden
1174
+ ):
1145
1175
  """
1146
1176
  Parse SVG Rect objects into `elem rect` objects.
1147
1177
 
@@ -1165,7 +1195,7 @@ class SVGProcessor:
1165
1195
  )
1166
1196
  self.check_for_label_display(node, element)
1167
1197
  self.check_for_line_attributes(node, element)
1168
- self.check_for_mk_path_attributes(node, element)
1198
+ self.check_for_mk_path_attributes(node, element, skip=("mkparam",))
1169
1199
  if not self.check_for_bound_information(node, element) and self.precalc_bbox:
1170
1200
  # bounds will be done here, paintbounds won't...
1171
1201
  points = (
@@ -1191,7 +1221,9 @@ class SVGProcessor:
1191
1221
  node._points_dirty = False
1192
1222
  e_list.append(node)
1193
1223
 
1194
- def _parse_line(self, element, ident, label, lock, context_node, e_list, set_hidden):
1224
+ def _parse_line(
1225
+ self, element, ident, label, lock, context_node, e_list, set_hidden
1226
+ ):
1195
1227
  """
1196
1228
  Parse SVG Line objects into `elem line`
1197
1229
 
@@ -1215,7 +1247,7 @@ class SVGProcessor:
1215
1247
  )
1216
1248
  self.check_for_label_display(node, element)
1217
1249
  self.check_for_line_attributes(node, element)
1218
- self.check_for_mk_path_attributes(node, element)
1250
+ self.check_for_mk_path_attributes(node, element, skip=("mkparam",))
1219
1251
  if not self.check_for_bound_information(node, element) and self.precalc_bbox:
1220
1252
  # bounds will be done here, paintbounds won't...
1221
1253
  points = (
@@ -1239,7 +1271,9 @@ class SVGProcessor:
1239
1271
  node._points_dirty = False
1240
1272
  e_list.append(node)
1241
1273
 
1242
- def _parse_image(self, element, ident, label, lock, context_node, e_list, set_hidden):
1274
+ def _parse_image(
1275
+ self, element, ident, label, lock, context_node, e_list, set_hidden
1276
+ ):
1243
1277
  """
1244
1278
  Parse SVG Image objects into either `image raster` or `elem image` objects, potentially other classes.
1245
1279
 
@@ -1516,7 +1550,6 @@ class SVGProcessor:
1516
1550
  if branch not in ("elements", "regmarks"):
1517
1551
  return
1518
1552
  if element.values.get("visibility") == "hidden" or display == "none":
1519
-
1520
1553
  if self.load_hidden_to_regmarks:
1521
1554
  if branch != "regmarks":
1522
1555
  self.parse(
@@ -1554,19 +1587,33 @@ class SVGProcessor:
1554
1587
  self.check_for_bound_information(node, element)
1555
1588
  e_list.append(node)
1556
1589
  elif isinstance(element, SVGText):
1557
- self._parse_text(element, ident, _label, _lock, context_node, e_list, set_hidden)
1590
+ self._parse_text(
1591
+ element, ident, _label, _lock, context_node, e_list, set_hidden
1592
+ )
1558
1593
  elif isinstance(element, Path):
1559
- self._parse_path(element, ident, _label, _lock, context_node, e_list, set_hidden)
1594
+ self._parse_path(
1595
+ element, ident, _label, _lock, context_node, e_list, set_hidden
1596
+ )
1560
1597
  elif isinstance(element, (Polygon, Polyline)):
1561
- self._parse_polyline(element, ident, _label, _lock, context_node, e_list, set_hidden)
1598
+ self._parse_polyline(
1599
+ element, ident, _label, _lock, context_node, e_list, set_hidden
1600
+ )
1562
1601
  elif isinstance(element, (Circle, Ellipse)):
1563
- self._parse_ellipse(element, ident, _label, _lock, context_node, e_list, set_hidden)
1602
+ self._parse_ellipse(
1603
+ element, ident, _label, _lock, context_node, e_list, set_hidden
1604
+ )
1564
1605
  elif isinstance(element, Rect):
1565
- self._parse_rect(element, ident, _label, _lock, context_node, e_list, set_hidden)
1606
+ self._parse_rect(
1607
+ element, ident, _label, _lock, context_node, e_list, set_hidden
1608
+ )
1566
1609
  elif isinstance(element, SimpleLine):
1567
- self._parse_line(element, ident, _label, _lock, context_node, e_list, set_hidden)
1610
+ self._parse_line(
1611
+ element, ident, _label, _lock, context_node, e_list, set_hidden
1612
+ )
1568
1613
  elif isinstance(element, SVGImage):
1569
- self._parse_image(element, ident, _label, _lock, context_node, e_list, set_hidden)
1614
+ self._parse_image(
1615
+ element, ident, _label, _lock, context_node, e_list, set_hidden
1616
+ )
1570
1617
  elif isinstance(element, SVG):
1571
1618
  # SVG is type of group, it must be processed before Group. Nothing special is done with the type.
1572
1619
  if self.reverse:
@@ -1708,7 +1755,9 @@ class SVGProcessor:
1708
1755
  file_node = context_node.add(type="file", filepath=self.pathname)
1709
1756
  for node in self.regmark_list:
1710
1757
  if node._parent is context_node:
1711
- if node.type == "group" and (node.id == "regmarks" or node.label == "regmarks"):
1758
+ if node.type == "group" and (
1759
+ node.id == "regmarks" or node.label == "regmarks"
1760
+ ):
1712
1761
  for n in list(node.children):
1713
1762
  file_node.append_child(n)
1714
1763
  node.remove_node() # Removing group/file node.
@@ -1740,6 +1789,7 @@ class SVGProcessor:
1740
1789
  if needs_update:
1741
1790
  self.elements.process_keyhole_updates(None)
1742
1791
 
1792
+
1743
1793
  class SVGLoader:
1744
1794
  """
1745
1795
  SVG loader - loading elements, regmarks and operations
@@ -1783,7 +1833,12 @@ class SVGLoader:
1783
1833
  reuse = elements_service.reuse_operations_on_load
1784
1834
  to_regmarks = elements_service.load_hidden_to_regmarks
1785
1835
  elements_service._loading_cleared = True
1786
- svg_processor = SVGProcessor(elements_service, load_operations=True, reuse_operations=reuse, load_hidden_to_regmarks=to_regmarks)
1836
+ svg_processor = SVGProcessor(
1837
+ elements_service,
1838
+ load_operations=True,
1839
+ reuse_operations=reuse,
1840
+ load_hidden_to_regmarks=to_regmarks,
1841
+ )
1787
1842
  svg_processor.process(svg, pathname)
1788
1843
  svg_processor.cleanup()
1789
1844
  return True
@@ -1830,7 +1885,9 @@ class SVGLoaderPlain:
1830
1885
  raise BadFileError(str(e)) from e
1831
1886
  elements_service._loading_cleared = True
1832
1887
  to_regmarks = elements_service.load_hidden_to_regmarks
1833
- svg_processor = SVGProcessor(elements_service, load_operations=False, load_hidden_to_regmarks=to_regmarks)
1888
+ svg_processor = SVGProcessor(
1889
+ elements_service, load_operations=False, load_hidden_to_regmarks=to_regmarks
1890
+ )
1834
1891
  svg_processor.process(svg, pathname)
1835
1892
  svg_processor.cleanup()
1836
1893
  return True
meerk40t/core/undos.py CHANGED
@@ -81,7 +81,7 @@ class Undo:
81
81
  elif self._undo_index < len(self._undo_stack) and self._undo_stack[self._undo_index].hold:
82
82
  # Just add another one on top of it
83
83
  self._undo_index += 1
84
- elif self._undo_stack[self._undo_index].message == self.LAST_STATE:
84
+ elif self._undo_index < len(self._undo_stack) and self._undo_stack[self._undo_index].message == self.LAST_STATE:
85
85
  # Will be overwritten
86
86
  pass
87
87
  elif self._undo_index < len(self._undo_stack) - 1 and self._undo_stack[self._undo_index + 1].message != self.LAST_STATE:
meerk40t/core/wordlist.py CHANGED
@@ -394,6 +394,7 @@ class Wordlist:
394
394
 
395
395
  def load_csv_file(self, filename, force_header=None):
396
396
  self.empty_csv()
397
+ ct = 0
397
398
  headers = []
398
399
  decoder = EncodingDetectFile()
399
400
  result = decoder.load(filename)
@@ -1,7 +1,7 @@
1
1
  from meerk40t.core.spoolers import Spooler
2
2
  from meerk40t.core.view import View
3
- from meerk40t.kernel import Service, signal_listener
4
3
  from meerk40t.device.devicechoices import get_effect_choices
4
+ from meerk40t.kernel import Service, signal_listener
5
5
 
6
6
  from .mixins import Status
7
7
 
@@ -161,3 +161,9 @@ class DummyDevice(Service, Status):
161
161
  @return: the location in device native units for the current known position.
162
162
  """
163
163
  return self.native_x, self.native_y
164
+
165
+ def location(self):
166
+ """
167
+ Provide information about the device interface
168
+ """
169
+ return "mock"
meerk40t/dxf/dxf_io.py CHANGED
@@ -251,6 +251,8 @@ class DXFProcessor:
251
251
  elif dxftype == "ELLIPSE":
252
252
  center = (entity.dxf.center) # Center point of the ellipse (3D, but we'll use x,y)
253
253
  major_axis = entity.dxf.major_axis # Vector representing the major axis
254
+ minor_axis = entity.minor_axis # Vector representing the minor axis
255
+ # They should have the same sign, if they are different then they are mirrored?!
254
256
  ratio = entity.dxf.ratio # Ratio of minor to major axis
255
257
  start_angle, end_angle = get_angles(entity)
256
258
 
@@ -262,6 +264,10 @@ class DXFProcessor:
262
264
  major_axis[0] ** 2 + major_axis[1] ** 2
263
265
  ) # Length of the major axis (in XY plane)
264
266
  b = a * ratio # Length of the minor axis
267
+ # Different signs? Inverse
268
+ if major_axis[0] * minor_axis[1] < 0:
269
+ b *= -1
270
+
265
271
  # geom = Geomstr.ellipse(
266
272
  # start_t=start_angle,
267
273
  # end_t=end_angle,