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
@@ -7,6 +7,7 @@ from meerk40t.kernel import CommandSyntaxError
7
7
  from ..core.exceptions import BadFileError
8
8
  from ..core.units import DEFAULT_PPI, UNITS_PER_PIXEL
9
9
  from ..svgelements import Angle, Color, Matrix, Path
10
+ from ..tools.geomstr import Geomstr
10
11
 
11
12
 
12
13
  def plugin(kernel, lifecycle=None):
@@ -993,6 +994,87 @@ def plugin(kernel, lifecycle=None):
993
994
  context.signal("element_property_update", data)
994
995
  return "image", data
995
996
 
997
+ @context.console_argument(
998
+ "x1",
999
+ type=float,
1000
+ help=_("X position of image cutline"),
1001
+ )
1002
+ @context.console_argument(
1003
+ "y1",
1004
+ type=float,
1005
+ help=_("Y position of image cutline"),
1006
+ )
1007
+ @context.console_argument(
1008
+ "x2",
1009
+ type=float,
1010
+ help=_("X position of image cutline"),
1011
+ )
1012
+ @context.console_argument(
1013
+ "y2",
1014
+ type=float,
1015
+ help=_("Y position of image cutline"),
1016
+ )
1017
+ @context.console_command(
1018
+ "linecut",
1019
+ help=_("Cuts and image with a line"),
1020
+ input_type="image",
1021
+ output_type="image",
1022
+ hidden=True,
1023
+ )
1024
+ def image_linecut(command, channel, _, data, x1, y1, x2, y2, **kwargs):
1025
+ data_out = list()
1026
+ for inode in data:
1027
+ if inode.lock:
1028
+ channel(
1029
+ _("Can't modify a locked image: {name}").format(name=str(inode))
1030
+ )
1031
+ continue
1032
+ b = inode.bounds
1033
+
1034
+ from meerk40t.core.node.elem_path import PathNode
1035
+ from meerk40t.core.node.elem_rect import RectNode
1036
+ from meerk40t.extra.imageactions import create_image, mask_image
1037
+
1038
+ rectnode = RectNode(
1039
+ x=b[0],
1040
+ y=b[1],
1041
+ width=b[2] - b[0],
1042
+ height=b[3] - b[1],
1043
+ stroke=None,
1044
+ fill=None,
1045
+ )
1046
+ bounds_rect = Geomstr.rect(b[0], b[1], b[2] - b[0], b[3] - b[1])
1047
+ line = Geomstr.lines(complex(x1, y1), complex(x2, y2))
1048
+ geoms = bounds_rect.divide(line)
1049
+ parent = inode.parent
1050
+
1051
+ make_raster = context.elements.lookup("render-op/make_raster")
1052
+
1053
+ elemimage, elemmatrix = create_image(
1054
+ make_raster, [inode], b, inode.dpi, keep_ratio=True
1055
+ )
1056
+
1057
+ for g in geoms:
1058
+ masknode = PathNode(geometry=g, stroke=None, fill=Color("black"))
1059
+ # Make sure they have the right size by adding a dummy node to it...
1060
+
1061
+ maskimage, maskmatrix = create_image(
1062
+ make_raster, (masknode, rectnode), b, inode.dpi, keep_ratio=True
1063
+ )
1064
+ if maskimage is None or elemimage is None:
1065
+ channel(_("Intermediary images were none"))
1066
+ continue
1067
+
1068
+ out = mask_image(
1069
+ elemimage, maskimage, elemmatrix, inode.dpi, dx=b[0], dy=b[1]
1070
+ )
1071
+ for imnode in out:
1072
+ parent.add_node(imnode)
1073
+ data_out.extend(out)
1074
+
1075
+ context.signal("element_property_update", data_out)
1076
+ return "image", data_out
1077
+
996
1078
  @context.console_argument(
997
1079
  "x",
998
1080
  type=int,
@@ -1014,16 +1096,19 @@ def plugin(kernel, lifecycle=None):
1014
1096
  img = inode.image
1015
1097
  image_left = img.crop((0, 0, x, inode.image.height))
1016
1098
  image_right = img.crop((x, 0, inode.image.width, inode.image.height))
1017
- inode_left = copy(inode)
1018
- inode_left.image = image_left
1019
- inode_right = copy(inode)
1020
- inode_right.image = image_right
1021
- inode_right.matrix.pre_translate(x)
1099
+
1100
+ parent = inode.parent
1022
1101
 
1023
1102
  inode.remove_node()
1024
1103
  elements = context.elements
1025
- node1 = elements.elem_branch.add_node(inode_left)
1026
- node2 = elements.elem_branch.add(inode_right)
1104
+
1105
+ node1 = parent.add(
1106
+ type="elem image", matrix=Matrix(inode.matrix), image=image_left
1107
+ )
1108
+ node2 = parent.add(
1109
+ type="elem image", matrix=Matrix(inode.matrix), image=image_right
1110
+ )
1111
+ node2.matrix.pre_translate(x)
1027
1112
  elements.classify([node1, node2])
1028
1113
  channel(_("Image sliced at position {position}").format(position=x))
1029
1114
  return "image", [node1, node2]
@@ -1052,17 +1137,20 @@ def plugin(kernel, lifecycle=None):
1052
1137
  img = inode.image
1053
1138
  image_top = img.crop((0, 0, inode.image.width, y))
1054
1139
  image_bottom = img.crop((0, y, inode.image.width, inode.image.height))
1055
- inode_top = copy(inode)
1056
- inode_top.image = image_top
1057
1140
 
1058
- inode_bottom = copy(inode)
1059
- inode_bottom.image = image_bottom
1060
- inode_bottom.transform.pre_translate(0, y)
1141
+ parent = inode.parent
1061
1142
 
1062
- update_image_node(inode)
1143
+ inode.remove_node()
1063
1144
  elements = context.elements
1064
- node1 = elements.elem_branch.add_node(inode_top)
1065
- node2 = elements.elem_branch.add_node(inode_bottom)
1145
+
1146
+ node1 = parent.add(
1147
+ type="elem image", matrix=Matrix(inode.matrix), image=image_top
1148
+ )
1149
+ node2 = parent.add(
1150
+ type="elem image", matrix=Matrix(inode.matrix), image=image_bottom
1151
+ )
1152
+ node2.matrix.pre_translate(0, y)
1153
+
1066
1154
  elements.classify([node1, node2])
1067
1155
  channel(_("Image slashed at position {position}").format(position=y))
1068
1156
  return "image", [node1, node2]
@@ -1286,6 +1374,346 @@ def plugin(kernel, lifecycle=None):
1286
1374
  update_image_node(inode)
1287
1375
  return "image", data
1288
1376
 
1377
+ @context.console_option("minimal", "m", type=int, help=_("minimal area"), default=2)
1378
+ @context.console_option(
1379
+ "outer",
1380
+ "o",
1381
+ type=bool,
1382
+ help=_("Ignore outer areas"),
1383
+ action="store_true",
1384
+ )
1385
+ @context.console_option(
1386
+ "simplified",
1387
+ "s",
1388
+ type=bool,
1389
+ help=_("Display simplified outline"),
1390
+ action="store_true",
1391
+ )
1392
+ @context.console_option(
1393
+ "line",
1394
+ "l",
1395
+ type=bool,
1396
+ help=_("Show split line candidates"),
1397
+ action="store_true",
1398
+ )
1399
+ @context.console_option(
1400
+ "breakdown",
1401
+ "b",
1402
+ type=bool,
1403
+ help=_("Break the image apart into slices"),
1404
+ action="store_true",
1405
+ )
1406
+ @context.console_option(
1407
+ "whiten",
1408
+ "w",
1409
+ type=bool,
1410
+ help=_("Break the image apart but whiten non-used areas"),
1411
+ action="store_true",
1412
+ )
1413
+ @context.console_command(
1414
+ "innerwhite",
1415
+ help=_("identify inner white areas in image"),
1416
+ input_type="image",
1417
+ output_type="image",
1418
+ )
1419
+ def image_white(
1420
+ command,
1421
+ channel,
1422
+ _,
1423
+ minimal=None,
1424
+ outer=False,
1425
+ simplified=False,
1426
+ line=False,
1427
+ breakdown=False,
1428
+ whiten=False,
1429
+ data=None,
1430
+ post=None,
1431
+ **kwargs,
1432
+ ):
1433
+ try:
1434
+ import cv2
1435
+ import numpy as np
1436
+ except ImportError:
1437
+ channel("Either cv2 or numpy weren't installed")
1438
+ return
1439
+ # from PIL import Image
1440
+ if data is None:
1441
+ channel(_("No elements selected"))
1442
+
1443
+ if minimal is None:
1444
+ minimal = 2
1445
+ if minimal <= 0 or minimal > 100:
1446
+ minimal = 2
1447
+
1448
+ data_out = list()
1449
+
1450
+ show_contour = not simplified
1451
+ show_simplified = simplified
1452
+ if breakdown and whiten:
1453
+ channel("You can't use --breakdown and --whiten at the same time")
1454
+ return
1455
+ if breakdown or whiten:
1456
+ line = False
1457
+ show_simplified = False
1458
+ show_contour = False
1459
+
1460
+ # channel (f"Options: breakdown={breakdown}, contour={show_contour}, simplified contour={show_simplified}, lines={line}")
1461
+ for inode in data:
1462
+ # node_image = inode.active_image
1463
+ node_image = inode.image
1464
+ width, height = node_image.size
1465
+ if width == 0 or height == 0:
1466
+ continue
1467
+ if not hasattr(inode, "bounds"):
1468
+ continue
1469
+ bb = inode.bounds
1470
+ ox = bb[0]
1471
+ oy = bb[1]
1472
+ coord_width = bb[2] - bb[0]
1473
+ coord_height = bb[3] - bb[1]
1474
+
1475
+ def getpoint(ix, iy):
1476
+ # Translate image to scene coordinates
1477
+ return (
1478
+ ox + ix / width * coord_width,
1479
+ oy + iy / height * coord_height,
1480
+ )
1481
+
1482
+ gray = np.array(node_image.convert("L"))
1483
+ # Threshold the image
1484
+ _, thresh = cv2.threshold(gray, 250, 255, cv2.THRESH_BINARY)
1485
+
1486
+ # Find contours
1487
+ contours, hierarchy = cv2.findContours(
1488
+ thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE
1489
+ )
1490
+ linecandidates = list()
1491
+
1492
+ minarea = int(minimal / 100.0 * width * height)
1493
+ # Filter contours based on area, rectangle of at least x%
1494
+
1495
+ large_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > minarea]
1496
+
1497
+ # Create some rectangles around the white areas
1498
+ for contour in large_contours:
1499
+ # Each individual contour is a Numpy array of (x, y) coordinates of boundary points of the object
1500
+ x, y, w, h = cv2.boundingRect(contour)
1501
+ rx, ry = getpoint(x, y)
1502
+ rw, rh = getpoint(w, h)
1503
+ rw -= ox
1504
+ rh -= oy
1505
+ if outer:
1506
+ # leftmost
1507
+ extreme = tuple(contour[contour[:, :, 0].argmin()][0])
1508
+ if extreme[0] == 0:
1509
+ # print ("Left edge")
1510
+ continue
1511
+ # rightmost
1512
+ extreme = tuple(contour[contour[:, :, 0].argmax()][0])
1513
+ if extreme[0] >= width - 1:
1514
+ # print ("Right edge")
1515
+ continue
1516
+ # topmost
1517
+ extreme = tuple(contour[contour[:, :, 1].argmin()][0])
1518
+ if extreme[1] == 0:
1519
+ # print ("Top edge")
1520
+ continue
1521
+ # bottommost
1522
+ extreme = tuple(contour[contour[:, :, 1].argmax()][0])
1523
+ if extreme[1] >= height - 1:
1524
+ # print ("Bottom edge")
1525
+ continue
1526
+
1527
+ linecandidates.append((x, w))
1528
+ area = cv2.contourArea(contour)
1529
+ rect_area = w * h
1530
+ extent = float(area) / rect_area
1531
+ # print (f"x={x}, y={y}, w={w}, h={h}, extent={extent*100:.1f}%")
1532
+ label = f"Contour - Area={100 * area / (width * height):.1f}%, Extent={extent*100:.1f}%"
1533
+ # if show_rect:
1534
+ # node = context.elements.elem_branch.add(
1535
+ # x=rx,
1536
+ # y=ry,
1537
+ # width=rw,
1538
+ # height=rh,
1539
+ # stroke=Color("red"),
1540
+ # label=label,
1541
+ # type="elem rect",
1542
+ # )
1543
+ # data_out.append(node)
1544
+ if show_contour:
1545
+ geom = Geomstr()
1546
+ notfirst = False
1547
+ for c in contour:
1548
+ for e in c:
1549
+ rx, ry = getpoint(e[0], e[1])
1550
+ if notfirst:
1551
+ geom.line(complex(lx, ly), complex(rx, ry))
1552
+ notfirst = True
1553
+ lx = rx
1554
+ ly = ry
1555
+ geom.close()
1556
+ node = context.elements.elem_branch.add(
1557
+ geometry=geom,
1558
+ stroke=Color("blue"),
1559
+ fill=Color("yellow"),
1560
+ label=label,
1561
+ type="elem path",
1562
+ )
1563
+ data_out.append(node)
1564
+ if show_simplified:
1565
+ # Set the epsilon value (adjust as needed)
1566
+ epsilon = 0.01 * cv2.arcLength(contour, True)
1567
+
1568
+ # Compute the approximate contour points
1569
+ approx = cv2.approxPolyDP(contour, epsilon, True)
1570
+ geom = Geomstr()
1571
+ notfirst = False
1572
+ for c in approx:
1573
+ for e in c:
1574
+ rx, ry = getpoint(e[0], e[1])
1575
+ if notfirst:
1576
+ geom.line(complex(lx, ly), complex(rx, ry))
1577
+ notfirst = True
1578
+ lx = rx
1579
+ ly = ry
1580
+ geom.close()
1581
+ node = context.elements.elem_branch.add(
1582
+ geometry=geom,
1583
+ stroke=Color("green"),
1584
+ label=label,
1585
+ type="elem path",
1586
+ )
1587
+ data_out.append(node)
1588
+
1589
+ # if show_extreme:
1590
+ # # leftmost
1591
+ # extreme = tuple(contour[contour[:, :, 0].argmin()][0])
1592
+ # lmx, lmy = getpoint(extreme[0], extreme[1])
1593
+ # # rightmost
1594
+ # extreme = tuple(contour[contour[:, :, 0].argmax()][0])
1595
+ # rmx, rmy = getpoint(extreme[0], extreme[1])
1596
+ # # topmost
1597
+ # extreme = tuple(contour[contour[:, :, 1].argmin()][0])
1598
+ # tmx, tmy = getpoint(extreme[0], extreme[1])
1599
+ # # bottommost
1600
+ # extreme = tuple(contour[contour[:, :, 1].argmax()][0])
1601
+ # bmx, bmy = getpoint(extreme[0], extreme[1])
1602
+ # geom = Geomstr()
1603
+ # geom.line(lmx + 1j * lmy, tmx + 1j * tmy)
1604
+ # geom.line(tmx + 1j * tmy, rmx + 1j * rmy)
1605
+ # geom.line(rmx + 1j * rmy, bmx + 1j * bmy)
1606
+ # geom.line(bmx + 1j * bmy, lmx + 1j * lmy)
1607
+ # node = context.elements.elem_branch.add(
1608
+ # geometry=geom,
1609
+ # stroke=Color("green"),
1610
+ # label=label,
1611
+ # type="elem path",
1612
+ # )
1613
+ # data_out.append(node)
1614
+ linecandidates.sort(key=lambda e: e[0])
1615
+ if line or breakdown or whiten:
1616
+ for idx1, c in enumerate(linecandidates):
1617
+ if c[0] < 0:
1618
+ continue
1619
+ cx = c[0] + c[1] / 2
1620
+ for idx2, d in enumerate(linecandidates):
1621
+ if idx1 == idx2 or d[0] < 0:
1622
+ continue
1623
+ # Does c line inside d? if yes then we don't need d
1624
+ if d[0] <= c[0] and d[0] + d[1] >= c[0] + c[1]:
1625
+ linecandidates[idx2] = (-1, -1)
1626
+ if line:
1627
+ for c in linecandidates:
1628
+ if c[0] < 0:
1629
+ continue
1630
+ sx, sy = getpoint(c[0] + c[1] / 2, 0)
1631
+ ex, ey = getpoint(c[0] + c[1] / 2, height)
1632
+ node = context.elements.elem_branch.add(
1633
+ x1=sx,
1634
+ y1=sy,
1635
+ x2=ex,
1636
+ y2=ey,
1637
+ stroke=Color("red"),
1638
+ label="Splitline",
1639
+ type="elem line",
1640
+ )
1641
+ data_out.append(node)
1642
+ white_paste = (255, 255, 255)
1643
+ if breakdown or whiten:
1644
+ anyslices = 0
1645
+ right_image = node_image.copy()
1646
+ dx = 0
1647
+ for c in linecandidates:
1648
+ if c[0] < 0:
1649
+ continue
1650
+ rdx, rdy = getpoint(dx, 0)
1651
+ rdx -= ox
1652
+ rwidth, rheight = right_image.size
1653
+ anyslices += 1
1654
+ if breakdown:
1655
+ x = int(c[0] + c[1] / 2 - dx)
1656
+ left_image = right_image.crop((0, 0, x, rheight))
1657
+ dx = x + 1
1658
+ right_image = right_image.crop((dx, 0, rwidth, rheight))
1659
+ elif whiten:
1660
+ x = int(c[0] + c[1] / 2)
1661
+ left_image = right_image.copy()
1662
+ # print(f"Break position: {x}")
1663
+ if dx > 0:
1664
+ # print(f"Erasing left: 0:{dx - 1}")
1665
+ left_image.paste(white_paste, (0, 0, dx - 1, rheight))
1666
+ dx = x + 1
1667
+ left_image.paste(white_paste, (dx, 0, rwidth, rheight))
1668
+ # print(f"Erasing right: {dx}:{rwidth}")
1669
+ newnode = copy(inode)
1670
+ newnode.label = (
1671
+ f"[{anyslices}]{'' if inode.label is None else inode.label}"
1672
+ )
1673
+ # newnode.dither = False
1674
+ # newnode.operations.clear()
1675
+ # newnode.prevent_crop = True
1676
+ newnode.image = left_image
1677
+ if breakdown and rdx != 0:
1678
+ newnode.matrix.post_translate_x(rdx)
1679
+ if whiten:
1680
+ newnode.prevent_crop = True
1681
+
1682
+ newnode.altered()
1683
+ newnode._processed_image = None
1684
+
1685
+ context.elements.elem_branch.add_node(newnode)
1686
+ data_out.append(newnode)
1687
+ if anyslices > 0:
1688
+ rdx, rdy = getpoint(dx, 0)
1689
+ rdx -= ox
1690
+ anyslices += 1
1691
+ newnode = copy(inode)
1692
+ newnode.label = (
1693
+ f"[{anyslices}]{'' if inode.label is None else inode.label}"
1694
+ )
1695
+ # newnode.dither = False
1696
+ # newnode.operations.clear()
1697
+ # newnode.prevent_crop = True
1698
+ if whiten:
1699
+ if dx > 0:
1700
+ # print(f"Last, erasing left: 0:{dx - 1}")
1701
+ right_image.paste(white_paste, (0, 0, dx - 1, rheight))
1702
+ newnode.image = right_image
1703
+ if breakdown and rdx != 0:
1704
+ newnode.matrix.post_translate_x(rdx)
1705
+ if whiten:
1706
+ newnode.prevent_crop = True
1707
+ newnode.altered()
1708
+ newnode._processed_image = None
1709
+ context.elements.elem_branch.add_node(newnode)
1710
+ data_out.append(newnode)
1711
+
1712
+ inode.remove_node()
1713
+
1714
+ post.append(context.elements.post_classify(data_out))
1715
+ return "image", data_out
1716
+
1289
1717
 
1290
1718
  _DIFFUSION_MAPS = {
1291
1719
  "floyd-steinberg": (
@@ -56,9 +56,9 @@ def plugin(kernel, lifecycle):
56
56
 
57
57
  plugins.append(fills.plugin)
58
58
 
59
- from .fill import patternfill
59
+ from .fill import patterns
60
60
 
61
- plugins.append(patternfill.plugin)
61
+ plugins.append(patterns.plugin)
62
62
 
63
63
  from .extra import vectrace
64
64
 
@@ -76,10 +76,6 @@ def plugin(kernel, lifecycle):
76
76
 
77
77
  plugins.append(hershey.plugin)
78
78
 
79
- from .extra import embroider
80
-
81
- plugins.append(embroider.plugin)
82
-
83
79
  from .extra import ezd
84
80
 
85
81
  plugins.append(ezd.plugin)
@@ -88,10 +84,6 @@ def plugin(kernel, lifecycle):
88
84
 
89
85
  plugins.append(lbrn.plugin)
90
86
 
91
- from .extra import pathoptimize
92
-
93
- plugins.append(pathoptimize.plugin)
94
-
95
87
  from .extra import updater
96
88
 
97
89
  plugins.append(updater.plugin)
meerk40t/kernel/kernel.py CHANGED
@@ -1187,7 +1187,7 @@ class Kernel(Settings):
1187
1187
 
1188
1188
  line = await loop.run_in_executor(None, sys.stdin.readline)
1189
1189
  line = line.strip()
1190
- if line in ("quit", "shutdown"):
1190
+ if line in ("quit", "shutdown", "restart"):
1191
1191
  self._quit = True
1192
1192
  break
1193
1193
  self.console(f".{line}\n")
@@ -1645,10 +1645,8 @@ class Kernel(Settings):
1645
1645
  @return:
1646
1646
  """
1647
1647
  self._registered[path] = obj
1648
- try:
1648
+ if hasattr(obj, "sub_register"):
1649
1649
  obj.sub_register(self)
1650
- except AttributeError:
1651
- pass
1652
1650
  self.lookup_change(path)
1653
1651
 
1654
1652
  def unregister(self, path: str):
@@ -3340,6 +3338,16 @@ class Kernel(Settings):
3340
3338
  self._shutdown = True
3341
3339
  self.set_kernel_lifecycle(self, LIFECYCLE_KERNEL_SHUTDOWN)
3342
3340
 
3341
+ @self.console_command(
3342
+ "restart", help=_("shuts down all processes, exits and restarts meerk40t")
3343
+ )
3344
+ def restart(**kwargs):
3345
+ if self._shutdown:
3346
+ return
3347
+ self._shutdown = True
3348
+ self.restart = True
3349
+ self.set_kernel_lifecycle(self, LIFECYCLE_KERNEL_SHUTDOWN)
3350
+
3343
3351
  # ==========
3344
3352
  # FILE MANAGER
3345
3353
  # ==========
@@ -523,13 +523,13 @@ class LihuiyuController:
523
523
  # If we are paused just wait until the state changes.
524
524
  if len(self._realtime_buffer) == 0 and len(self._preempt) == 0:
525
525
  # Only pause if there are no realtime commands to queue.
526
- self.context.signal("pipe;running", False)
526
+ self.context.laser_status = "idle"
527
527
  with self._loop_cond:
528
528
  self._loop_cond.wait()
529
529
  continue
530
530
  if self.aborted_retries:
531
531
  # We are not trying reconnection anymore.
532
- self.context.signal("pipe;running", False)
532
+ self.context.laser_status = "idle"
533
533
  with self._loop_cond:
534
534
  self._loop_cond.wait()
535
535
  continue
@@ -537,7 +537,7 @@ class LihuiyuController:
537
537
  self._check_transfer_buffer()
538
538
  if len(self._realtime_buffer) <= 0 and len(self._buffer) <= 0:
539
539
  # The buffer and realtime buffers are empty. No packet creation possible.
540
- self.context.signal("pipe;running", False)
540
+ self.context.laser_status = "idle"
541
541
  with self._loop_cond:
542
542
  self._loop_cond.wait()
543
543
  continue
@@ -557,7 +557,7 @@ class LihuiyuController:
557
557
  if self.refuse_counts >= 5:
558
558
  self.context.signal("pipe;state", "STATE_FAILED_RETRYING")
559
559
  self.context.signal("pipe;failing", self.refuse_counts)
560
- self.context.signal("pipe;running", False)
560
+ self.context.laser_status = "idle"
561
561
  if self.is_shutdown:
562
562
  return # Sometimes it could reset this and escape.
563
563
  time.sleep(3) # 3-second sleep on failed connection attempt.
@@ -567,12 +567,12 @@ class LihuiyuController:
567
567
  self.connection_errors += 1
568
568
  self.pre_ok = False
569
569
 
570
- self.context.signal("pipe;running", False)
570
+ self.context.laser_status = "idle"
571
571
  time.sleep(0.5)
572
572
  self.close()
573
573
  continue
574
574
 
575
- self.context.signal("pipe;running", queue_processed)
575
+ self.context.laser_status = "active" if queue_processed else "idle"
576
576
  if queue_processed:
577
577
  # Packet was sent.
578
578
  if self.state not in (
@@ -603,7 +603,7 @@ class LihuiyuController:
603
603
  self._thread = None
604
604
  self.update_state("end")
605
605
  self.pre_ok = False
606
- self.context.signal("pipe;running", False)
606
+ self.context.laser_status = "idle"
607
607
 
608
608
  def _check_transfer_buffer(self):
609
609
  if len(self._queue): # check for and append queue
@@ -13,18 +13,20 @@ from meerk40t.core.view import View
13
13
  from meerk40t.kernel import CommandSyntaxError, Service, signal_listener
14
14
 
15
15
  from ..core.units import UNITS_PER_MIL, Length
16
+ from ..device.mixins import Status
16
17
  from .controller import LihuiyuController
17
18
  from .driver import LihuiyuDriver
18
19
  from .tcp_connection import TCPOutput
19
20
 
20
21
 
21
- class LihuiyuDevice(Service):
22
+ class LihuiyuDevice(Service, Status):
22
23
  """
23
24
  LihuiyuDevice is driver for the M2 Nano and other classes of Lihuiyu boards.
24
25
  """
25
26
 
26
27
  def __init__(self, kernel, path, *args, choices=None, **kwargs):
27
28
  Service.__init__(self, kernel, path)
29
+ Status.__init__(self)
28
30
  self.name = "LihuiyuDevice"
29
31
  _ = kernel.translation
30
32
  self.extension = "egv"
@@ -295,6 +295,7 @@ class LihuiyuDriver(Parameters):
295
295
  """
296
296
  self(b"~PN!\n~")
297
297
  self.paused = True
298
+ self.service.signal("pause")
298
299
 
299
300
  def resume(self, *values):
300
301
  """
@@ -308,6 +309,7 @@ class LihuiyuDriver(Parameters):
308
309
  """
309
310
  self(b"~PN&\n~")
310
311
  self.paused = False
312
+ self.service.signal("pause")
311
313
 
312
314
  def reset(self):
313
315
  """
@@ -328,6 +330,7 @@ class LihuiyuDriver(Parameters):
328
330
  self._reset_modes()
329
331
  self.state = DRIVER_STATE_RAPID
330
332
  self.paused = False
333
+ self.service.signal("pause")
331
334
 
332
335
  def abort(self):
333
336
  self(b"I\n")