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.
- meerk40t/balormk/clone_loader.py +3 -2
- meerk40t/balormk/controller.py +28 -11
- meerk40t/balormk/cylindermod.py +1 -0
- meerk40t/balormk/device.py +13 -9
- meerk40t/balormk/driver.py +9 -2
- meerk40t/balormk/galvo_commands.py +3 -1
- meerk40t/balormk/gui/gui.py +6 -0
- meerk40t/balormk/livelightjob.py +338 -321
- meerk40t/balormk/mock_connection.py +4 -3
- meerk40t/balormk/usb_connection.py +11 -2
- meerk40t/camera/camera.py +19 -14
- meerk40t/camera/gui/camerapanel.py +6 -0
- meerk40t/core/cutcode/cutcode.py +1 -1
- meerk40t/core/cutplan.py +169 -43
- meerk40t/core/elements/element_treeops.py +444 -147
- meerk40t/core/elements/elements.py +100 -9
- meerk40t/core/elements/grid.py +8 -1
- meerk40t/core/elements/offset_mk.py +2 -1
- meerk40t/core/elements/shapes.py +618 -279
- meerk40t/core/elements/tree_commands.py +10 -5
- meerk40t/core/node/elem_ellipse.py +18 -8
- meerk40t/core/node/elem_image.py +51 -19
- meerk40t/core/node/elem_line.py +18 -8
- meerk40t/core/node/elem_path.py +18 -8
- meerk40t/core/node/elem_point.py +10 -4
- meerk40t/core/node/elem_polyline.py +19 -11
- meerk40t/core/node/elem_rect.py +18 -8
- meerk40t/core/node/elem_text.py +11 -5
- meerk40t/core/node/filenode.py +2 -8
- meerk40t/core/node/groupnode.py +11 -11
- meerk40t/core/node/image_processed.py +11 -5
- meerk40t/core/node/image_raster.py +11 -5
- meerk40t/core/node/node.py +70 -19
- meerk40t/core/node/refnode.py +2 -1
- meerk40t/core/planner.py +23 -0
- meerk40t/core/svg_io.py +91 -34
- meerk40t/core/undos.py +1 -1
- meerk40t/core/wordlist.py +1 -0
- meerk40t/device/dummydevice.py +7 -1
- meerk40t/dxf/dxf_io.py +6 -0
- meerk40t/extra/mk_potrace.py +1959 -0
- meerk40t/extra/param_functions.py +1 -1
- meerk40t/extra/potrace.py +14 -10
- meerk40t/extra/vtracer.py +222 -0
- meerk40t/grbl/device.py +81 -8
- meerk40t/grbl/interpreter.py +1 -1
- meerk40t/gui/about.py +21 -3
- meerk40t/gui/basicops.py +3 -3
- meerk40t/gui/choicepropertypanel.py +1 -4
- meerk40t/gui/devicepanel.py +20 -16
- meerk40t/gui/gui_mixins.py +8 -1
- meerk40t/gui/icons.py +330 -253
- meerk40t/gui/laserpanel.py +8 -3
- meerk40t/gui/laserrender.py +41 -21
- meerk40t/gui/magnetoptions.py +158 -65
- meerk40t/gui/materialtest.py +229 -39
- meerk40t/gui/navigationpanels.py +229 -24
- meerk40t/gui/propertypanels/hatchproperty.py +2 -0
- meerk40t/gui/propertypanels/imageproperty.py +160 -106
- meerk40t/gui/ribbon.py +6 -1
- meerk40t/gui/scenewidgets/gridwidget.py +29 -32
- meerk40t/gui/scenewidgets/rectselectwidget.py +190 -192
- meerk40t/gui/simulation.py +75 -77
- meerk40t/gui/spoolerpanel.py +6 -9
- meerk40t/gui/statusbarwidgets/defaultoperations.py +84 -48
- meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
- meerk40t/gui/themes.py +7 -1
- meerk40t/gui/tips.py +15 -1
- meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
- meerk40t/gui/wxmeerk40t.py +26 -0
- meerk40t/gui/wxmmain.py +242 -114
- meerk40t/gui/wxmscene.py +180 -4
- meerk40t/gui/wxmtree.py +4 -2
- meerk40t/gui/wxutils.py +60 -15
- meerk40t/image/imagetools.py +130 -66
- meerk40t/internal_plugins.py +4 -0
- meerk40t/kernel/kernel.py +49 -22
- meerk40t/kernel/settings.py +29 -8
- meerk40t/lihuiyu/device.py +30 -12
- meerk40t/main.py +22 -5
- meerk40t/moshi/device.py +20 -6
- meerk40t/network/console_server.py +22 -6
- meerk40t/newly/device.py +10 -3
- meerk40t/newly/gui/gui.py +10 -0
- meerk40t/ruida/device.py +22 -2
- meerk40t/ruida/gui/gui.py +6 -6
- meerk40t/ruida/gui/ruidaoperationproperties.py +1 -10
- meerk40t/ruida/loader.py +6 -3
- meerk40t/ruida/rdjob.py +3 -3
- meerk40t/tools/geomstr.py +195 -39
- meerk40t/tools/rasterplotter.py +179 -93
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/METADATA +1 -1
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/RECORD +98 -96
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/WHEEL +1 -1
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/LICENSE +0 -0
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/entry_points.txt +0 -0
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/top_level.txt +0 -0
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/zip-safe +0 -0
meerk40t/image/imagetools.py
CHANGED
@@ -19,11 +19,11 @@ from .dither import dither
|
|
19
19
|
|
20
20
|
|
21
21
|
def img_to_polygons(
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
node_image, # The image
|
23
|
+
minimal, # Minimum area in percent (int) to consider
|
24
|
+
maximal, # Maximum area in percent (int) to consider
|
25
|
+
ignoreinner, # Ignore inner contours
|
26
|
+
needs_invert=True, # Does the image require inverting (we need white strcutures on a black background)
|
27
27
|
):
|
28
28
|
"""
|
29
29
|
Takes the image and provides a list of geomstr + associated matrix
|
@@ -33,7 +33,7 @@ def img_to_polygons(
|
|
33
33
|
import cv2
|
34
34
|
from PIL import ImageOps
|
35
35
|
except ImportError:
|
36
|
-
return
|
36
|
+
return [[], []]
|
37
37
|
geom_list = list()
|
38
38
|
|
39
39
|
# Convert the image to grayscale
|
@@ -44,10 +44,12 @@ def img_to_polygons(
|
|
44
44
|
|
45
45
|
# Find contours in the binary image
|
46
46
|
try:
|
47
|
-
contours, hierarchies = cv2.findContours(
|
47
|
+
contours, hierarchies = cv2.findContours(
|
48
|
+
th2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE
|
49
|
+
)
|
48
50
|
except ValueError:
|
49
51
|
# Invalid data
|
50
|
-
return
|
52
|
+
return [[], []]
|
51
53
|
|
52
54
|
# print(f"Found {len(contours)} contours and {len(hierarchies)} hierarchies")
|
53
55
|
width, height = node_image.size
|
@@ -68,12 +70,12 @@ def img_to_polygons(
|
|
68
70
|
continue
|
69
71
|
region = contour.squeeze().astype(float).tolist()
|
70
72
|
if type(region[0]) is list and len(region) > 2:
|
71
|
-
crdnts = [{
|
73
|
+
crdnts = [{"x": i[0], "y": i[1]} for i in region]
|
72
74
|
|
73
75
|
geom = Geomstr()
|
74
76
|
notfirst = False
|
75
77
|
for pnt in crdnts:
|
76
|
-
rx, ry = pnt[
|
78
|
+
rx, ry = pnt["x"], pnt["y"]
|
77
79
|
if notfirst:
|
78
80
|
geom.line(complex(lx, ly), complex(rx, ry))
|
79
81
|
notfirst = True
|
@@ -81,20 +83,20 @@ def img_to_polygons(
|
|
81
83
|
ly = ry
|
82
84
|
geom.close()
|
83
85
|
matrix = Matrix()
|
84
|
-
geom_list.append(
|
86
|
+
geom_list.append((geom, 100 * area / (width * height)))
|
85
87
|
|
86
88
|
return geom_list
|
87
89
|
|
88
|
-
def do_innerwhite(
|
89
|
-
minimal=None,
|
90
|
-
outer=False,
|
91
|
-
simplified=False,
|
92
|
-
line=False,
|
93
|
-
breakdown=False,
|
94
|
-
whiten=False,
|
95
|
-
data=None,
|
96
|
-
):
|
97
90
|
|
91
|
+
def do_innerwhite(
|
92
|
+
minimal=None,
|
93
|
+
outer=False,
|
94
|
+
simplified=False,
|
95
|
+
line=False,
|
96
|
+
breakdown=False,
|
97
|
+
whiten=False,
|
98
|
+
data=None,
|
99
|
+
):
|
98
100
|
import cv2
|
99
101
|
|
100
102
|
def org_bounds(node):
|
@@ -342,9 +344,7 @@ def do_innerwhite(
|
|
342
344
|
# print(f"Erasing right: {dx}:{rwidth}")
|
343
345
|
newnode = copy(inode)
|
344
346
|
newnode.matrix = copy(inode.matrix)
|
345
|
-
newnode.label = (
|
346
|
-
f"[{anyslices}]{'' if inode.label is None else inode.display_label()}"
|
347
|
-
)
|
347
|
+
newnode.label = f"[{anyslices}]{'' if inode.label is None else inode.display_label()}"
|
348
348
|
# newnode.dither = False
|
349
349
|
# newnode.operations.clear()
|
350
350
|
# newnode.prevent_crop = True
|
@@ -364,9 +364,7 @@ def do_innerwhite(
|
|
364
364
|
anyslices += 1
|
365
365
|
newnode = copy(inode)
|
366
366
|
newnode.matrix = copy(inode.matrix)
|
367
|
-
newnode.label = (
|
368
|
-
f"[{anyslices}]{'' if inode.label is None else inode.display_label()}"
|
369
|
-
)
|
367
|
+
newnode.label = f"[{anyslices}]{'' if inode.label is None else inode.display_label()}"
|
370
368
|
# newnode.dither = False
|
371
369
|
# newnode.operations.clear()
|
372
370
|
# newnode.prevent_crop = True
|
@@ -382,12 +380,13 @@ def do_innerwhite(
|
|
382
380
|
data_out.append(newnode)
|
383
381
|
return data_out
|
384
382
|
|
383
|
+
|
385
384
|
def img_to_rectangles(
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
385
|
+
node_image, # The image
|
386
|
+
minimal, # Minimum area in percent (int) to consider
|
387
|
+
maximal, # Maximum area in percent (int) to consider
|
388
|
+
ignoreinner, # Ignore inner contours
|
389
|
+
needs_invert=True, # Does the image require inverting (we need white strcutures on a black background)
|
391
390
|
):
|
392
391
|
"""
|
393
392
|
Takes the image and provides a list of geomstr + associated matrix
|
@@ -413,7 +412,9 @@ def img_to_rectangles(
|
|
413
412
|
|
414
413
|
# Find contours in the binary image
|
415
414
|
try:
|
416
|
-
contours, hierarchies = cv2.findContours(
|
415
|
+
contours, hierarchies = cv2.findContours(
|
416
|
+
th2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE
|
417
|
+
)
|
417
418
|
except ValueError:
|
418
419
|
# Invalid data
|
419
420
|
return ([], [])
|
@@ -442,12 +443,20 @@ def img_to_rectangles(
|
|
442
443
|
# print (f"center: {center_x:.2f}, {center_y:.2f}, dimension: {rect_width:.2f}x{rect_height:.2f}, angle={angle_deg:.1f}")
|
443
444
|
# print (box)
|
444
445
|
geom = Geomstr()
|
445
|
-
geom.line(
|
446
|
-
|
447
|
-
|
448
|
-
geom.line(
|
446
|
+
geom.line(
|
447
|
+
start=complex(box[0][0], box[0][1]), end=complex(box[1][0], box[1][1])
|
448
|
+
)
|
449
|
+
geom.line(
|
450
|
+
start=complex(box[1][0], box[1][1]), end=complex(box[2][0], box[2][1])
|
451
|
+
)
|
452
|
+
geom.line(
|
453
|
+
start=complex(box[2][0], box[2][1]), end=complex(box[3][0], box[3][1])
|
454
|
+
)
|
455
|
+
geom.line(
|
456
|
+
start=complex(box[3][0], box[3][1]), end=complex(box[0][0], box[0][1])
|
457
|
+
)
|
449
458
|
# Geometry plus enclosed area
|
450
|
-
geom_list.append(
|
459
|
+
geom_list.append((geom, 100 * area / (width * height)))
|
451
460
|
|
452
461
|
return geom_list
|
453
462
|
|
@@ -509,7 +518,9 @@ def plugin(kernel, lifecycle=None):
|
|
509
518
|
"default": True,
|
510
519
|
"type": bool,
|
511
520
|
"label": _("Scale oversized images"),
|
512
|
-
"tip": _(
|
521
|
+
"tip": _(
|
522
|
+
"Set: Will scale down large images so they will fit on the laserbed."
|
523
|
+
),
|
513
524
|
"page": "Input/Output",
|
514
525
|
"section": "Images",
|
515
526
|
},
|
@@ -717,8 +728,9 @@ def plugin(kernel, lifecycle=None):
|
|
717
728
|
):
|
718
729
|
if threshold_min is None:
|
719
730
|
raise CommandSyntaxError
|
720
|
-
threshold_min, threshold_max =
|
721
|
-
threshold_max,
|
731
|
+
threshold_min, threshold_max = (
|
732
|
+
min(threshold_min, threshold_max),
|
733
|
+
max(threshold_max, threshold_min),
|
722
734
|
)
|
723
735
|
divide = (threshold_max - threshold_min) / 255.0
|
724
736
|
for node in data:
|
@@ -1282,7 +1294,7 @@ def plugin(kernel, lifecycle=None):
|
|
1282
1294
|
_("Can't modify a locked image: {name}").format(name=str(inode))
|
1283
1295
|
)
|
1284
1296
|
continue
|
1285
|
-
img = inode.image
|
1297
|
+
img = inode.image # opaque_image
|
1286
1298
|
original_mode = inode.image.mode
|
1287
1299
|
img = img.convert("RGBA")
|
1288
1300
|
try:
|
@@ -1495,7 +1507,7 @@ def plugin(kernel, lifecycle=None):
|
|
1495
1507
|
)
|
1496
1508
|
@context.console_command(
|
1497
1509
|
"linecut",
|
1498
|
-
help=_("Cuts
|
1510
|
+
help=_("Cuts an image with a line"),
|
1499
1511
|
input_type="image",
|
1500
1512
|
output_type="image",
|
1501
1513
|
hidden=True,
|
@@ -1859,7 +1871,9 @@ def plugin(kernel, lifecycle=None):
|
|
1859
1871
|
update_image_node(inode)
|
1860
1872
|
return "image", data
|
1861
1873
|
|
1862
|
-
@context.console_option(
|
1874
|
+
@context.console_option(
|
1875
|
+
"minimal", "m", type=float, help=_("minimal area (%)"), default=2
|
1876
|
+
)
|
1863
1877
|
@context.console_option(
|
1864
1878
|
"outer",
|
1865
1879
|
"o",
|
@@ -1915,7 +1929,6 @@ def plugin(kernel, lifecycle=None):
|
|
1915
1929
|
post=None,
|
1916
1930
|
**kwargs,
|
1917
1931
|
):
|
1918
|
-
|
1919
1932
|
try:
|
1920
1933
|
import cv2
|
1921
1934
|
except ImportError:
|
@@ -1950,7 +1963,7 @@ def plugin(kernel, lifecycle=None):
|
|
1950
1963
|
if len(data_out):
|
1951
1964
|
# _("Image white")
|
1952
1965
|
with context.elements.undoscope("Image white"):
|
1953
|
-
needs_adding = [True]*len(data_out)
|
1966
|
+
needs_adding = [True] * len(data_out)
|
1954
1967
|
if breakdown or whiten:
|
1955
1968
|
for inode in data:
|
1956
1969
|
if inode in data_out:
|
@@ -1965,8 +1978,16 @@ def plugin(kernel, lifecycle=None):
|
|
1965
1978
|
post.append(context.elements.post_classify(data_out))
|
1966
1979
|
return "image", data_out
|
1967
1980
|
|
1968
|
-
@context.console_option(
|
1969
|
-
|
1981
|
+
@context.console_option(
|
1982
|
+
"threshold",
|
1983
|
+
"t",
|
1984
|
+
type=float,
|
1985
|
+
help=_("Threshold for simplification"),
|
1986
|
+
default=0.25,
|
1987
|
+
)
|
1988
|
+
@context.console_option(
|
1989
|
+
"minimal", "m", type=float, help=_("minimal area (%)"), default=2
|
1990
|
+
)
|
1970
1991
|
@context.console_option(
|
1971
1992
|
"inner",
|
1972
1993
|
"i",
|
@@ -2028,7 +2049,9 @@ def plugin(kernel, lifecycle=None):
|
|
2028
2049
|
elements = context.elements
|
2029
2050
|
# from PIL import Image
|
2030
2051
|
if data is None:
|
2031
|
-
data = list(
|
2052
|
+
data = list(
|
2053
|
+
e for e in elements.flat(emphasized=True) if e.type == "elem image"
|
2054
|
+
)
|
2032
2055
|
if data is None or len(data) == 0:
|
2033
2056
|
channel(_("No images selected"))
|
2034
2057
|
return
|
@@ -2050,7 +2073,9 @@ def plugin(kernel, lifecycle=None):
|
|
2050
2073
|
if threshold is None:
|
2051
2074
|
# We are on pixel level
|
2052
2075
|
threshold = 0.25
|
2053
|
-
channel(
|
2076
|
+
channel(
|
2077
|
+
f"Contouring: {minimal:.2f} <= area <= {maximal:.2f}, inverting: {'No' if dontinvert else 'Yes'}, threshold: {threshold:.2f}, inner: {'No' if ignoreinner else 'Yes'}"
|
2078
|
+
)
|
2054
2079
|
data_out = list()
|
2055
2080
|
|
2056
2081
|
remembered_dithers = list()
|
@@ -2073,13 +2098,25 @@ def plugin(kernel, lifecycle=None):
|
|
2073
2098
|
continue
|
2074
2099
|
# Extract polygons from the image
|
2075
2100
|
if rectangles:
|
2076
|
-
contours = img_to_rectangles(
|
2101
|
+
contours = img_to_rectangles(
|
2102
|
+
node_image,
|
2103
|
+
minimal,
|
2104
|
+
maximal,
|
2105
|
+
ignoreinner,
|
2106
|
+
needs_invert=not dontinvert,
|
2107
|
+
)
|
2077
2108
|
msg = "Bounding"
|
2078
2109
|
else:
|
2079
|
-
contours = img_to_polygons(
|
2110
|
+
contours = img_to_polygons(
|
2111
|
+
node_image,
|
2112
|
+
minimal,
|
2113
|
+
maximal,
|
2114
|
+
ignoreinner,
|
2115
|
+
needs_invert=not dontinvert,
|
2116
|
+
)
|
2080
2117
|
msg = "Contour"
|
2081
2118
|
pidx = 0
|
2082
|
-
for
|
2119
|
+
for geom, c_info in contours:
|
2083
2120
|
pidx += 1
|
2084
2121
|
channel(f"Processing {idx+1}.{pidx}: area={c_info:.2f}%")
|
2085
2122
|
if simplified:
|
@@ -2101,7 +2138,9 @@ def plugin(kernel, lifecycle=None):
|
|
2101
2138
|
inode.update(None)
|
2102
2139
|
|
2103
2140
|
t_total_overall = time.perf_counter() - t0
|
2104
|
-
channel(
|
2141
|
+
channel(
|
2142
|
+
f"Done, created: {len(data_out)} contour elements >= {minimal}% (Total time: {t_total_overall:.2f} sec)"
|
2143
|
+
)
|
2105
2144
|
post.append(context.elements.post_classify(data_out))
|
2106
2145
|
return "elements", data_out
|
2107
2146
|
|
@@ -2119,7 +2158,11 @@ def plugin(kernel, lifecycle=None):
|
|
2119
2158
|
**kwargs,
|
2120
2159
|
):
|
2121
2160
|
if data is None:
|
2122
|
-
data = list(
|
2161
|
+
data = list(
|
2162
|
+
e
|
2163
|
+
for e in context.elements.flat(emphasized=True)
|
2164
|
+
if e.type == "elem image"
|
2165
|
+
)
|
2123
2166
|
if len(data) == 0:
|
2124
2167
|
channel("No image provided")
|
2125
2168
|
return
|
@@ -2141,8 +2184,8 @@ def plugin(kernel, lifecycle=None):
|
|
2141
2184
|
@context.console_command(
|
2142
2185
|
"linefill",
|
2143
2186
|
help=_("Create a linefill representation of the provided images"),
|
2144
|
-
input_type
|
2145
|
-
output_type
|
2187
|
+
input_type=(None, "image", "elements"),
|
2188
|
+
output_type="elements",
|
2146
2189
|
)
|
2147
2190
|
def do_linefill(
|
2148
2191
|
command,
|
@@ -2153,6 +2196,7 @@ def plugin(kernel, lifecycle=None):
|
|
2153
2196
|
**kwargs,
|
2154
2197
|
):
|
2155
2198
|
from time import perf_counter
|
2199
|
+
|
2156
2200
|
def trace_black_areas(image):
|
2157
2201
|
def dfs(x, y):
|
2158
2202
|
path = []
|
@@ -2165,7 +2209,14 @@ def plugin(kernel, lifecycle=None):
|
|
2165
2209
|
continue
|
2166
2210
|
visited[x, y] = True
|
2167
2211
|
path.append((x_prev, y_prev, x, y))
|
2168
|
-
stack.extend(
|
2212
|
+
stack.extend(
|
2213
|
+
[
|
2214
|
+
(x, y, x + 1, y),
|
2215
|
+
(x, y, x - 1, y),
|
2216
|
+
(x, y, x, y + 1),
|
2217
|
+
(x, y, x, y - 1),
|
2218
|
+
]
|
2219
|
+
)
|
2169
2220
|
return path
|
2170
2221
|
|
2171
2222
|
visited = np.zeros_like(image, dtype=bool)
|
@@ -2181,7 +2232,11 @@ def plugin(kernel, lifecycle=None):
|
|
2181
2232
|
return separated
|
2182
2233
|
|
2183
2234
|
if data is None:
|
2184
|
-
data = list(
|
2235
|
+
data = list(
|
2236
|
+
e
|
2237
|
+
for e in context.elements.flat(emphasized=True)
|
2238
|
+
if e.type == "elem image"
|
2239
|
+
)
|
2185
2240
|
if len(data) == 0:
|
2186
2241
|
channel("No image provided")
|
2187
2242
|
return
|
@@ -2209,13 +2264,15 @@ def plugin(kernel, lifecycle=None):
|
|
2209
2264
|
|
2210
2265
|
multiple = trace_black_areas(image)
|
2211
2266
|
points = sum(len(path) for path in multiple)
|
2212
|
-
channel
|
2267
|
+
channel(f"Found points: {points}, subpaths={len(multiple)}")
|
2213
2268
|
# for y in range(20):
|
2214
2269
|
# msg = f"[{y:2d}]"
|
2215
2270
|
# for x in range(20):
|
2216
2271
|
# msg =f"{msg} {image[x,y]}"
|
2217
2272
|
# print (msg)
|
2218
|
-
matrix = Matrix(
|
2273
|
+
matrix = Matrix(
|
2274
|
+
f"scale ({(bounds[2] - bounds[0]) / node_image.width}, {(bounds[3] - bounds[1]) / node_image.height})"
|
2275
|
+
)
|
2219
2276
|
matrix.post_translate(bounds[0], bounds[1])
|
2220
2277
|
# matrix = node.active_matrix
|
2221
2278
|
|
@@ -2234,8 +2291,8 @@ def plugin(kernel, lifecycle=None):
|
|
2234
2291
|
stroke=Color("blue"),
|
2235
2292
|
label=label,
|
2236
2293
|
type="elem path",
|
2237
|
-
stroke_width
|
2238
|
-
linejoin=Linejoin.JOIN_ROUND
|
2294
|
+
stroke_width=1000,
|
2295
|
+
linejoin=Linejoin.JOIN_ROUND,
|
2239
2296
|
)
|
2240
2297
|
data_out.append(rnode)
|
2241
2298
|
return idx
|
@@ -2254,7 +2311,9 @@ def plugin(kernel, lifecycle=None):
|
|
2254
2311
|
y_prev, x_prev, y, x = path[i]
|
2255
2312
|
dx = x - x_prev
|
2256
2313
|
dy = y - y_prev
|
2257
|
-
if (
|
2314
|
+
if (
|
2315
|
+
dx * dx + dy * dy
|
2316
|
+
) >= 4: # Thats too wide by definition, new subpath
|
2258
2317
|
idx = save_geom(geom, idx, matrix)
|
2259
2318
|
geom = Geomstr()
|
2260
2319
|
continue
|
@@ -2268,6 +2327,7 @@ def plugin(kernel, lifecycle=None):
|
|
2268
2327
|
post.append(context.elements.post_classify(data_out))
|
2269
2328
|
return "elements", data_out
|
2270
2329
|
|
2330
|
+
|
2271
2331
|
class RasterImagePreprocessor:
|
2272
2332
|
def __init__(self, kernel):
|
2273
2333
|
self.test = False
|
@@ -2275,7 +2335,10 @@ class RasterImagePreprocessor:
|
|
2275
2335
|
_ = self.kernel.translation
|
2276
2336
|
RASTER_METHOD_OFFSET = 100
|
2277
2337
|
self.methods = {
|
2278
|
-
RASTER_METHOD_OFFSET + 0: (
|
2338
|
+
RASTER_METHOD_OFFSET + 0: (
|
2339
|
+
_("Split image along white areas"),
|
2340
|
+
self.process_innerwhite,
|
2341
|
+
),
|
2279
2342
|
}
|
2280
2343
|
self.register_methods()
|
2281
2344
|
|
@@ -2311,11 +2374,12 @@ class RasterImagePreprocessor:
|
|
2311
2374
|
operation.bidirectional = True
|
2312
2375
|
# print (f"Innerwhite exit: {len(operation.children)} children of {operation.type}")
|
2313
2376
|
|
2314
|
-
|
2315
2377
|
def register_methods(self):
|
2316
2378
|
# Split image along white areas
|
2317
2379
|
for method, (description, routine) in self.methods.items():
|
2318
|
-
self.kernel.register(
|
2380
|
+
self.kernel.register(
|
2381
|
+
f"raster_preprocessor/Method_{method}", (method, description, routine)
|
2382
|
+
)
|
2319
2383
|
|
2320
2384
|
|
2321
2385
|
class RasterScripts:
|
meerk40t/internal_plugins.py
CHANGED
meerk40t/kernel/kernel.py
CHANGED
@@ -181,6 +181,7 @@ class Kernel(Settings):
|
|
181
181
|
self.args = None
|
182
182
|
|
183
183
|
self.os_information = self._get_environment()
|
184
|
+
self.show_aio_prompt = True
|
184
185
|
|
185
186
|
def __str__(self):
|
186
187
|
return f"Kernel({self.name}, {self.profile}, {self.version})"
|
@@ -188,8 +189,9 @@ class Kernel(Settings):
|
|
188
189
|
def _get_environment(self):
|
189
190
|
from platform import system
|
190
191
|
from tempfile import gettempdir
|
192
|
+
|
191
193
|
return {
|
192
|
-
"OS_NAME":
|
194
|
+
"OS_NAME": system(),
|
193
195
|
"OS_TEMPDIR": os.path.realpath(gettempdir()),
|
194
196
|
"WORKDIR": os.path.realpath(get_safe_path(self.name, create=True)),
|
195
197
|
}
|
@@ -254,15 +256,21 @@ class Kernel(Settings):
|
|
254
256
|
kwargs_repr = [f"{k}={v}" for k, v in kwargs.items()]
|
255
257
|
signature = ", ".join(args_repr + kwargs_repr)
|
256
258
|
start = f"Calling {str(obj)}.{func.__name__}({signature})"
|
257
|
-
|
259
|
+
try:
|
260
|
+
debug_file.write(start + "\n")
|
261
|
+
except (ValueError, OSError):
|
262
|
+
pass
|
258
263
|
print(start)
|
259
264
|
t = time.time()
|
260
265
|
value = func(*args, **kwargs)
|
261
266
|
t = time.time() - t
|
262
267
|
finish = f" {func.__name__} returned {value} after {t * 1000}ms"
|
263
268
|
print(finish)
|
264
|
-
|
265
|
-
|
269
|
+
try:
|
270
|
+
debug_file.write(finish + "\n")
|
271
|
+
debug_file.flush()
|
272
|
+
except (ValueError, OSError):
|
273
|
+
pass
|
266
274
|
return value
|
267
275
|
|
268
276
|
return wrapper_debug
|
@@ -1232,11 +1240,12 @@ class Kernel(Settings):
|
|
1232
1240
|
|
1233
1241
|
async def aio_readline(loop):
|
1234
1242
|
while not self._shutdown:
|
1235
|
-
|
1243
|
+
if self.show_aio_prompt:
|
1244
|
+
print(">>", end="", flush=True)
|
1236
1245
|
|
1237
1246
|
line = await loop.run_in_executor(None, sys.stdin.readline)
|
1238
1247
|
line = line.strip()
|
1239
|
-
if line in ("quit", "shutdown", "restart"):
|
1248
|
+
if line in ("quit", "shutdown", "exit", "restart"):
|
1240
1249
|
self._quit = True
|
1241
1250
|
if line == "restart":
|
1242
1251
|
self._restart = True
|
@@ -2290,7 +2299,7 @@ class Kernel(Settings):
|
|
2290
2299
|
it will execute that in the console_parser. This works like a
|
2291
2300
|
terminal, where each letter of data can be sent to the console and
|
2292
2301
|
execution will occur at the carriage return.
|
2293
|
-
Additionally you can provide a '|' character that will separate commands on a single line
|
2302
|
+
Additionally you can provide a '|' character that will separate commands on a single line
|
2294
2303
|
@param data:
|
2295
2304
|
@return:
|
2296
2305
|
"""
|
@@ -2309,7 +2318,7 @@ class Kernel(Settings):
|
|
2309
2318
|
# No: we can split the command
|
2310
2319
|
quotations = data.count('"', 0, idx)
|
2311
2320
|
if quotations % 2 == 0:
|
2312
|
-
data = data[:idx].rstrip() + "\n" + data[idx+1:].lstrip()
|
2321
|
+
data = data[:idx].rstrip() + "\n" + data[idx + 1 :].lstrip()
|
2313
2322
|
start = idx + 1
|
2314
2323
|
self._console_buffer += data
|
2315
2324
|
data_out = None
|
@@ -2323,6 +2332,17 @@ class Kernel(Settings):
|
|
2323
2332
|
def _console_interface(self, command: str):
|
2324
2333
|
pass
|
2325
2334
|
|
2335
|
+
def has_command(self, command: str) -> bool:
|
2336
|
+
command = command.lower()
|
2337
|
+
input_type = None # Initial command context is None
|
2338
|
+
# Process command matches.
|
2339
|
+
for funct, name, regex in self.find("command", str(input_type), ".*"):
|
2340
|
+
# Find all commands with matching input_type.
|
2341
|
+
if not funct.regex and regex == command:
|
2342
|
+
# Exact match only.
|
2343
|
+
return True
|
2344
|
+
return False
|
2345
|
+
|
2326
2346
|
def _console_parse(self, text: str, channel: "Channel"):
|
2327
2347
|
"""
|
2328
2348
|
Takes single line console commands and executes them.
|
@@ -2515,9 +2535,7 @@ class Kernel(Settings):
|
|
2515
2535
|
if not helpstr:
|
2516
2536
|
helpstr = func.help
|
2517
2537
|
if helpstr:
|
2518
|
-
channel(
|
2519
|
-
"\t" + inspect.cleandoc(helpstr).replace("\n", " ")
|
2520
|
-
)
|
2538
|
+
channel("\t" + inspect.cleandoc(helpstr).replace("\n", " "))
|
2521
2539
|
channel("\n")
|
2522
2540
|
|
2523
2541
|
channel(f"\t{command_item} {' '.join(help_args)}")
|
@@ -2856,39 +2874,42 @@ class Kernel(Settings):
|
|
2856
2874
|
"Linux": "/usr/share/sounds/freedesktop/stereo/phone-incoming-call.oga",
|
2857
2875
|
}
|
2858
2876
|
|
2859
|
-
sys_snd = self.root.setting(
|
2877
|
+
sys_snd = self.root.setting(
|
2878
|
+
str, "beep_soundfile", system_sound.get(OS_NAME, "")
|
2879
|
+
)
|
2860
2880
|
use_default = not sys_snd or not os.path.exists(sys_snd)
|
2861
2881
|
|
2862
2882
|
def _play_windows():
|
2863
2883
|
try:
|
2864
2884
|
import winsound
|
2885
|
+
|
2865
2886
|
if use_default:
|
2866
2887
|
for x in range(5):
|
2867
2888
|
winsound.Beep(2000, 100)
|
2868
2889
|
else:
|
2869
2890
|
winsound.PlaySound(sys_snd, winsound.SND_FILENAME)
|
2870
2891
|
except Exception as e:
|
2871
|
-
channel
|
2892
|
+
channel("Encountered exception {e} during play")
|
2872
2893
|
pass
|
2873
2894
|
|
2874
2895
|
def _play_darwin():
|
2875
|
-
arg = system_sound[
|
2876
|
-
cmd = [
|
2896
|
+
arg = system_sound["Darwin"] if use_default else sys_snd
|
2897
|
+
cmd = ["afplay", arg]
|
2877
2898
|
try:
|
2878
2899
|
subprocess.run(cmd, shell=False)
|
2879
2900
|
except OSError as e:
|
2880
|
-
channel
|
2901
|
+
channel(f"Could not run {cmd[0]}: {e}")
|
2881
2902
|
|
2882
2903
|
def _play_linux():
|
2883
2904
|
try:
|
2884
2905
|
if not use_default:
|
2885
|
-
cmd = [
|
2906
|
+
cmd = ["play", sys_snd]
|
2886
2907
|
else:
|
2887
2908
|
print("\a")
|
2888
|
-
cmd = [
|
2909
|
+
cmd = ["say", "Ding"]
|
2889
2910
|
subprocess.run(cmd, shell=False)
|
2890
2911
|
except OSError as e:
|
2891
|
-
channel
|
2912
|
+
channel(f"Could not run {cmd[0]}: {e}")
|
2892
2913
|
|
2893
2914
|
players = {
|
2894
2915
|
"Windows": _play_windows,
|
@@ -3180,7 +3201,9 @@ class Kernel(Settings):
|
|
3180
3201
|
# ==========
|
3181
3202
|
@self.console_command(
|
3182
3203
|
"execute",
|
3183
|
-
help=_(
|
3204
|
+
help=_(
|
3205
|
+
"Loads a given file and executes all lines as commmands (as long as they don't start with a #)"
|
3206
|
+
),
|
3184
3207
|
)
|
3185
3208
|
def load_and_execute(channel, _, remainder=None, **kwargs):
|
3186
3209
|
if not remainder:
|
@@ -3346,13 +3369,17 @@ class Kernel(Settings):
|
|
3346
3369
|
channel(_("Infinite Loop Error."))
|
3347
3370
|
else:
|
3348
3371
|
if not force and channel_name not in self.channels:
|
3349
|
-
channel(
|
3372
|
+
channel(
|
3373
|
+
f"There is no channel named '{channel_name}', please use one of the existing channels or use the '-f' parameter to create one from scratch."
|
3374
|
+
)
|
3350
3375
|
channel(_("----------"))
|
3351
3376
|
channel(_("Channels Active:"))
|
3352
3377
|
for i, name in enumerate(self.channels):
|
3353
3378
|
channel_name = self.channels[name]
|
3354
3379
|
is_watched = (
|
3355
|
-
"* "
|
3380
|
+
"* "
|
3381
|
+
if self._console_channel in channel_name.watchers
|
3382
|
+
else " "
|
3356
3383
|
)
|
3357
3384
|
channel(f"{is_watched}{i + 1}: {name}")
|
3358
3385
|
return "channel", None
|