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.
- meerk40t/balormk/balor_params.py +1 -43
- meerk40t/balormk/controller.py +1 -41
- meerk40t/balormk/device.py +16 -22
- meerk40t/balormk/driver.py +4 -4
- meerk40t/balormk/gui/balorconfig.py +2 -2
- meerk40t/balormk/gui/balorcontroller.py +13 -5
- meerk40t/balormk/gui/baloroperationproperties.py +0 -46
- meerk40t/balormk/gui/gui.py +17 -17
- meerk40t/camera/gui/camerapanel.py +18 -11
- meerk40t/core/cutcode/rastercut.py +3 -1
- meerk40t/core/cutplan.py +145 -14
- meerk40t/core/elements/clipboard.py +18 -9
- meerk40t/core/elements/element_treeops.py +320 -180
- meerk40t/core/elements/element_types.py +7 -2
- meerk40t/core/elements/elements.py +53 -27
- meerk40t/core/elements/geometry.py +8 -0
- meerk40t/core/elements/offset_clpr.py +129 -4
- meerk40t/core/elements/offset_mk.py +3 -1
- meerk40t/core/elements/shapes.py +28 -25
- meerk40t/core/laserjob.py +7 -0
- meerk40t/core/node/bootstrap.py +4 -0
- meerk40t/core/node/effect_hatch.py +85 -96
- meerk40t/core/node/effect_wobble.py +309 -0
- meerk40t/core/node/elem_image.py +49 -19
- meerk40t/core/node/elem_line.py +60 -0
- meerk40t/core/node/elem_rect.py +5 -3
- meerk40t/core/node/image_processed.py +766 -0
- meerk40t/core/node/image_raster.py +113 -0
- meerk40t/core/node/node.py +120 -1
- meerk40t/core/node/op_cut.py +2 -8
- meerk40t/core/node/op_dots.py +0 -8
- meerk40t/core/node/op_engrave.py +2 -18
- meerk40t/core/node/op_image.py +22 -35
- meerk40t/core/node/op_raster.py +0 -9
- meerk40t/core/planner.py +32 -2
- meerk40t/core/svg_io.py +699 -461
- meerk40t/core/treeop.py +191 -0
- meerk40t/core/undos.py +15 -1
- meerk40t/core/units.py +14 -4
- meerk40t/device/dummydevice.py +3 -2
- meerk40t/device/gui/defaultactions.py +43 -55
- meerk40t/device/gui/formatterpanel.py +58 -49
- meerk40t/device/gui/warningpanel.py +12 -12
- meerk40t/device/mixins.py +13 -0
- meerk40t/dxf/dxf_io.py +9 -5
- meerk40t/extra/ezd.py +28 -26
- meerk40t/extra/imageactions.py +300 -308
- meerk40t/extra/lbrn.py +19 -2
- meerk40t/fill/fills.py +6 -6
- meerk40t/fill/patternfill.py +1061 -1061
- meerk40t/fill/patterns.py +2 -6
- meerk40t/grbl/controller.py +168 -52
- meerk40t/grbl/device.py +23 -18
- meerk40t/grbl/driver.py +39 -0
- meerk40t/grbl/emulator.py +79 -19
- meerk40t/grbl/gcodejob.py +10 -0
- meerk40t/grbl/gui/grblconfiguration.py +2 -2
- meerk40t/grbl/gui/grblcontroller.py +24 -8
- meerk40t/grbl/gui/grblhardwareconfig.py +153 -0
- meerk40t/grbl/gui/gui.py +17 -14
- meerk40t/grbl/mock_connection.py +15 -34
- meerk40t/grbl/plugin.py +0 -4
- meerk40t/grbl/serial_connection.py +2 -1
- meerk40t/gui/about.py +8 -5
- meerk40t/gui/alignment.py +10 -6
- meerk40t/gui/basicops.py +27 -17
- meerk40t/gui/bufferview.py +2 -2
- meerk40t/gui/choicepropertypanel.py +101 -13
- meerk40t/gui/consolepanel.py +12 -9
- meerk40t/gui/devicepanel.py +38 -25
- meerk40t/gui/executejob.py +6 -4
- meerk40t/gui/help_assets/help_assets.py +13 -10
- meerk40t/gui/hersheymanager.py +8 -6
- meerk40t/gui/icons.py +1951 -3065
- meerk40t/gui/imagesplitter.py +14 -7
- meerk40t/gui/keymap.py +3 -3
- meerk40t/gui/laserpanel.py +151 -84
- meerk40t/gui/laserrender.py +61 -70
- meerk40t/gui/lasertoolpanel.py +8 -7
- meerk40t/gui/materialtest.py +3 -3
- meerk40t/gui/mkdebug.py +254 -1
- meerk40t/gui/navigationpanels.py +321 -180
- meerk40t/gui/notes.py +3 -3
- meerk40t/gui/opassignment.py +12 -12
- meerk40t/gui/operation_info.py +13 -13
- meerk40t/gui/plugin.py +5 -0
- meerk40t/gui/position.py +20 -18
- meerk40t/gui/preferences.py +21 -6
- meerk40t/gui/propertypanels/attributes.py +70 -22
- meerk40t/gui/propertypanels/blobproperty.py +2 -2
- meerk40t/gui/propertypanels/consoleproperty.py +2 -2
- meerk40t/gui/propertypanels/groupproperties.py +3 -3
- meerk40t/gui/propertypanels/hatchproperty.py +11 -18
- meerk40t/gui/propertypanels/imageproperty.py +4 -3
- meerk40t/gui/propertypanels/opbranchproperties.py +1 -1
- meerk40t/gui/propertypanels/pathproperty.py +2 -2
- meerk40t/gui/propertypanels/pointproperty.py +2 -2
- meerk40t/gui/propertypanels/propertywindow.py +4 -4
- meerk40t/gui/propertypanels/textproperty.py +3 -3
- meerk40t/gui/propertypanels/wobbleproperty.py +204 -0
- meerk40t/gui/ribbon.py +367 -259
- meerk40t/gui/scene/scene.py +31 -5
- meerk40t/gui/scenewidgets/elementswidget.py +12 -4
- meerk40t/gui/scenewidgets/gridwidget.py +2 -2
- meerk40t/gui/scenewidgets/laserpathwidget.py +7 -2
- meerk40t/gui/scenewidgets/machineoriginwidget.py +6 -2
- meerk40t/gui/scenewidgets/relocatewidget.py +1 -1
- meerk40t/gui/scenewidgets/reticlewidget.py +9 -0
- meerk40t/gui/scenewidgets/selectionwidget.py +12 -7
- meerk40t/gui/simpleui.py +95 -8
- meerk40t/gui/simulation.py +44 -36
- meerk40t/gui/spoolerpanel.py +124 -26
- meerk40t/gui/statusbarwidgets/defaultoperations.py +18 -6
- meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
- meerk40t/gui/statusbarwidgets/opassignwidget.py +12 -12
- meerk40t/gui/statusbarwidgets/shapepropwidget.py +45 -18
- meerk40t/gui/statusbarwidgets/statusbar.py +11 -4
- meerk40t/gui/themes.py +78 -0
- meerk40t/gui/toolwidgets/toolcircle.py +2 -1
- meerk40t/gui/toolwidgets/toolellipse.py +2 -1
- meerk40t/gui/toolwidgets/toolimagecut.py +132 -0
- meerk40t/gui/toolwidgets/toolline.py +144 -0
- meerk40t/gui/toolwidgets/toolnodeedit.py +72 -145
- meerk40t/gui/toolwidgets/toolpoint.py +1 -1
- meerk40t/gui/toolwidgets/toolpolygon.py +8 -55
- meerk40t/gui/toolwidgets/toolrect.py +2 -1
- meerk40t/gui/usbconnect.py +2 -2
- meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +2 -2
- meerk40t/gui/utilitywidgets/harmonograph.py +7 -7
- meerk40t/gui/utilitywidgets/scalewidget.py +1 -1
- meerk40t/gui/wordlisteditor.py +33 -18
- meerk40t/gui/wxmeerk40t.py +166 -66
- meerk40t/gui/wxmmain.py +236 -157
- meerk40t/gui/wxmribbon.py +49 -25
- meerk40t/gui/wxmscene.py +49 -38
- meerk40t/gui/wxmtree.py +216 -85
- meerk40t/gui/wxutils.py +62 -4
- meerk40t/image/imagetools.py +443 -15
- meerk40t/internal_plugins.py +2 -10
- meerk40t/kernel/kernel.py +12 -4
- meerk40t/lihuiyu/controller.py +7 -7
- meerk40t/lihuiyu/device.py +3 -1
- meerk40t/lihuiyu/driver.py +3 -0
- meerk40t/lihuiyu/gui/gui.py +8 -8
- meerk40t/lihuiyu/gui/lhyaccelgui.py +2 -2
- meerk40t/lihuiyu/gui/lhycontrollergui.py +73 -27
- meerk40t/lihuiyu/gui/lhydrivergui.py +2 -2
- meerk40t/lihuiyu/gui/tcpcontroller.py +22 -9
- meerk40t/main.py +6 -1
- meerk40t/moshi/controller.py +5 -5
- meerk40t/moshi/device.py +5 -2
- meerk40t/moshi/driver.py +4 -0
- meerk40t/moshi/gui/gui.py +8 -8
- meerk40t/moshi/gui/moshicontrollergui.py +24 -8
- meerk40t/moshi/gui/moshidrivergui.py +2 -2
- meerk40t/newly/controller.py +2 -0
- meerk40t/newly/device.py +9 -2
- meerk40t/newly/driver.py +4 -0
- meerk40t/newly/gui/gui.py +16 -17
- meerk40t/newly/gui/newlyconfig.py +2 -2
- meerk40t/newly/gui/newlycontroller.py +13 -5
- meerk40t/rotary/gui/gui.py +2 -2
- meerk40t/rotary/gui/rotarysettings.py +2 -2
- meerk40t/ruida/device.py +3 -0
- meerk40t/ruida/driver.py +4 -0
- meerk40t/ruida/gui/gui.py +6 -6
- meerk40t/ruida/gui/ruidaconfig.py +2 -2
- meerk40t/ruida/gui/ruidacontroller.py +13 -5
- meerk40t/svgelements.py +9 -9
- meerk40t/tools/geomstr.py +849 -153
- meerk40t/tools/kerftest.py +8 -4
- meerk40t/tools/livinghinges.py +15 -8
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/METADATA +21 -16
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/RECORD +185 -177
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/entry_points.txt +0 -1
- test/test_core_elements.py +8 -24
- test/test_file_svg.py +88 -0
- test/test_fill.py +9 -9
- test/test_geomstr.py +258 -8
- test/test_kernel.py +4 -0
- test/test_tools_rasterplotter.py +29 -0
- meerk40t/extra/embroider.py +0 -56
- meerk40t/extra/pathoptimize.py +0 -249
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/LICENSE +0 -0
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/WHEEL +0 -0
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/top_level.txt +0 -0
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/zip-safe +0 -0
meerk40t/image/imagetools.py
CHANGED
@@ -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
|
-
|
1018
|
-
|
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
|
-
|
1026
|
-
|
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
|
-
|
1059
|
-
inode_bottom.image = image_bottom
|
1060
|
-
inode_bottom.transform.pre_translate(0, y)
|
1141
|
+
parent = inode.parent
|
1061
1142
|
|
1062
|
-
|
1143
|
+
inode.remove_node()
|
1063
1144
|
elements = context.elements
|
1064
|
-
|
1065
|
-
|
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": (
|
meerk40t/internal_plugins.py
CHANGED
@@ -56,9 +56,9 @@ def plugin(kernel, lifecycle):
|
|
56
56
|
|
57
57
|
plugins.append(fills.plugin)
|
58
58
|
|
59
|
-
from .fill import
|
59
|
+
from .fill import patterns
|
60
60
|
|
61
|
-
plugins.append(
|
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
|
-
|
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
|
# ==========
|
meerk40t/lihuiyu/controller.py
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
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.
|
570
|
+
self.context.laser_status = "idle"
|
571
571
|
time.sleep(0.5)
|
572
572
|
self.close()
|
573
573
|
continue
|
574
574
|
|
575
|
-
self.context.
|
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.
|
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
|
meerk40t/lihuiyu/device.py
CHANGED
@@ -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"
|
meerk40t/lihuiyu/driver.py
CHANGED
@@ -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")
|