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.
Files changed (79) hide show
  1. meerk40t/balormk/clone_loader.py +3 -2
  2. meerk40t/balormk/controller.py +28 -11
  3. meerk40t/balormk/cylindermod.py +1 -0
  4. meerk40t/balormk/device.py +13 -9
  5. meerk40t/balormk/driver.py +9 -2
  6. meerk40t/balormk/galvo_commands.py +3 -1
  7. meerk40t/balormk/gui/gui.py +6 -0
  8. meerk40t/balormk/livelightjob.py +338 -321
  9. meerk40t/balormk/mock_connection.py +4 -3
  10. meerk40t/balormk/usb_connection.py +11 -2
  11. meerk40t/camera/camera.py +19 -14
  12. meerk40t/camera/gui/camerapanel.py +6 -0
  13. meerk40t/core/cutplan.py +109 -51
  14. meerk40t/core/elements/element_treeops.py +435 -140
  15. meerk40t/core/elements/elements.py +100 -9
  16. meerk40t/core/elements/shapes.py +259 -39
  17. meerk40t/core/elements/tree_commands.py +10 -5
  18. meerk40t/core/node/elem_ellipse.py +18 -8
  19. meerk40t/core/node/elem_image.py +51 -19
  20. meerk40t/core/node/elem_line.py +18 -8
  21. meerk40t/core/node/elem_path.py +18 -8
  22. meerk40t/core/node/elem_point.py +10 -4
  23. meerk40t/core/node/elem_polyline.py +19 -11
  24. meerk40t/core/node/elem_rect.py +18 -8
  25. meerk40t/core/node/elem_text.py +11 -5
  26. meerk40t/core/node/filenode.py +2 -8
  27. meerk40t/core/node/groupnode.py +11 -11
  28. meerk40t/core/node/image_processed.py +11 -5
  29. meerk40t/core/node/image_raster.py +11 -5
  30. meerk40t/core/node/node.py +64 -16
  31. meerk40t/core/node/refnode.py +2 -1
  32. meerk40t/core/svg_io.py +91 -34
  33. meerk40t/device/dummydevice.py +7 -1
  34. meerk40t/extra/vtracer.py +222 -0
  35. meerk40t/grbl/device.py +81 -8
  36. meerk40t/gui/about.py +20 -0
  37. meerk40t/gui/devicepanel.py +20 -16
  38. meerk40t/gui/gui_mixins.py +4 -0
  39. meerk40t/gui/icons.py +330 -253
  40. meerk40t/gui/laserpanel.py +8 -3
  41. meerk40t/gui/laserrender.py +41 -21
  42. meerk40t/gui/magnetoptions.py +158 -65
  43. meerk40t/gui/materialtest.py +229 -39
  44. meerk40t/gui/navigationpanels.py +229 -24
  45. meerk40t/gui/propertypanels/hatchproperty.py +2 -0
  46. meerk40t/gui/propertypanels/imageproperty.py +160 -106
  47. meerk40t/gui/ribbon.py +6 -1
  48. meerk40t/gui/scenewidgets/gridwidget.py +29 -32
  49. meerk40t/gui/scenewidgets/rectselectwidget.py +190 -192
  50. meerk40t/gui/simulation.py +75 -77
  51. meerk40t/gui/statusbarwidgets/defaultoperations.py +84 -48
  52. meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
  53. meerk40t/gui/tips.py +15 -1
  54. meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
  55. meerk40t/gui/wxmmain.py +242 -114
  56. meerk40t/gui/wxmscene.py +107 -24
  57. meerk40t/gui/wxmtree.py +4 -2
  58. meerk40t/gui/wxutils.py +60 -15
  59. meerk40t/image/imagetools.py +129 -65
  60. meerk40t/internal_plugins.py +4 -0
  61. meerk40t/kernel/kernel.py +39 -18
  62. meerk40t/kernel/settings.py +28 -9
  63. meerk40t/lihuiyu/device.py +24 -12
  64. meerk40t/main.py +1 -1
  65. meerk40t/moshi/device.py +20 -6
  66. meerk40t/network/console_server.py +22 -6
  67. meerk40t/newly/device.py +10 -3
  68. meerk40t/newly/gui/gui.py +10 -0
  69. meerk40t/ruida/device.py +22 -2
  70. meerk40t/ruida/loader.py +6 -3
  71. meerk40t/tools/geomstr.py +193 -125
  72. meerk40t/tools/rasterplotter.py +179 -93
  73. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/METADATA +1 -1
  74. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/RECORD +79 -78
  75. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/LICENSE +0 -0
  76. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/WHEEL +0 -0
  77. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/entry_points.txt +0 -0
  78. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/top_level.txt +0 -0
  79. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/zip-safe +0 -0
@@ -19,11 +19,11 @@ from .dither import dither
19
19
 
20
20
 
21
21
  def img_to_polygons(
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)
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(th2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
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 = [{'x': i[0], 'y': i[1]} for i in region]
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['x'], pnt['y']
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( (geom, 100 * area / (width * height)) )
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
- node_image, # The image
387
- minimal, # Minimum area in percent (int) to consider
388
- maximal, # Maximum area in percent (int) to consider
389
- ignoreinner, # Ignore inner contours
390
- needs_invert=True # Does the image require inverting (we need white strcutures on a black background)
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(th2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
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(start=complex(box[0][0], box[0][1]), end=complex(box[1][0], box[1][1]))
446
- geom.line(start=complex(box[1][0], box[1][1]), end=complex(box[2][0], box[2][1]))
447
- geom.line(start=complex(box[2][0], box[2][1]), end=complex(box[3][0], box[3][1]))
448
- geom.line(start=complex(box[3][0], box[3][1]), end=complex(box[0][0], box[0][1]))
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( (geom, 100 * area / (width * height)) )
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": _("Set: Will scale down large images so they will fit on the laserbed."),
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 = min(threshold_min, threshold_max), max(
721
- threshold_max, threshold_min
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 #opaque_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("minimal", "m", type=float, help=_("minimal area (%)"), default=2)
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("threshold", "t", type=float, help=_("Threshold for simplification"), default=0.25)
1969
- @context.console_option("minimal", "m", type=float, help=_("minimal area (%)"), default=2)
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(e for e in elements.flat(emphasized=True) if e.type == "elem image")
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(f"Contouring: {minimal:.2f} <= area <= {maximal:.2f}, inverting: {'No' if dontinvert else 'Yes'}, threshold: {threshold:.2f}, inner: {'No' if ignoreinner else 'Yes'}")
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(node_image, minimal, maximal, ignoreinner, needs_invert=not dontinvert)
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(node_image, minimal, maximal, ignoreinner, needs_invert=not dontinvert)
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 (geom, c_info) in contours:
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(f"Done, created: {len(data_out)} contour elements >= {minimal}% (Total time: {t_total_overall:.2f} sec)")
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(e for e in context.elements.flat(emphasized=True) if e.type == "elem image")
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 = (None, "image", "elements"),
2145
- output_type = "elements",
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([(x, y, x + 1, y), (x, y, x - 1, y), (x, y, x, y + 1), (x, y, x, y - 1)])
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(e for e in context.elements.flat(emphasized=True) if e.type == "elem image")
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 (f"Found points: {points}, subpaths={len(multiple)}")
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(f"scale ({(bounds[2] - bounds[0]) / node_image.width}, {(bounds[3] - bounds[1]) / node_image.height})")
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 = 1000,
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 (dx * dx + dy * dy) >= 4: # Thats too wide by definition, new subpath
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: (_("Split image along white areas"), self.process_innerwhite),
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(f"raster_preprocessor/Method_{method}", (method, description, routine))
2380
+ self.kernel.register(
2381
+ f"raster_preprocessor/Method_{method}", (method, description, routine)
2382
+ )
2319
2383
 
2320
2384
 
2321
2385
  class RasterScripts:
@@ -76,6 +76,10 @@ def plugin(kernel, lifecycle):
76
76
 
77
77
  plugins.append(potrace.plugin)
78
78
 
79
+ from .extra import vtracer
80
+
81
+ plugins.append(vtracer.plugin)
82
+
79
83
  from .extra import inkscape
80
84
 
81
85
  plugins.append(inkscape.plugin)
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": system(),
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
- print(">>", end="", flush=True)
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(str, "beep_soundfile", system_sound.get(OS_NAME, ""))
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 ("Encountered exception {e} during play")
2892
+ channel("Encountered exception {e} during play")
2878
2893
  pass
2879
2894
 
2880
2895
  def _play_darwin():
2881
- arg = system_sound['Darwin'] if use_default else sys_snd
2882
- cmd = ['afplay', arg]
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 (f"Could not run {cmd[0]}: {e}")
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 = ['play', sys_snd]
2906
+ cmd = ["play", sys_snd]
2892
2907
  else:
2893
2908
  print("\a")
2894
- cmd = ['say', 'Ding']
2909
+ cmd = ["say", "Ding"]
2895
2910
  subprocess.run(cmd, shell=False)
2896
2911
  except OSError as e:
2897
- channel (f"Could not run {cmd[0]}: {e}")
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=_("Loads a given file and executes all lines as commmands (as long as they don't start with a #)"),
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(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.")
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
- "* " if self._console_channel in channel_name.watchers else " "
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
@@ -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(filename)
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 ("The config file contained unsupported characters, please share the file with the dev team")
59
- except (configparser.DuplicateOptionError, configparser.DuplicateSectionError) as e:
60
- print (f"We had a duplication error in the config, try to recover from {e}")
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
- config_section[option] = parser.get(section, option)
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 (configparser.DuplicateOptionError, configparser.DuplicateSectionError) as e:
95
- print (f"We had a duplication error in the config, try to recover from {e}")
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):