meerk40t 0.9.7030__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/cutplan.py +109 -51
- meerk40t/core/elements/element_treeops.py +435 -140
- meerk40t/core/elements/elements.py +100 -9
- meerk40t/core/elements/shapes.py +259 -39
- 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 +64 -16
- meerk40t/core/node/refnode.py +2 -1
- meerk40t/core/svg_io.py +91 -34
- meerk40t/device/dummydevice.py +7 -1
- meerk40t/extra/vtracer.py +222 -0
- meerk40t/grbl/device.py +81 -8
- meerk40t/gui/about.py +20 -0
- meerk40t/gui/devicepanel.py +20 -16
- meerk40t/gui/gui_mixins.py +4 -0
- 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/statusbarwidgets/defaultoperations.py +84 -48
- meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
- meerk40t/gui/tips.py +15 -1
- meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
- meerk40t/gui/wxmmain.py +242 -114
- meerk40t/gui/wxmscene.py +107 -24
- meerk40t/gui/wxmtree.py +4 -2
- meerk40t/gui/wxutils.py +60 -15
- meerk40t/image/imagetools.py +129 -65
- meerk40t/internal_plugins.py +4 -0
- meerk40t/kernel/kernel.py +39 -18
- meerk40t/kernel/settings.py +28 -9
- meerk40t/lihuiyu/device.py +24 -12
- meerk40t/main.py +1 -1
- 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/loader.py +6 -3
- meerk40t/tools/geomstr.py +193 -125
- meerk40t/tools/rasterplotter.py +179 -93
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/METADATA +1 -1
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/RECORD +79 -78
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/LICENSE +0 -0
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/WHEEL +0 -0
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/entry_points.txt +0 -0
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/top_level.txt +0 -0
- {meerk40t-0.9.7030.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:
|
@@ -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
|
}
|
@@ -1238,7 +1240,8 @@ class Kernel(Settings):
|
|
1238
1240
|
|
1239
1241
|
async def aio_readline(loop):
|
1240
1242
|
while not self._shutdown:
|
1241
|
-
|
1243
|
+
if self.show_aio_prompt:
|
1244
|
+
print(">>", end="", flush=True)
|
1242
1245
|
|
1243
1246
|
line = await loop.run_in_executor(None, sys.stdin.readline)
|
1244
1247
|
line = line.strip()
|
@@ -2296,7 +2299,7 @@ class Kernel(Settings):
|
|
2296
2299
|
it will execute that in the console_parser. This works like a
|
2297
2300
|
terminal, where each letter of data can be sent to the console and
|
2298
2301
|
execution will occur at the carriage return.
|
2299
|
-
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
|
2300
2303
|
@param data:
|
2301
2304
|
@return:
|
2302
2305
|
"""
|
@@ -2315,7 +2318,7 @@ class Kernel(Settings):
|
|
2315
2318
|
# No: we can split the command
|
2316
2319
|
quotations = data.count('"', 0, idx)
|
2317
2320
|
if quotations % 2 == 0:
|
2318
|
-
data = data[:idx].rstrip() + "\n" + data[idx+1:].lstrip()
|
2321
|
+
data = data[:idx].rstrip() + "\n" + data[idx + 1 :].lstrip()
|
2319
2322
|
start = idx + 1
|
2320
2323
|
self._console_buffer += data
|
2321
2324
|
data_out = None
|
@@ -2329,6 +2332,17 @@ class Kernel(Settings):
|
|
2329
2332
|
def _console_interface(self, command: str):
|
2330
2333
|
pass
|
2331
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
|
+
|
2332
2346
|
def _console_parse(self, text: str, channel: "Channel"):
|
2333
2347
|
"""
|
2334
2348
|
Takes single line console commands and executes them.
|
@@ -2521,9 +2535,7 @@ class Kernel(Settings):
|
|
2521
2535
|
if not helpstr:
|
2522
2536
|
helpstr = func.help
|
2523
2537
|
if helpstr:
|
2524
|
-
channel(
|
2525
|
-
"\t" + inspect.cleandoc(helpstr).replace("\n", " ")
|
2526
|
-
)
|
2538
|
+
channel("\t" + inspect.cleandoc(helpstr).replace("\n", " "))
|
2527
2539
|
channel("\n")
|
2528
2540
|
|
2529
2541
|
channel(f"\t{command_item} {' '.join(help_args)}")
|
@@ -2862,39 +2874,42 @@ class Kernel(Settings):
|
|
2862
2874
|
"Linux": "/usr/share/sounds/freedesktop/stereo/phone-incoming-call.oga",
|
2863
2875
|
}
|
2864
2876
|
|
2865
|
-
sys_snd = self.root.setting(
|
2877
|
+
sys_snd = self.root.setting(
|
2878
|
+
str, "beep_soundfile", system_sound.get(OS_NAME, "")
|
2879
|
+
)
|
2866
2880
|
use_default = not sys_snd or not os.path.exists(sys_snd)
|
2867
2881
|
|
2868
2882
|
def _play_windows():
|
2869
2883
|
try:
|
2870
2884
|
import winsound
|
2885
|
+
|
2871
2886
|
if use_default:
|
2872
2887
|
for x in range(5):
|
2873
2888
|
winsound.Beep(2000, 100)
|
2874
2889
|
else:
|
2875
2890
|
winsound.PlaySound(sys_snd, winsound.SND_FILENAME)
|
2876
2891
|
except Exception as e:
|
2877
|
-
channel
|
2892
|
+
channel("Encountered exception {e} during play")
|
2878
2893
|
pass
|
2879
2894
|
|
2880
2895
|
def _play_darwin():
|
2881
|
-
arg = system_sound[
|
2882
|
-
cmd = [
|
2896
|
+
arg = system_sound["Darwin"] if use_default else sys_snd
|
2897
|
+
cmd = ["afplay", arg]
|
2883
2898
|
try:
|
2884
2899
|
subprocess.run(cmd, shell=False)
|
2885
2900
|
except OSError as e:
|
2886
|
-
channel
|
2901
|
+
channel(f"Could not run {cmd[0]}: {e}")
|
2887
2902
|
|
2888
2903
|
def _play_linux():
|
2889
2904
|
try:
|
2890
2905
|
if not use_default:
|
2891
|
-
cmd = [
|
2906
|
+
cmd = ["play", sys_snd]
|
2892
2907
|
else:
|
2893
2908
|
print("\a")
|
2894
|
-
cmd = [
|
2909
|
+
cmd = ["say", "Ding"]
|
2895
2910
|
subprocess.run(cmd, shell=False)
|
2896
2911
|
except OSError as e:
|
2897
|
-
channel
|
2912
|
+
channel(f"Could not run {cmd[0]}: {e}")
|
2898
2913
|
|
2899
2914
|
players = {
|
2900
2915
|
"Windows": _play_windows,
|
@@ -3186,7 +3201,9 @@ class Kernel(Settings):
|
|
3186
3201
|
# ==========
|
3187
3202
|
@self.console_command(
|
3188
3203
|
"execute",
|
3189
|
-
help=_(
|
3204
|
+
help=_(
|
3205
|
+
"Loads a given file and executes all lines as commmands (as long as they don't start with a #)"
|
3206
|
+
),
|
3190
3207
|
)
|
3191
3208
|
def load_and_execute(channel, _, remainder=None, **kwargs):
|
3192
3209
|
if not remainder:
|
@@ -3352,13 +3369,17 @@ class Kernel(Settings):
|
|
3352
3369
|
channel(_("Infinite Loop Error."))
|
3353
3370
|
else:
|
3354
3371
|
if not force and channel_name not in self.channels:
|
3355
|
-
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
|
+
)
|
3356
3375
|
channel(_("----------"))
|
3357
3376
|
channel(_("Channels Active:"))
|
3358
3377
|
for i, name in enumerate(self.channels):
|
3359
3378
|
channel_name = self.channels[name]
|
3360
3379
|
is_watched = (
|
3361
|
-
"* "
|
3380
|
+
"* "
|
3381
|
+
if self._console_channel in channel_name.watchers
|
3382
|
+
else " "
|
3362
3383
|
)
|
3363
3384
|
channel(f"{is_watched}{i + 1}: {name}")
|
3364
3385
|
return "channel", None
|
meerk40t/kernel/settings.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import ast
|
2
|
-
import os
|
3
2
|
import configparser
|
3
|
+
import os
|
4
4
|
from pathlib import Path
|
5
5
|
from typing import Any, Dict, Generator, Optional, Union
|
6
6
|
|
@@ -22,7 +22,9 @@ class Settings:
|
|
22
22
|
|
23
23
|
def __init__(self, directory, filename, ignore_settings=False, create_backup=False):
|
24
24
|
if directory:
|
25
|
-
self._config_file = Path(get_safe_path(directory, create=True)).joinpath(
|
25
|
+
self._config_file = Path(get_safe_path(directory, create=True)).joinpath(
|
26
|
+
filename
|
27
|
+
)
|
26
28
|
else:
|
27
29
|
self._config_file = filename
|
28
30
|
self._config_dict = {}
|
@@ -51,13 +53,20 @@ class Settings:
|
|
51
53
|
PermissionError,
|
52
54
|
configparser.NoSectionError,
|
53
55
|
configparser.MissingSectionHeaderError,
|
56
|
+
configparser.ParsingError,
|
57
|
+
configparser.NoOptionError,
|
54
58
|
FileNotFoundError,
|
55
59
|
):
|
56
60
|
return
|
57
61
|
except UnicodeDecodeError as e:
|
58
|
-
print
|
59
|
-
|
60
|
-
|
62
|
+
print(
|
63
|
+
"The config file contained unsupported characters, please share the file with the dev team"
|
64
|
+
)
|
65
|
+
except (
|
66
|
+
configparser.DuplicateOptionError,
|
67
|
+
configparser.DuplicateSectionError,
|
68
|
+
) as e:
|
69
|
+
print(f"We had a duplication error in the config, try to recover from {e}")
|
61
70
|
for section in parser.sections():
|
62
71
|
for option in parser.options(section):
|
63
72
|
try:
|
@@ -65,7 +74,12 @@ class Settings:
|
|
65
74
|
except KeyError:
|
66
75
|
config_section = {}
|
67
76
|
self._config_dict[section] = config_section
|
68
|
-
|
77
|
+
try:
|
78
|
+
config_section[option] = parser.get(section, option)
|
79
|
+
except Exception as e:
|
80
|
+
print(
|
81
|
+
f"We had an error in the config, section {section}.{option}, try to recover from {e}"
|
82
|
+
)
|
69
83
|
|
70
84
|
def write_configuration(self, targetfile=None):
|
71
85
|
"""
|
@@ -91,8 +105,13 @@ class Settings:
|
|
91
105
|
except configparser.NoSectionError:
|
92
106
|
parser.add_section(section_key)
|
93
107
|
parser.set(section_key, key, value)
|
94
|
-
except (
|
95
|
-
|
108
|
+
except (
|
109
|
+
configparser.DuplicateOptionError,
|
110
|
+
configparser.DuplicateSectionError,
|
111
|
+
) as e:
|
112
|
+
print(
|
113
|
+
f"We had a duplication error in the config, try to recover from {e}"
|
114
|
+
)
|
96
115
|
|
97
116
|
if self.create_backup:
|
98
117
|
VERSIONS = 5
|
@@ -125,7 +144,7 @@ class Settings:
|
|
125
144
|
pass
|
126
145
|
with open(targetfile, "w", encoding="utf-8") as fp:
|
127
146
|
parser.write(fp)
|
128
|
-
except (PermissionError, FileNotFoundError):
|
147
|
+
except (PermissionError, FileNotFoundError, OSError, RuntimeError):
|
129
148
|
return
|
130
149
|
|
131
150
|
def literal_dict(self):
|