meerk40t 0.9.7030__py2.py3-none-any.whl → 0.9.7050__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 (85) hide show
  1. meerk40t/balormk/clone_loader.py +3 -2
  2. meerk40t/balormk/controller.py +38 -13
  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/cutplan.py +101 -78
  14. meerk40t/core/elements/element_treeops.py +435 -140
  15. meerk40t/core/elements/elements.py +100 -9
  16. meerk40t/core/elements/shapes.py +259 -72
  17. meerk40t/core/elements/tree_commands.py +10 -5
  18. meerk40t/core/node/blobnode.py +19 -4
  19. meerk40t/core/node/elem_ellipse.py +18 -8
  20. meerk40t/core/node/elem_image.py +51 -19
  21. meerk40t/core/node/elem_line.py +18 -8
  22. meerk40t/core/node/elem_path.py +18 -8
  23. meerk40t/core/node/elem_point.py +10 -4
  24. meerk40t/core/node/elem_polyline.py +19 -11
  25. meerk40t/core/node/elem_rect.py +18 -8
  26. meerk40t/core/node/elem_text.py +11 -5
  27. meerk40t/core/node/filenode.py +2 -8
  28. meerk40t/core/node/groupnode.py +11 -11
  29. meerk40t/core/node/image_processed.py +11 -5
  30. meerk40t/core/node/image_raster.py +11 -5
  31. meerk40t/core/node/node.py +64 -16
  32. meerk40t/core/node/refnode.py +2 -1
  33. meerk40t/core/planner.py +25 -11
  34. meerk40t/core/svg_io.py +91 -34
  35. meerk40t/device/dummydevice.py +7 -1
  36. meerk40t/extra/vtracer.py +222 -0
  37. meerk40t/grbl/device.py +96 -9
  38. meerk40t/grbl/driver.py +15 -5
  39. meerk40t/gui/about.py +20 -0
  40. meerk40t/gui/devicepanel.py +20 -16
  41. meerk40t/gui/gui_mixins.py +4 -0
  42. meerk40t/gui/icons.py +330 -253
  43. meerk40t/gui/laserpanel.py +27 -3
  44. meerk40t/gui/laserrender.py +41 -21
  45. meerk40t/gui/magnetoptions.py +158 -65
  46. meerk40t/gui/materialtest.py +569 -310
  47. meerk40t/gui/navigationpanels.py +229 -24
  48. meerk40t/gui/propertypanels/hatchproperty.py +2 -0
  49. meerk40t/gui/propertypanels/imageproperty.py +160 -106
  50. meerk40t/gui/propertypanels/wobbleproperty.py +6 -2
  51. meerk40t/gui/ribbon.py +6 -1
  52. meerk40t/gui/scenewidgets/gridwidget.py +29 -32
  53. meerk40t/gui/scenewidgets/rectselectwidget.py +190 -192
  54. meerk40t/gui/simulation.py +75 -77
  55. meerk40t/gui/spoolerpanel.py +27 -7
  56. meerk40t/gui/statusbarwidgets/defaultoperations.py +84 -48
  57. meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
  58. meerk40t/gui/tips.py +15 -1
  59. meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
  60. meerk40t/gui/wxmmain.py +242 -114
  61. meerk40t/gui/wxmscene.py +107 -24
  62. meerk40t/gui/wxmtree.py +4 -2
  63. meerk40t/gui/wxutils.py +286 -15
  64. meerk40t/image/imagetools.py +129 -65
  65. meerk40t/internal_plugins.py +4 -0
  66. meerk40t/kernel/kernel.py +67 -18
  67. meerk40t/kernel/settings.py +28 -9
  68. meerk40t/lihuiyu/device.py +24 -12
  69. meerk40t/main.py +14 -9
  70. meerk40t/moshi/device.py +20 -6
  71. meerk40t/network/console_server.py +22 -6
  72. meerk40t/newly/device.py +10 -3
  73. meerk40t/newly/gui/gui.py +10 -0
  74. meerk40t/ruida/device.py +22 -2
  75. meerk40t/ruida/loader.py +9 -4
  76. meerk40t/ruida/rdjob.py +48 -8
  77. meerk40t/tools/geomstr.py +240 -123
  78. meerk40t/tools/rasterplotter.py +185 -94
  79. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/METADATA +1 -1
  80. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/RECORD +85 -84
  81. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/LICENSE +0 -0
  82. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/WHEEL +0 -0
  83. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/entry_points.txt +0 -0
  84. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/top_level.txt +0 -0
  85. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/zip-safe +0 -0
@@ -66,7 +66,7 @@ from meerk40t.svgelements import (
66
66
  Polygon,
67
67
  Polyline,
68
68
  )
69
- from meerk40t.tools.geomstr import Geomstr, stitch_geometries
69
+ from meerk40t.tools.geomstr import Geomstr, stitch_geometries, stitcheable_nodes
70
70
 
71
71
 
72
72
  def plugin(kernel, lifecycle=None):
@@ -181,12 +181,18 @@ def init_commands(kernel):
181
181
  @self.console_argument("x_pos", type=Length, help=_("X-coordinate of center"))
182
182
  @self.console_argument("y_pos", type=Length, help=_("Y-coordinate of center"))
183
183
  @self.console_argument("rx", type=Length, help=_("Primary radius of ellipse"))
184
- @self.console_argument("ry", type=Length, help=_("Secondary radius of ellipse (default equal to primary radius=circle)"))
185
- @self.console_argument("start_angle", type=Angle, help=_("Start angle of arc (default 0°)"))
186
- @self.console_argument("end_angle", type=Angle, help=_("End angle of arc (default 360°)"))
187
- @self.console_option(
188
- "rotation", "r", type=Angle, help=_("Rotation of arc")
184
+ @self.console_argument(
185
+ "ry",
186
+ type=Length,
187
+ help=_("Secondary radius of ellipse (default equal to primary radius=circle)"),
188
+ )
189
+ @self.console_argument(
190
+ "start_angle", type=Angle, help=_("Start angle of arc (default 0°)")
189
191
  )
192
+ @self.console_argument(
193
+ "end_angle", type=Angle, help=_("End angle of arc (default 360°)")
194
+ )
195
+ @self.console_option("rotation", "r", type=Angle, help=_("Rotation of arc"))
190
196
  @self.console_command(
191
197
  "arc",
192
198
  help=_("arc <cx> <cy> <rx> <ry> <start> <end>"),
@@ -195,7 +201,18 @@ def init_commands(kernel):
195
201
  all_arguments_required=True,
196
202
  )
197
203
  def element_arc(
198
- channel, _, x_pos, y_pos, rx, ry=None, start_angle=None, end_angle=None, rotation=None, data=None, post=None, **kwargs
204
+ channel,
205
+ _,
206
+ x_pos,
207
+ y_pos,
208
+ rx,
209
+ ry=None,
210
+ start_angle=None,
211
+ end_angle=None,
212
+ rotation=None,
213
+ data=None,
214
+ post=None,
215
+ **kwargs,
199
216
  ):
200
217
  if start_angle is None:
201
218
  start_angle = Angle("0deg")
@@ -211,7 +228,7 @@ def init_commands(kernel):
211
228
  cy = float(y_pos)
212
229
  geom = Geomstr()
213
230
  geom.arc_as_cubics(
214
- start_t=start_angle.radians,
231
+ start_t=start_angle.radians,
215
232
  end_t=end_angle.radians,
216
233
  rx=rx_val,
217
234
  ry=ry_val,
@@ -656,7 +673,83 @@ def init_commands(kernel):
656
673
  height=500,
657
674
  )
658
675
  except Exception:
659
- pass # Not relevant...
676
+ pass # Not relevant...
677
+
678
+ @self.console_argument("prop", type=str, help=_("property to get"))
679
+ @self.console_command(
680
+ "property-get",
681
+ help=_("get property value"),
682
+ input_type=(
683
+ None,
684
+ "elements",
685
+ ),
686
+ output_type="elements",
687
+ )
688
+ def element_property_get(command, channel, _, data, post=None, prop=None, **kwargs):
689
+ def possible_representation(node, prop) -> str:
690
+ def simple_rep(prop, value):
691
+ if isinstance(value, (float, int)) and prop in (
692
+ "x",
693
+ "y",
694
+ "cx",
695
+ "cy",
696
+ "r",
697
+ "rx",
698
+ "ry",
699
+ ):
700
+ try:
701
+ s = Length(value).length_mm
702
+ return s
703
+ except ValueError:
704
+ pass
705
+ elif isinstance(value, Length):
706
+ return value.length_mm
707
+ elif isinstance(value, Angle):
708
+ return value.angle_degrees
709
+ elif isinstance(value, str):
710
+ return f"'{value}'"
711
+ return repr(value)
712
+
713
+ value = getattr(node, prop, None)
714
+ if isinstance(value, (str, float, int)):
715
+ return simple_rep(prop, value)
716
+ elif isinstance(value, (tuple, list)):
717
+ stuff = []
718
+ for v in value:
719
+ stuff.append(simple_rep("x", v))
720
+ return ",".join(stuff)
721
+ return simple_rep(prop, value)
722
+
723
+ if data is None:
724
+ data = list(self.elems(emphasized=True))
725
+ if len(data) == 0:
726
+ channel(_("No selected elements."))
727
+ return
728
+ if prop is None or (prop == "?"):
729
+ channel(_("You need to provide the property to get."))
730
+ identified = []
731
+ for op in data:
732
+ if op.type in identified:
733
+ continue
734
+ identified.append(op.type)
735
+ prop_str = f"{op.type} has the following properties:"
736
+ first = True
737
+ for d in op.__dict__:
738
+ if d.startswith("_"):
739
+ continue
740
+ prop_str = f"{prop_str}{'' if first else ','} {d}"
741
+ first = False
742
+ channel(prop_str)
743
+ return
744
+ for d in data:
745
+ if not hasattr(d, prop):
746
+ channel(
747
+ f"Node: {d.display_label()} (Type: {d.type}) has no property called '{prop}'"
748
+ )
749
+ else:
750
+ channel(
751
+ f"Node: {d.display_label()} (Type: {d.type}): {prop}={getattr(d, prop, '')} ({possible_representation(d, prop)})"
752
+ )
660
753
 
661
754
  @self.console_argument("prop", type=str, help=_("property to set"))
662
755
  @self.console_argument("new_value", type=str, help=_("new property value"))
@@ -681,7 +774,7 @@ def init_commands(kernel):
681
774
  if len(data) == 0:
682
775
  channel(_("No selected elements."))
683
776
  return
684
- if prop is None or (prop == "?" and new_value=="?"):
777
+ if prop is None or (prop == "?" and new_value == "?"):
685
778
  channel(_("You need to provide the property to set."))
686
779
  if prop == "?":
687
780
  identified = []
@@ -689,13 +782,17 @@ def init_commands(kernel):
689
782
  if op.type in identified:
690
783
  continue
691
784
  identified.append(op.type)
692
- prop_str = f"{op.type} has the following properties: "
785
+ prop_str = f"{op.type} has the following properties:"
786
+ first = True
693
787
  for d in op.__dict__:
694
788
  if d.startswith("_"):
695
789
  continue
696
- prop_str = f"{prop_str}, {d}"
790
+ prop_str = f"{prop_str}{'' if first else ','} {d}"
791
+ first = False
697
792
  channel(prop_str)
698
- channel ("Be careful what you do - this is a failsafe method to crash MeerK40t, burn down your house or whatever...")
793
+ channel(
794
+ "Be careful what you do - this is a failsafe method to crash MeerK40t, burn down your house or whatever..."
795
+ )
699
796
  return
700
797
  classify_required = False
701
798
  prop = prop.lower()
@@ -769,7 +866,9 @@ def init_commands(kernel):
769
866
  if prop in ("x", "y"):
770
867
  if not e.can_move(self.lock_allows_move):
771
868
  channel(
772
- _("Element can not be moved: {name}").format(name=str(e))
869
+ _("Element can not be moved: {name}").format(
870
+ name=str(e)
871
+ )
773
872
  )
774
873
  continue
775
874
  # We need to adjust the matrix
@@ -795,7 +894,9 @@ def init_commands(kernel):
795
894
  continue
796
895
  if hasattr(e, "can_scale") and not e.can_scale:
797
896
  channel(
798
- _("Element can not be scaled: {name}").format(name=str(e))
897
+ _("Element can not be scaled: {name}").format(
898
+ name=str(e)
899
+ )
799
900
  )
800
901
  continue
801
902
  if hasattr(e, "matrix") and hasattr(e, "bounds"):
@@ -865,7 +966,9 @@ def init_commands(kernel):
865
966
  delattr(e, "wxfont")
866
967
  text_elems.append(e)
867
968
  if prop in ("mktext", "mkfont"):
868
- for property_op in self.kernel.lookup_all("path_updater/.*"):
969
+ for property_op in self.kernel.lookup_all(
970
+ "path_updater/.*"
971
+ ):
869
972
  property_op(self.kernel.root, e)
870
973
  if prop in (
871
974
  "dpi",
@@ -935,7 +1038,7 @@ def init_commands(kernel):
935
1038
  if not data:
936
1039
  channel(_("No selected operations."))
937
1040
  return
938
- if prop is None or (prop == "?" and new_value=="?"):
1041
+ if prop is None or (prop == "?" and new_value == "?"):
939
1042
  channel(_("You need to provide the property to set."))
940
1043
  if prop == "?":
941
1044
  identified = []
@@ -949,7 +1052,9 @@ def init_commands(kernel):
949
1052
  continue
950
1053
  prop_str = f"{prop_str}, {d}"
951
1054
  channel(prop_str)
952
- channel ("Be careful what you do - this is a failsafe method to crash MeerK40t, burn down your house or whatever...")
1055
+ channel(
1056
+ "Be careful what you do - this is a failsafe method to crash MeerK40t, burn down your house or whatever..."
1057
+ )
953
1058
  return
954
1059
  prop = prop.lower()
955
1060
  if len(new_value) == 0:
@@ -982,7 +1087,6 @@ def init_commands(kernel):
982
1087
  new_value = testval
983
1088
  prevalidated = True
984
1089
 
985
-
986
1090
  changed = []
987
1091
  # _("Update property")
988
1092
  with self.undoscope("Update property"):
@@ -1030,7 +1134,6 @@ def init_commands(kernel):
1030
1134
  ).format(val=new_value, field=prop, oldval=oldval)
1031
1135
  )
1032
1136
 
1033
-
1034
1137
  else:
1035
1138
  channel(
1036
1139
  _("Operation {name} has no property {field}").format(
@@ -1122,7 +1225,7 @@ def init_commands(kernel):
1122
1225
  f"Simplified {node.type} ({node.display_label()}), tolerance: {tolerance}={Length(tolerance, digits=4).length_mm})"
1123
1226
  )
1124
1227
  if seg_before:
1125
- saving = f"({(seg_before - seg_after)/seg_before*100:.1f}%)"
1228
+ saving = f"({(seg_before - seg_after) / seg_before * 100:.1f}%)"
1126
1229
  else:
1127
1230
  saving = ""
1128
1231
  channel(f"Subpaths before: {sub_before} to {sub_after}")
@@ -1363,7 +1466,9 @@ def init_commands(kernel):
1363
1466
  with self.undoscope("Set stroke-width"):
1364
1467
  for e in data:
1365
1468
  if hasattr(e, "lock") and e.lock:
1366
- channel(_("Can't modify a locked element: {name}").format(name=str(e)))
1469
+ channel(
1470
+ _("Can't modify a locked element: {name}").format(name=str(e))
1471
+ )
1367
1472
  continue
1368
1473
  e.stroke_width = stroke_width
1369
1474
  try:
@@ -1396,7 +1501,9 @@ def init_commands(kernel):
1396
1501
  with self.undoscope("Update stroke-scale"):
1397
1502
  for e in data:
1398
1503
  if hasattr(e, "lock") and e.lock:
1399
- channel(_("Can't modify a locked element: {name}").format(name=str(e)))
1504
+ channel(
1505
+ _("Can't modify a locked element: {name}").format(name=str(e))
1506
+ )
1400
1507
  continue
1401
1508
  e.stroke_scaled = command == "enable_stroke_scale"
1402
1509
  e.altered()
@@ -1700,7 +1807,9 @@ def init_commands(kernel):
1700
1807
  for e in apply:
1701
1808
  if hasattr(e, "lock") and e.lock:
1702
1809
  channel(
1703
- _("Can't modify a locked element: {name}").format(name=str(e))
1810
+ _("Can't modify a locked element: {name}").format(
1811
+ name=str(e)
1812
+ )
1704
1813
  )
1705
1814
  continue
1706
1815
  e.stroke = None
@@ -1712,7 +1821,9 @@ def init_commands(kernel):
1712
1821
  for e in apply:
1713
1822
  if hasattr(e, "lock") and e.lock:
1714
1823
  channel(
1715
- _("Can't modify a locked element: {name}").format(name=str(e))
1824
+ _("Can't modify a locked element: {name}").format(
1825
+ name=str(e)
1826
+ )
1716
1827
  )
1717
1828
  continue
1718
1829
  e.stroke = Color(color)
@@ -1803,13 +1914,14 @@ def init_commands(kernel):
1803
1914
  return "elements", data
1804
1915
  # _("Set fill")
1805
1916
  with self.undoscope("Set fill"):
1806
-
1807
1917
  if color == "none":
1808
1918
  self.set_start_time("fill")
1809
1919
  for e in apply:
1810
1920
  if hasattr(e, "lock") and e.lock:
1811
1921
  channel(
1812
- _("Can't modify a locked element: {name}").format(name=str(e))
1922
+ _("Can't modify a locked element: {name}").format(
1923
+ name=str(e)
1924
+ )
1813
1925
  )
1814
1926
  continue
1815
1927
  e.fill = None
@@ -1821,7 +1933,9 @@ def init_commands(kernel):
1821
1933
  for e in apply:
1822
1934
  if hasattr(e, "lock") and e.lock:
1823
1935
  channel(
1824
- _("Can't modify a locked element: {name}").format(name=str(e))
1936
+ _("Can't modify a locked element: {name}").format(
1937
+ name=str(e)
1938
+ )
1825
1939
  )
1826
1940
  continue
1827
1941
  e.fill = Color(color)
@@ -2626,15 +2740,26 @@ def init_commands(kernel):
2626
2740
  self.first_emphasized = None
2627
2741
  return "elements", data
2628
2742
 
2629
- @self.console_argument("tolerance", type=str, help=_("Tolerance to stitch paths together"))
2630
- @self.console_option("keep", "k", type=bool, action="store_true", default=False, help=_("Keep original paths"))
2743
+ @self.console_argument(
2744
+ "tolerance", type=str, help=_("Tolerance to stitch paths together")
2745
+ )
2746
+ @self.console_option(
2747
+ "keep",
2748
+ "k",
2749
+ type=bool,
2750
+ action="store_true",
2751
+ default=False,
2752
+ help=_("Keep original paths"),
2753
+ )
2631
2754
  @self.console_command(
2632
2755
  "stitch",
2633
2756
  help=_("stitch selected elements"),
2634
2757
  input_type=(None, "elements"),
2635
2758
  output_type="elements",
2636
2759
  )
2637
- def stitched(command, channel, _, data=None, tolerance=None, keep=None, post=None, **kwargs):
2760
+ def stitched(
2761
+ command, channel, _, data=None, tolerance=None, keep=None, post=None, **kwargs
2762
+ ):
2638
2763
  def _prepare_stitching_params(channel, data, tolerance, keep):
2639
2764
  if data is None:
2640
2765
  data = list(self.elems(emphasized=True))
@@ -2652,38 +2777,10 @@ def init_commands(kernel):
2652
2777
  channel(f"Invalid tolerance value: {tolerance}")
2653
2778
  return data, tolerance, keep, False
2654
2779
  return data, tolerance_val, keep, True
2655
-
2656
- def stitcheable_nodes(data, tolerance) -> list:
2657
- out = []
2658
- geoms = []
2659
- # Store all geometries together with an indicator, to which node they belong
2660
- for idx, node in enumerate(data):
2661
- if not hasattr(node, "as_geometry"):
2662
- continue
2663
- for g1 in node.as_geometry().as_contiguous():
2664
- geoms.append((idx, g1))
2665
- for idx1, (nodeidx1, g1) in enumerate(geoms):
2666
- for idx2 in range(idx1 + 1, len(geoms)):
2667
- nodeidx2 = geoms[idx2][0]
2668
- g2 = geoms[idx2][1]
2669
- fp1 = g1.first_point
2670
- fp2 = g2.first_point
2671
- lp1 = g1.last_point
2672
- lp2 = g2.last_point
2673
- if (
2674
- abs(lp1 - lp2) <= tolerance or
2675
- abs(lp1 - fp2) <= tolerance or
2676
- abs(fp1 - fp2) <= tolerance or
2677
- abs(fp1 - lp2) <= tolerance
2678
- ):
2679
- if nodeidx1 not in out:
2680
- out.append(nodeidx1)
2681
- if nodeidx2 not in out:
2682
- out.append(nodeidx2)
2683
-
2684
- return [data[idx] for idx in out]
2685
-
2686
- data, tolerance, keep, valid = _prepare_stitching_params(channel, data, tolerance, keep)
2780
+
2781
+ data, tolerance, keep, valid = _prepare_stitching_params(
2782
+ channel, data, tolerance, keep
2783
+ )
2687
2784
  if not valid:
2688
2785
  return
2689
2786
  s_data = stitcheable_nodes(data, tolerance)
@@ -2700,13 +2797,12 @@ def init_commands(kernel):
2700
2797
  default_strokewidth = None
2701
2798
  default_fill = None
2702
2799
  for node in s_data:
2703
- if hasattr(node, "as_geometry"):
2704
- geom : Geomstr = node.as_geometry()
2705
- geoms.extend(iter(geom.as_contiguous()))
2706
- if default_stroke is None and hasattr(node, "stroke"):
2707
- default_stroke = node.stroke
2708
- if default_strokewidth is None and hasattr(node, "stroke_width"):
2709
- default_strokewidth = node.stroke_width
2800
+ geom: Geomstr = node.as_geometry()
2801
+ geoms.extend(iter(geom.as_contiguous()))
2802
+ if default_stroke is None and hasattr(node, "stroke"):
2803
+ default_stroke = node.stroke
2804
+ if default_strokewidth is None and hasattr(node, "stroke_width"):
2805
+ default_strokewidth = node.stroke_width
2710
2806
  to_be_deleted.append(node)
2711
2807
  prev_len = len(geoms)
2712
2808
  if geoms:
@@ -2728,10 +2824,101 @@ def init_commands(kernel):
2728
2824
  )
2729
2825
  data_out.append(node)
2730
2826
  new_len = len(data_out)
2731
- channel(f"Sub-Paths before: {prev_len} -> consolidated to {new_len} sub-paths")
2732
-
2827
+ channel(
2828
+ f"Sub-Paths before: {prev_len} -> consolidated to {new_len} sub-paths"
2829
+ )
2830
+
2733
2831
  post.append(classify_new(data_out))
2734
2832
  self.set_emphasis(data_out)
2735
2833
  return "elements", data_out
2736
2834
 
2835
+ @self.console_argument("xpos", type=Length, help=_("X-Position of cross center"))
2836
+ @self.console_argument("ypos", type=Length, help=_("Y-Position of cross center"))
2837
+ @self.console_argument("diameter", type=Length, help=_("Diameter of cross"))
2838
+ @self.console_option(
2839
+ "circle",
2840
+ "c",
2841
+ type=bool,
2842
+ action="store_true",
2843
+ default=False,
2844
+ help=_("Draw a circle around cross"),
2845
+ )
2846
+ @self.console_option(
2847
+ "diagonal",
2848
+ "d",
2849
+ type=bool,
2850
+ action="store_true",
2851
+ default=False,
2852
+ help=_("Draw the cross diagonally"),
2853
+ )
2854
+ @self.console_command(
2855
+ "cross",
2856
+ help=_("Create a small cross at the given position"),
2857
+ input_type=None,
2858
+ output_type="elements",
2859
+ )
2860
+ def cross(
2861
+ command,
2862
+ channel,
2863
+ _,
2864
+ data=None,
2865
+ xpos=None,
2866
+ ypos=None,
2867
+ diameter=None,
2868
+ circle=None,
2869
+ diagonal=None,
2870
+ post=None,
2871
+ **kwargs,
2872
+ ):
2873
+ if xpos is None or ypos is None or diameter is None:
2874
+ channel(_("You need to provide center-point and diameter: cross x y d"))
2875
+ return
2876
+ try:
2877
+ xp = float(xpos)
2878
+ yp = float(ypos)
2879
+ dia = float(diameter)
2880
+ except ValueError:
2881
+ channel(_("Invalid values given"))
2882
+ return
2883
+ if circle is None:
2884
+ circle = False
2885
+ if diagonal is None:
2886
+ diagonal = False
2887
+ geom = Geomstr()
2888
+ if diagonal:
2889
+ sincos45 = dia / 2 * sqrt(2) / 2
2890
+ geom.line(
2891
+ complex(xp - sincos45, yp - sincos45),
2892
+ complex(xp + sincos45, yp + sincos45),
2893
+ )
2894
+ geom.line(
2895
+ complex(xp + sincos45, yp - sincos45),
2896
+ complex(xp - sincos45, yp + sincos45),
2897
+ )
2898
+ else:
2899
+ geom.line(complex(xp - dia / 2, yp), complex(xp + dia / 2, yp))
2900
+ geom.line(complex(xp, yp - dia / 2), complex(xp, yp + dia / 2))
2901
+ if circle:
2902
+ geom.append(Geomstr.circle(dia / 2, xp, yp))
2903
+ # _("Create cross") - hint for translator
2904
+ with self.undoscope("Create cross"):
2905
+ node = self.elem_branch.add(
2906
+ label=_("Cross at ({xp}, {yp})").format(
2907
+ xp=xpos.length_mm, yp=ypos.length_mm
2908
+ ),
2909
+ geometry=geom,
2910
+ stroke=self.default_stroke,
2911
+ stroke_width=self.default_strokewidth,
2912
+ fill=None,
2913
+ type="elem path",
2914
+ )
2915
+ if data is None:
2916
+ data = []
2917
+ data.append(node)
2918
+
2919
+ # Newly created! Classification needed?
2920
+ post.append(classify_new(data))
2921
+ self.signal("refresh_scene", "Scene")
2922
+ return "elements", data
2923
+
2737
2924
  # --------------------------- END COMMANDS ------------------------------
@@ -2,7 +2,6 @@
2
2
  This is a giant list of console commands that deal with and often implement the elements system in the program.
3
3
  """
4
4
 
5
-
6
5
  from meerk40t.kernel import CommandSyntaxError
7
6
 
8
7
  from .element_types import *
@@ -39,9 +38,10 @@ def init_commands(kernel):
39
38
  for i, n in enumerate(node.children):
40
39
  p = list(path)
41
40
  p.append(str(i))
42
- channel(
43
- f"{'.'.join(p).ljust(10)}: {str(n._bounds)} - {str(n._bounds_dirty)} {str(n.type)} - {str(str(n)[:16])}"
44
- )
41
+ if hasattr(n, "_bounds"):
42
+ channel(
43
+ f"{'.'.join(p).ljust(10)}: {str(n._bounds)} - {str(n._bounds_dirty)} {str(n.type)} - {str(str(n)[:16])}"
44
+ )
45
45
  b_list(p, n)
46
46
 
47
47
  for d in data:
@@ -49,7 +49,12 @@ def init_commands(kernel):
49
49
  if d.type == "root":
50
50
  channel(_("Tree:"))
51
51
  else:
52
- channel(f"{str(d)}:")
52
+ if hasattr(d, "_bounds"):
53
+ channel(
54
+ f"{str(d)}: {str(d._bounds)} - {str(d._bounds_dirty)} {str(d.type)} - {str(str(d)[:16])}"
55
+ )
56
+ else:
57
+ channel(f"{str(d)}:")
53
58
  b_list([], d)
54
59
  channel("----------")
55
60
 
@@ -41,8 +41,15 @@ class BlobNode(Node):
41
41
  return False
42
42
 
43
43
  @staticmethod
44
- def hex_view(data, data_type):
45
- header1 = f"Data-Type: {data_type}, Length={len(data)}\n"
44
+ def hex_view(data, data_type, info=''):
45
+ """
46
+ Render a hex dump with an optional info header.
47
+
48
+ data: bytes to display
49
+ data_type: mime type
50
+ info: an arbitrary string to append to the end of first line of header
51
+ """
52
+ header1 = f"Data-Type: {data_type}, Length={len(data)}{info}\n"
46
53
  header2 = "Offset | Hex | Ascii \n"
47
54
  header2 += "-------+-------------------------------------------------+----------------\n"
48
55
  if isinstance(data, str):
@@ -70,8 +77,16 @@ class BlobNode(Node):
70
77
  return header1 + header2 + "".join(hex_data)
71
78
 
72
79
  @staticmethod
73
- def ascii_view(data, data_type):
74
- header1 = f"Data-Type: {data_type}, Length={len(data)}\n"
80
+ def ascii_view(data, data_type, info=''):
81
+ """
82
+ Render an ascii view with an optional info header.
83
+
84
+ data: bytes to display
85
+ data_type: mime type
86
+ info: an arbitrary string to append to the end of first line of header
87
+ """
88
+
89
+ header1 = f"Data-Type: {data_type}, Length={len(data)}{info}\n"
75
90
  header2 = "Offset | Hex | Ascii \n"
76
91
  header2 += "-------+-------------------------------------------------+----------------\n"
77
92
  if isinstance(data, str):
@@ -3,8 +3,8 @@ from math import cos, sin, sqrt, tau
3
3
 
4
4
  from meerk40t.core.node.mixins import (
5
5
  FunctionalParameter,
6
- Stroked,
7
6
  LabelDisplay,
7
+ Stroked,
8
8
  Suppressable,
9
9
  )
10
10
  from meerk40t.core.node.node import Fillrule, Node
@@ -171,12 +171,16 @@ class EllipseNode(Node, Stroked, FunctionalParameter, LabelDisplay, Suppressable
171
171
  tablen = self.mktablength
172
172
  numtabs = self.mktabpositions
173
173
  if tablen and numtabs:
174
- path = Geomstr.wobble_tab(path, tablen, resolution, numtabs, unit_factor=unit_factor)
174
+ path = Geomstr.wobble_tab(
175
+ path, tablen, resolution, numtabs, unit_factor=unit_factor
176
+ )
175
177
  # Is there a dash/dot pattern to apply?
176
178
  dashlen = self.stroke_dash
177
179
  irrelevant = 50
178
180
  if dashlen:
179
- path = Geomstr.wobble_dash(path, dashlen, resolution, irrelevant, unit_factor=unit_factor)
181
+ path = Geomstr.wobble_dash(
182
+ path, dashlen, resolution, irrelevant, unit_factor=unit_factor
183
+ )
180
184
 
181
185
  return path
182
186
 
@@ -261,19 +265,25 @@ class EllipseNode(Node, Stroked, FunctionalParameter, LabelDisplay, Suppressable
261
265
  return default_map
262
266
 
263
267
  def can_drop(self, drag_node):
268
+ if self.is_a_child_of(drag_node):
269
+ return False
264
270
  # Dragging element into element.
265
271
  return bool(
266
- hasattr(drag_node, "as_geometry") or
267
- hasattr(drag_node, "as_image") or
268
- (drag_node.type.startswith("op ") and drag_node.type != "op dots") or
269
- drag_node.type in ("file", "group")
272
+ hasattr(drag_node, "as_geometry")
273
+ or hasattr(drag_node, "as_image")
274
+ or (drag_node.type.startswith("op ") and drag_node.type != "op dots")
275
+ or drag_node.type in ("file", "group")
270
276
  )
271
277
 
272
278
  def drop(self, drag_node, modify=True, flag=False):
273
279
  # Dragging element into element.
274
280
  if not self.can_drop(drag_node):
275
281
  return False
276
- if hasattr(drag_node, "as_geometry") or hasattr(drag_node, "as_image") or drag_node.type in ("file", "group"):
282
+ if (
283
+ hasattr(drag_node, "as_geometry")
284
+ or hasattr(drag_node, "as_image")
285
+ or drag_node.type in ("file", "group")
286
+ ):
277
287
  if modify:
278
288
  self.insert_sibling(drag_node)
279
289
  return True