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.
Files changed (98) 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/cutcode/cutcode.py +1 -1
  14. meerk40t/core/cutplan.py +169 -43
  15. meerk40t/core/elements/element_treeops.py +444 -147
  16. meerk40t/core/elements/elements.py +100 -9
  17. meerk40t/core/elements/grid.py +8 -1
  18. meerk40t/core/elements/offset_mk.py +2 -1
  19. meerk40t/core/elements/shapes.py +618 -279
  20. meerk40t/core/elements/tree_commands.py +10 -5
  21. meerk40t/core/node/elem_ellipse.py +18 -8
  22. meerk40t/core/node/elem_image.py +51 -19
  23. meerk40t/core/node/elem_line.py +18 -8
  24. meerk40t/core/node/elem_path.py +18 -8
  25. meerk40t/core/node/elem_point.py +10 -4
  26. meerk40t/core/node/elem_polyline.py +19 -11
  27. meerk40t/core/node/elem_rect.py +18 -8
  28. meerk40t/core/node/elem_text.py +11 -5
  29. meerk40t/core/node/filenode.py +2 -8
  30. meerk40t/core/node/groupnode.py +11 -11
  31. meerk40t/core/node/image_processed.py +11 -5
  32. meerk40t/core/node/image_raster.py +11 -5
  33. meerk40t/core/node/node.py +70 -19
  34. meerk40t/core/node/refnode.py +2 -1
  35. meerk40t/core/planner.py +23 -0
  36. meerk40t/core/svg_io.py +91 -34
  37. meerk40t/core/undos.py +1 -1
  38. meerk40t/core/wordlist.py +1 -0
  39. meerk40t/device/dummydevice.py +7 -1
  40. meerk40t/dxf/dxf_io.py +6 -0
  41. meerk40t/extra/mk_potrace.py +1959 -0
  42. meerk40t/extra/param_functions.py +1 -1
  43. meerk40t/extra/potrace.py +14 -10
  44. meerk40t/extra/vtracer.py +222 -0
  45. meerk40t/grbl/device.py +81 -8
  46. meerk40t/grbl/interpreter.py +1 -1
  47. meerk40t/gui/about.py +21 -3
  48. meerk40t/gui/basicops.py +3 -3
  49. meerk40t/gui/choicepropertypanel.py +1 -4
  50. meerk40t/gui/devicepanel.py +20 -16
  51. meerk40t/gui/gui_mixins.py +8 -1
  52. meerk40t/gui/icons.py +330 -253
  53. meerk40t/gui/laserpanel.py +8 -3
  54. meerk40t/gui/laserrender.py +41 -21
  55. meerk40t/gui/magnetoptions.py +158 -65
  56. meerk40t/gui/materialtest.py +229 -39
  57. meerk40t/gui/navigationpanels.py +229 -24
  58. meerk40t/gui/propertypanels/hatchproperty.py +2 -0
  59. meerk40t/gui/propertypanels/imageproperty.py +160 -106
  60. meerk40t/gui/ribbon.py +6 -1
  61. meerk40t/gui/scenewidgets/gridwidget.py +29 -32
  62. meerk40t/gui/scenewidgets/rectselectwidget.py +190 -192
  63. meerk40t/gui/simulation.py +75 -77
  64. meerk40t/gui/spoolerpanel.py +6 -9
  65. meerk40t/gui/statusbarwidgets/defaultoperations.py +84 -48
  66. meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
  67. meerk40t/gui/themes.py +7 -1
  68. meerk40t/gui/tips.py +15 -1
  69. meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
  70. meerk40t/gui/wxmeerk40t.py +26 -0
  71. meerk40t/gui/wxmmain.py +242 -114
  72. meerk40t/gui/wxmscene.py +180 -4
  73. meerk40t/gui/wxmtree.py +4 -2
  74. meerk40t/gui/wxutils.py +60 -15
  75. meerk40t/image/imagetools.py +130 -66
  76. meerk40t/internal_plugins.py +4 -0
  77. meerk40t/kernel/kernel.py +49 -22
  78. meerk40t/kernel/settings.py +29 -8
  79. meerk40t/lihuiyu/device.py +30 -12
  80. meerk40t/main.py +22 -5
  81. meerk40t/moshi/device.py +20 -6
  82. meerk40t/network/console_server.py +22 -6
  83. meerk40t/newly/device.py +10 -3
  84. meerk40t/newly/gui/gui.py +10 -0
  85. meerk40t/ruida/device.py +22 -2
  86. meerk40t/ruida/gui/gui.py +6 -6
  87. meerk40t/ruida/gui/ruidaoperationproperties.py +1 -10
  88. meerk40t/ruida/loader.py +6 -3
  89. meerk40t/ruida/rdjob.py +3 -3
  90. meerk40t/tools/geomstr.py +195 -39
  91. meerk40t/tools/rasterplotter.py +179 -93
  92. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/METADATA +1 -1
  93. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/RECORD +98 -96
  94. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/WHEEL +1 -1
  95. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/LICENSE +0 -0
  96. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/entry_points.txt +0 -0
  97. {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/top_level.txt +0 -0
  98. {meerk40t-0.9.7020.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:
@@ -1495,7 +1507,7 @@ def plugin(kernel, lifecycle=None):
1495
1507
  )
1496
1508
  @context.console_command(
1497
1509
  "linecut",
1498
- help=_("Cuts and image with a line"),
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("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
  }
@@ -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
- debug_file.write(start + "\n")
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
- debug_file.write(finish + "\n")
265
- debug_file.flush()
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
- print(">>", end="", flush=True)
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(str, "beep_soundfile", system_sound.get(OS_NAME, ""))
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 ("Encountered exception {e} during play")
2892
+ channel("Encountered exception {e} during play")
2872
2893
  pass
2873
2894
 
2874
2895
  def _play_darwin():
2875
- arg = system_sound['Darwin'] if use_default else sys_snd
2876
- cmd = ['afplay', arg]
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 (f"Could not run {cmd[0]}: {e}")
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 = ['play', sys_snd]
2906
+ cmd = ["play", sys_snd]
2886
2907
  else:
2887
2908
  print("\a")
2888
- cmd = ['say', 'Ding']
2909
+ cmd = ["say", "Ding"]
2889
2910
  subprocess.run(cmd, shell=False)
2890
2911
  except OSError as e:
2891
- channel (f"Could not run {cmd[0]}: {e}")
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=_("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
+ ),
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(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
+ )
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
- "* " if self._console_channel in channel_name.watchers else " "
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