meerk40t 0.9.7030__py2.py3-none-any.whl → 0.9.7050__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 (85) hide show
  1. meerk40t/balormk/clone_loader.py +3 -2
  2. meerk40t/balormk/controller.py +38 -13
  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 +101 -78
  14. meerk40t/core/elements/element_treeops.py +435 -140
  15. meerk40t/core/elements/elements.py +100 -9
  16. meerk40t/core/elements/shapes.py +259 -72
  17. meerk40t/core/elements/tree_commands.py +10 -5
  18. meerk40t/core/node/blobnode.py +19 -4
  19. meerk40t/core/node/elem_ellipse.py +18 -8
  20. meerk40t/core/node/elem_image.py +51 -19
  21. meerk40t/core/node/elem_line.py +18 -8
  22. meerk40t/core/node/elem_path.py +18 -8
  23. meerk40t/core/node/elem_point.py +10 -4
  24. meerk40t/core/node/elem_polyline.py +19 -11
  25. meerk40t/core/node/elem_rect.py +18 -8
  26. meerk40t/core/node/elem_text.py +11 -5
  27. meerk40t/core/node/filenode.py +2 -8
  28. meerk40t/core/node/groupnode.py +11 -11
  29. meerk40t/core/node/image_processed.py +11 -5
  30. meerk40t/core/node/image_raster.py +11 -5
  31. meerk40t/core/node/node.py +64 -16
  32. meerk40t/core/node/refnode.py +2 -1
  33. meerk40t/core/planner.py +25 -11
  34. meerk40t/core/svg_io.py +91 -34
  35. meerk40t/device/dummydevice.py +7 -1
  36. meerk40t/extra/vtracer.py +222 -0
  37. meerk40t/grbl/device.py +96 -9
  38. meerk40t/grbl/driver.py +15 -5
  39. meerk40t/gui/about.py +20 -0
  40. meerk40t/gui/devicepanel.py +20 -16
  41. meerk40t/gui/gui_mixins.py +4 -0
  42. meerk40t/gui/icons.py +330 -253
  43. meerk40t/gui/laserpanel.py +27 -3
  44. meerk40t/gui/laserrender.py +41 -21
  45. meerk40t/gui/magnetoptions.py +158 -65
  46. meerk40t/gui/materialtest.py +569 -310
  47. meerk40t/gui/navigationpanels.py +229 -24
  48. meerk40t/gui/propertypanels/hatchproperty.py +2 -0
  49. meerk40t/gui/propertypanels/imageproperty.py +160 -106
  50. meerk40t/gui/propertypanels/wobbleproperty.py +6 -2
  51. meerk40t/gui/ribbon.py +6 -1
  52. meerk40t/gui/scenewidgets/gridwidget.py +29 -32
  53. meerk40t/gui/scenewidgets/rectselectwidget.py +190 -192
  54. meerk40t/gui/simulation.py +75 -77
  55. meerk40t/gui/spoolerpanel.py +27 -7
  56. meerk40t/gui/statusbarwidgets/defaultoperations.py +84 -48
  57. meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
  58. meerk40t/gui/tips.py +15 -1
  59. meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
  60. meerk40t/gui/wxmmain.py +242 -114
  61. meerk40t/gui/wxmscene.py +107 -24
  62. meerk40t/gui/wxmtree.py +4 -2
  63. meerk40t/gui/wxutils.py +286 -15
  64. meerk40t/image/imagetools.py +129 -65
  65. meerk40t/internal_plugins.py +4 -0
  66. meerk40t/kernel/kernel.py +67 -18
  67. meerk40t/kernel/settings.py +28 -9
  68. meerk40t/lihuiyu/device.py +24 -12
  69. meerk40t/main.py +14 -9
  70. meerk40t/moshi/device.py +20 -6
  71. meerk40t/network/console_server.py +22 -6
  72. meerk40t/newly/device.py +10 -3
  73. meerk40t/newly/gui/gui.py +10 -0
  74. meerk40t/ruida/device.py +22 -2
  75. meerk40t/ruida/loader.py +9 -4
  76. meerk40t/ruida/rdjob.py +48 -8
  77. meerk40t/tools/geomstr.py +240 -123
  78. meerk40t/tools/rasterplotter.py +185 -94
  79. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/METADATA +1 -1
  80. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/RECORD +85 -84
  81. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/LICENSE +0 -0
  82. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/WHEEL +0 -0
  83. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/entry_points.txt +0 -0
  84. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/top_level.txt +0 -0
  85. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/zip-safe +0 -0
@@ -48,6 +48,7 @@ class MockConnection:
48
48
 
49
49
  def write(self, index=0, packet=None):
50
50
  from meerk40t.balormk.controller import GetSerialNo
51
+
51
52
  packet_length = len(packet)
52
53
  assert packet_length == 0xC or packet_length == 0xC00
53
54
  if packet is not None:
@@ -88,6 +89,7 @@ class MockConnection:
88
89
 
89
90
  def _parse_single(self, packet):
90
91
  from meerk40t.balormk.controller import single_command_lookup
92
+
91
93
  b0 = packet[1] << 8 | packet[0]
92
94
  b1 = packet[3] << 8 | packet[2]
93
95
  b2 = packet[5] << 8 | packet[4]
@@ -104,12 +106,11 @@ class MockConnection:
104
106
 
105
107
  # Convert input to bytes early
106
108
  if isinstance(data, str):
107
- data = data.encode('ascii')
109
+ data = data.encode("ascii")
108
110
 
109
111
  # Create fixed-size response with padding
110
112
  self._implied_response = bytearray(8)
111
- self._implied_response[:len(data)] = data[:8]
112
-
113
+ self._implied_response[: len(data)] = data[:8]
113
114
 
114
115
  def read(self, index=0):
115
116
  if self._implied_response is None:
@@ -232,10 +232,19 @@ class USBConnection:
232
232
  except usb.core.NoBackendError as e:
233
233
  self.channel(str(e))
234
234
  from platform import system
235
+
235
236
  osname = system()
236
237
  if osname == "Windows":
237
- self.channel(_("Did you install the libusb driver via Zadig (https://zadig.akeo.ie/)?"))
238
- self.channel(_("Consult the wiki: https://github.com/meerk40t/meerk40t/wiki/Install%3A-Windows"))
238
+ self.channel(
239
+ _(
240
+ "Did you install the libusb driver via Zadig (https://zadig.akeo.ie/)?"
241
+ )
242
+ )
243
+ self.channel(
244
+ _(
245
+ "Consult the wiki: https://github.com/meerk40t/meerk40t/wiki/Install%3A-Windows"
246
+ )
247
+ )
239
248
  self.channel(_("PyUsb detected no backend LibUSB driver."))
240
249
  return -2
241
250
  except ConnectionRefusedError:
meerk40t/camera/camera.py CHANGED
@@ -189,32 +189,33 @@ class Camera(Service):
189
189
  pass
190
190
  return actual_width, actual_height
191
191
 
192
- def _get_capture(self, set_resolution = True):
192
+ def _get_capture(self, set_resolution=True):
193
193
  import platform
194
+
194
195
  # print (self.uri, type(self.uri).__name__)
195
196
  if platform.system() == "Windows":
196
197
  self.logger("Set DSHOW for Windows")
197
198
  cv2.CAP_DSHOW
198
- #sets the Windows cv2 backend to DSHOW (Direct Video Input Show)
199
+ # sets the Windows cv2 backend to DSHOW (Direct Video Input Show)
199
200
  cap = cv2.VideoCapture(self.uri)
200
201
  elif platform.system() == "Linux":
201
202
  self.logger("Set GSTREAMER for Linux")
202
- cv2.CAP_GSTREAMER # set the Linux cv2 backend to GTREAMER
203
- #cv2.CAP_V4L
203
+ cv2.CAP_GSTREAMER # set the Linux cv2 backend to GTREAMER
204
+ # cv2.CAP_V4L
204
205
  cap = cv2.VideoCapture(self.uri)
205
206
  else:
206
207
  self.logger("Try something for Darwin")
207
208
  cap = cv2.VideoCapture(self.uri)
208
209
  # For MAC please refer to link below for I/O
209
- cap.set(cv2.CAP_FFMPEG, cv2.CAP_AVFOUNDATION) # not sure!
210
- #please refer to reference link at bottom of page for more I/O
210
+ cap.set(cv2.CAP_FFMPEG, cv2.CAP_AVFOUNDATION) # not sure!
211
+ # please refer to reference link at bottom of page for more I/O
211
212
  if set_resolution:
212
- self.logger (f"Try to start camera with {self.width}x{self.height}")
213
+ self.logger(f"Try to start camera with {self.width}x{self.height}")
213
214
  cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
214
215
  cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
215
- self.logger (
216
- f"Capture: {str(self.capture)}\n" +
217
- f"Frame resolution set to: ({cap.get(cv2.CAP_PROP_FRAME_WIDTH)}x{cap.get(cv2.CAP_PROP_FRAME_HEIGHT)})"
216
+ self.logger(
217
+ f"Capture: {str(self.capture)}\n"
218
+ + f"Frame resolution set to: ({cap.get(cv2.CAP_PROP_FRAME_WIDTH)}x{cap.get(cv2.CAP_PROP_FRAME_HEIGHT)})"
218
219
  )
219
220
 
220
221
  return cap
@@ -297,14 +298,18 @@ class Camera(Service):
297
298
  actual_height = 0
298
299
  actual_width = 0
299
300
  msg = f"(Fail: {e})"
300
- self.logger(f"Tried {width}x{height} ({description}) - received {actual_width}x{actual_height} {msg}")
301
+ self.logger(
302
+ f"Tried {width}x{height} ({description}) - received {actual_width}x{actual_height} {msg}"
303
+ )
301
304
  if int(actual_width) == width and int(actual_height) == height:
302
305
  supported_resolutions.append((width, height, description))
303
-
304
- cap.release()
306
+ try:
307
+ # Might crash if the camera is not opened
308
+ cap.release()
309
+ except cv2.error:
310
+ pass
305
311
  return supported_resolutions
306
312
 
307
-
308
313
  def process_frame(self):
309
314
  frame = self._current_raw
310
315
  if (
@@ -947,35 +947,41 @@ class CameraInterface(MWindow):
947
947
  "label": _("Camera {index}").format(index=0),
948
948
  "action": camera_click(0),
949
949
  "signal": "camset0",
950
+ "multi_autoexec": True,
950
951
  },
951
952
  {
952
953
  "identifier": "cam1",
953
954
  "label": _("Camera {index}").format(index=1),
954
955
  "action": camera_click(1),
955
956
  "signal": "camset1",
957
+ "multi_autoexec": True,
956
958
  },
957
959
  {
958
960
  "identifier": "cam2",
959
961
  "label": _("Camera {index}").format(index=2),
960
962
  "action": camera_click(2),
961
963
  "signal": "camset2",
964
+ "multi_autoexec": True,
962
965
  },
963
966
  {
964
967
  "identifier": "cam3",
965
968
  "label": _("Camera {index}").format(index=3),
966
969
  "action": camera_click(3),
967
970
  "signal": "camset3",
971
+ "multi_autoexec": True,
968
972
  },
969
973
  {
970
974
  "identifier": "cam4",
971
975
  "label": _("Camera {index}").format(index=4),
972
976
  "action": camera_click(4),
973
977
  "signal": "camset4",
978
+ "multi_autoexec": True,
974
979
  },
975
980
  {
976
981
  "identifier": "id_cam",
977
982
  "label": _("Identify cameras"),
978
983
  "action": detect_usb_cameras,
984
+ "multi_autoexec": True,
979
985
  },
980
986
  ],
981
987
  },
meerk40t/core/cutplan.py CHANGED
@@ -15,15 +15,16 @@ CutPlan handles the various complicated algorithms to optimising the sequence of
15
15
  """
16
16
 
17
17
  from copy import copy
18
+ from functools import lru_cache
18
19
  from math import isinf
19
20
  from os import times
20
21
  from time import perf_counter, time
21
22
  from typing import Optional
22
- from functools import lru_cache
23
+
23
24
  import numpy as np
24
25
 
25
26
  from ..svgelements import Group, Matrix, Path, Polygon
26
- from ..tools.geomstr import Geomstr, stitch_geometries
27
+ from ..tools.geomstr import Geomstr, stitch_geometries, stitcheable_nodes
27
28
  from ..tools.pathtools import VectorMontonizer
28
29
  from .cutcode.cutcode import CutCode
29
30
  from .cutcode.cutgroup import CutGroup
@@ -31,7 +32,8 @@ from .cutcode.cutobject import CutObject
31
32
  from .cutcode.rastercut import RasterCut
32
33
  from .node.node import Node
33
34
  from .node.util_console import ConsoleOperation
34
- from .units import Length, UNITS_PER_MM
35
+ from .units import UNITS_PER_MM, Length
36
+
35
37
  """
36
38
  The time to compile does outweigh the benefit...
37
39
  try:
@@ -46,6 +48,7 @@ except Exception as e:
46
48
  return inner
47
49
  """
48
50
 
51
+
49
52
  class CutPlanningFailedError(Exception):
50
53
  pass
51
54
 
@@ -171,7 +174,9 @@ class CutPlan:
171
174
  for place in self.plan:
172
175
  if not hasattr(place, "type"):
173
176
  continue
174
- if place.type.startswith("place ") and (hasattr(place, "output") and place.output):
177
+ if place.type.startswith("place ") and (
178
+ hasattr(place, "output") and place.output
179
+ ):
175
180
  loops = 1
176
181
  if hasattr(place, "loops") and place.loops > 1:
177
182
  loops = place.loops
@@ -225,7 +230,9 @@ class CutPlan:
225
230
  coolop = ConsoleOperation(command=cmd)
226
231
  self.plan.append(coolop)
227
232
  else:
228
- self.channel("The current device does not support a coolant method")
233
+ self.channel(
234
+ "The current device does not support a coolant method"
235
+ )
229
236
  current_cool = cool
230
237
  # Is there already a coolant operation?
231
238
  if getattr(original_op, "type", "") == "util console":
@@ -244,45 +251,20 @@ class CutPlan:
244
251
  op_type = getattr(op, "type", "")
245
252
  if op_type.startswith("place "):
246
253
  continue
247
- if op_type == "op cut" and self.context.opt_stitching and self.context.do_optimization:
254
+ if (
255
+ op_type == "op cut"
256
+ and self.context.opt_stitching
257
+ and self.context.do_optimization
258
+ ):
248
259
  # This isn't a lossless operation: dotted/dashed lines will be treated as solid lines
249
260
  try:
250
- stitch_tolerance = float(Length(self.context.opt_stitch_tolerance))
261
+ stitch_tolerance = float(
262
+ Length(self.context.opt_stitch_tolerance)
263
+ )
251
264
  except ValueError:
252
265
  stitch_tolerance = 0
253
266
  default_stroke = None
254
267
  default_strokewidth = None
255
-
256
- def stitcheable_nodes(data, tolerance) -> list:
257
- out = []
258
- geoms = []
259
- # Store all geometries together with an indicator, to which node they belong
260
- for idx, node in enumerate(data):
261
- if not hasattr(node, "as_geometry"):
262
- continue
263
- for g1 in node.as_geometry().as_contiguous():
264
- geoms.append((idx, g1))
265
- for idx1, (nodeidx1, g1) in enumerate(geoms):
266
- for idx2 in range(idx1 + 1, len(geoms)):
267
- nodeidx2 = geoms[idx2][0]
268
- g2 = geoms[idx2][1]
269
- fp1 = g1.first_point
270
- fp2 = g2.first_point
271
- lp1 = g1.last_point
272
- lp2 = g2.last_point
273
- if (
274
- abs(lp1 - lp2) <= tolerance or
275
- abs(lp1 - fp2) <= tolerance or
276
- abs(fp1 - fp2) <= tolerance or
277
- abs(fp1 - lp2) <= tolerance
278
- ):
279
- if nodeidx1 not in out:
280
- out.append(nodeidx1)
281
- if nodeidx2 not in out:
282
- out.append(nodeidx2)
283
-
284
- return [data[idx] for idx in out]
285
-
286
268
  geoms = []
287
269
  to_be_deleted = []
288
270
  data = stitcheable_nodes(list(op.flat()), stitch_tolerance)
@@ -290,11 +272,13 @@ class CutPlan:
290
272
  if node is op:
291
273
  continue
292
274
  if hasattr(node, "as_geometry"):
293
- geom : Geomstr = node.as_geometry()
275
+ geom: Geomstr = node.as_geometry()
294
276
  geoms.extend(iter(geom.as_contiguous()))
295
277
  if default_stroke is None and hasattr(node, "stroke"):
296
278
  default_stroke = node.stroke
297
- if default_strokewidth is None and hasattr(node, "stroke_width"):
279
+ if default_strokewidth is None and hasattr(
280
+ node, "stroke_width"
281
+ ):
298
282
  default_strokewidth = node.stroke_width
299
283
  to_be_deleted.append(node)
300
284
  result = stitch_geometries(geoms, stitch_tolerance)
@@ -313,7 +297,9 @@ class CutPlan:
313
297
  # print (f"Paths at start of action: {len(list(op.flat()))}")
314
298
 
315
299
  self.plan.append(op)
316
- if (op_type.startswith("op") or op_type.startswith("util")) and hasattr(op, "preprocess"):
300
+ if (op_type.startswith("op") or op_type.startswith("util")) and hasattr(
301
+ op, "preprocess"
302
+ ):
317
303
  op.preprocess(self.context, placement, self)
318
304
  if op_type.startswith("op"):
319
305
  for node in op.flat():
@@ -523,11 +509,15 @@ class CutPlan:
523
509
  """
524
510
  if not isinstance(last_item, CutCode):
525
511
  # The last plan item is not cutcode, merge is only between cutobjects adding to cutcode.
526
- self.channel (f"last_item is no cutcode ({type(last_item).__name__}), can't merge")
512
+ self.channel(
513
+ f"last_item is no cutcode ({type(last_item).__name__}), can't merge"
514
+ )
527
515
  return False
528
516
  if not isinstance(current_item, CutObject):
529
517
  # The object to be merged is not a cutObject and cannot be added to Cutcode.
530
- self.channel (f"current_item is no cutcode ({type(current_item).__name__}), can't merge")
518
+ self.channel(
519
+ f"current_item is no cutcode ({type(current_item).__name__}), can't merge"
520
+ )
531
521
  return False
532
522
  last_op = last_item.original_op
533
523
  if last_op is None:
@@ -536,7 +526,9 @@ class CutPlan:
536
526
  if current_op is None:
537
527
  current_op = ""
538
528
  if last_op.startswith("util") or current_op.startswith("util"):
539
- self.channel (f"{last_op} / {current_op} - at least one is a util operation, can't merge")
529
+ self.channel(
530
+ f"{last_op} / {current_op} - at least one is a util operation, can't merge"
531
+ )
540
532
  return False
541
533
 
542
534
  if (
@@ -544,7 +536,9 @@ class CutPlan:
544
536
  and last_item.pass_index != current_item.pass_index
545
537
  ):
546
538
  # Do not merge if opt_merge_passes is off, and pass_index do not match
547
- self.channel (f"{last_item.pass_index} / {current_item.pass_index} - pass indices are different, can't merge")
539
+ self.channel(
540
+ f"{last_item.pass_index} / {current_item.pass_index} - pass indices are different, can't merge"
541
+ )
548
542
  return False
549
543
 
550
544
  if (
@@ -553,11 +547,15 @@ class CutPlan:
553
547
  ):
554
548
  # Do not merge if opt_merge_ops is off, and the original ops do not match
555
549
  # Same settings object implies same original operation
556
- self.channel (f"Settings do differ from {last_op} to {current_op} and merge ops= {context.opt_merge_ops}")
550
+ self.channel(
551
+ f"Settings do differ from {last_op} to {current_op} and merge ops= {context.opt_merge_ops}"
552
+ )
557
553
  return False
558
554
  if not context.opt_inner_first and last_item.original_op == "op cut":
559
555
  # Do not merge if opt_inner_first is off, and operation was originally a cut.
560
- self.channel (f"Inner first {context.opt_inner_first}, last op= {last_item.original_op} - Last op was a cut, can't merge")
556
+ self.channel(
557
+ f"Inner first {context.opt_inner_first}, last op= {last_item.original_op} - Last op was a cut, can't merge"
558
+ )
561
559
  return False
562
560
  return True # No reason these should not be merged.
563
561
 
@@ -773,7 +771,9 @@ class CutPlan:
773
771
  # We don't combine across plan boundaries
774
772
  if not isinstance(pitem, CutGroup):
775
773
  continue
776
- grouping, to_be_deleted, item_combined, total = process_plan_item(pitem, busy, total, plan_idx, l_plan)
774
+ grouping, to_be_deleted, item_combined, total = process_plan_item(
775
+ pitem, busy, total, plan_idx, l_plan
776
+ )
777
777
  combined += item_combined
778
778
  group_count += len(grouping)
779
779
 
@@ -1164,7 +1164,9 @@ def is_inside(inner, outer, tolerance=0, resolution=50):
1164
1164
  return False
1165
1165
  return True
1166
1166
 
1167
- def scanbeam_code_not_working_reliably(outer_cut, outer_path, inner_cut, inner_path):
1167
+ def scanbeam_code_not_working_reliably(
1168
+ outer_cut, outer_path, inner_cut, inner_path
1169
+ ):
1168
1170
  from ..tools.geomstr import Polygon as Gpoly
1169
1171
  from ..tools.geomstr import Scanbeam
1170
1172
 
@@ -1195,24 +1197,25 @@ def is_inside(inner, outer, tolerance=0, resolution=50):
1195
1197
 
1196
1198
  p1x, p1y = poly[0]
1197
1199
  old_sq_dist = sq_length(p1x - x, p1y - y)
1198
- for i in range(n+1):
1200
+ for i in range(n + 1):
1199
1201
  p2x, p2y = poly[i % n]
1200
1202
  new_sq_dist = sq_length(p2x - x, p2y - y)
1201
1203
  # We are approximating the edge to an extremely thin ellipse and see
1202
1204
  # whether our point is on that ellipse
1203
1205
  reldist = (
1204
- old_sq_dist + new_sq_dist +
1205
- 2.0 * np.sqrt(old_sq_dist * new_sq_dist) -
1206
- sq_length(p2x - p1x, p2y - p1y)
1206
+ old_sq_dist
1207
+ + new_sq_dist
1208
+ + 2.0 * np.sqrt(old_sq_dist * new_sq_dist)
1209
+ - sq_length(p2x - p1x, p2y - p1y)
1207
1210
  )
1208
1211
  if reldist < tolerance_square:
1209
1212
  return True
1210
1213
 
1211
- if y > min(p1y,p2y):
1212
- if y <= max(p1y,p2y):
1213
- if x <= max(p1x,p2x):
1214
+ if y > min(p1y, p2y):
1215
+ if y <= max(p1y, p2y):
1216
+ if x <= max(p1x, p2x):
1214
1217
  if p1y != p2y:
1215
- xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
1218
+ xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
1216
1219
  if p1x == p2x or x <= xints:
1217
1220
  inside = not inside
1218
1221
  p1x, p1y = p2x, p2y
@@ -1231,20 +1234,20 @@ def is_inside(inner, outer, tolerance=0, resolution=50):
1231
1234
 
1232
1235
  # Separating Axis Theorem (SAT) for Polygon Containment
1233
1236
 
1234
- # The Separating Axis Theorem (SAT) is a powerful technique for collision detection
1237
+ # The Separating Axis Theorem (SAT) is a powerful technique for collision detection
1235
1238
  # between convex polygons. It can also be adapted to determine polygon containment.
1236
1239
 
1237
1240
  # How SAT Works:
1238
1241
 
1239
1242
  # Generate Axes: For each edge of the outer polygon, create a perpendicular axis.
1240
1243
  # Project Polygons: Project both the inner and outer polygons onto each axis.
1241
- # Check Overlap: If the projections of the inner polygon are completely contained
1242
- # within the projections of the outer polygon on all axes,
1244
+ # Check Overlap: If the projections of the inner polygon are completely contained
1245
+ # within the projections of the outer polygon on all axes,
1243
1246
  # then the inner polygon is fully contained.
1244
-
1245
- # Convex Polygons: SAT is most efficient for convex polygons.
1247
+
1248
+ # Convex Polygons: SAT is most efficient for convex polygons.
1246
1249
  # For concave polygons, you might need to decompose them into convex sub-polygons.
1247
- # Computational Cost: SAT can be computationally expensive for large numbers of polygons.
1250
+ # Computational Cost: SAT can be computationally expensive for large numbers of polygons.
1248
1251
  # In such cases, spatial indexing can be used to reduce the number of pairwise comparisons.
1249
1252
  def project_polygon(polygon, axis):
1250
1253
  # Projects a polygon onto a given axis.
@@ -1280,7 +1283,6 @@ def is_inside(inner, outer, tolerance=0, resolution=50):
1280
1283
  """
1281
1284
 
1282
1285
  def raycasting_code_new(outer_polygon, inner_polygon):
1283
-
1284
1286
  def precompute_intersections(polygon):
1285
1287
  slopes = []
1286
1288
  intercepts = []
@@ -1309,9 +1311,13 @@ def is_inside(inner, outer, tolerance=0, resolution=50):
1309
1311
  slope = slopes[i]
1310
1312
  intercept = intercepts[i]
1311
1313
  p1, p2 = polygon[i], polygon[(i + 1) % len(polygon)]
1312
-
1314
+
1313
1315
  if np.isnan(slope): # Vertical line
1314
- if x == intercept and y >= min(p1[1], p2[1]) and y <= max(p1[1], p2[1]):
1316
+ if (
1317
+ x == intercept
1318
+ and y >= min(p1[1], p2[1])
1319
+ and y <= max(p1[1], p2[1])
1320
+ ):
1315
1321
  inside = not inside
1316
1322
  else:
1317
1323
  if y > min(p1[1], p2[1]):
@@ -1321,17 +1327,21 @@ def is_inside(inner, outer, tolerance=0, resolution=50):
1321
1327
  xints = (y - intercept) / slope
1322
1328
  if p1[0] == p2[0] or x <= xints:
1323
1329
  inside = not inside
1324
-
1330
+
1325
1331
  return inside
1326
-
1332
+
1327
1333
  def is_polygon_inside(outer_polygon, inner_polygon):
1328
1334
  slopes, intercepts, is_vertical = precompute_intersections(outer_polygon)
1329
1335
  for point in inner_polygon:
1330
- if not point_in_polygon(point[0], point[1], slopes, intercepts, is_vertical, outer_polygon):
1336
+ if not point_in_polygon(
1337
+ point[0], point[1], slopes, intercepts, is_vertical, outer_polygon
1338
+ ):
1331
1339
  return False
1332
1340
  return True
1333
1341
 
1334
- return is_polygon_inside(outer_polygon=outer_polygon, inner_polygon=inner_polygon)
1342
+ return is_polygon_inside(
1343
+ outer_polygon=outer_polygon, inner_polygon=inner_polygon
1344
+ )
1335
1345
 
1336
1346
  def shapely_code(outer_polygon, inner_polygon):
1337
1347
  from shapely.geometry import Polygon
@@ -1341,15 +1351,18 @@ def is_inside(inner, outer, tolerance=0, resolution=50):
1341
1351
  poly_b = Polygon(outer_polygon)
1342
1352
  # Check for containment
1343
1353
  return poly_a.within(poly_b)
1344
-
1354
+
1345
1355
  @lru_cache(maxsize=128)
1346
1356
  def get_polygon(path, resolution):
1347
1357
  geom = Geomstr.svg(path)
1348
1358
  polygon = np.array(
1349
- list((p.real, p.imag) for p in geom.as_equal_interpolated_points(distance = resolution))
1359
+ list(
1360
+ (p.real, p.imag)
1361
+ for p in geom.as_equal_interpolated_points(distance=resolution)
1362
+ )
1350
1363
  )
1351
1364
  return polygon
1352
-
1365
+
1353
1366
  """
1354
1367
  # Testroutines
1355
1368
  from time import perf_counter
@@ -1372,13 +1385,14 @@ def is_inside(inner, outer, tolerance=0, resolution=50):
1372
1385
  t5 = perf_counter()
1373
1386
  except ImportError:
1374
1387
  res4 = "Shapely missing"
1375
- t5 = t4
1388
+ t5 = t4
1376
1389
  print (f"Tolerance: {tolerance}, vm={res0} in {t1 - t0:.3f}s, sb={res1} in {t1 - t0:.3f}s, ray-old={res2} in {t2 - t1:.3f}s, ray-new={res3} in {t3 - t2:.3f}s, shapely={res4} in {t4 - t3:.3f}s")
1377
1390
  """
1378
1391
  inner_polygon = get_polygon(inner_path.d(), resolution)
1379
- outer_polygon = get_polygon(outer_path.d(), resolution)
1392
+ outer_polygon = get_polygon(outer_path.d(), resolution)
1380
1393
  try:
1381
1394
  import shapely
1395
+
1382
1396
  return shapely_code(outer_polygon, inner_polygon)
1383
1397
  except ImportError:
1384
1398
  return vm_code(outer, outer_polygon, inner, inner_polygon)
@@ -1387,6 +1401,7 @@ def is_inside(inner, outer, tolerance=0, resolution=50):
1387
1401
  # return vm_code(outer, outer_path, inner, inner_path)
1388
1402
  return
1389
1403
 
1404
+
1390
1405
  def reify_matrix(self):
1391
1406
  """Apply the matrix to the path and reset matrix."""
1392
1407
  self.element = abs(self.element)
@@ -1466,13 +1481,15 @@ def inner_first_ident(context: CutGroup, kernel=None, channel=None, tolerance=0)
1466
1481
  if kernel:
1467
1482
  busy = kernel.busyinfo
1468
1483
  _ = kernel.translation
1469
- min_res = min(kernel.device.view.native_scale_x, kernel.device.view.native_scale_y)
1484
+ min_res = min(
1485
+ kernel.device.view.native_scale_x, kernel.device.view.native_scale_y
1486
+ )
1470
1487
  # a 0.5 mm resolution is enough
1471
1488
  resolution = int(0.5 * UNITS_PER_MM / min_res)
1472
1489
  # print(f"Chosen resolution: {resolution} - minscale = {min_res}")
1473
1490
  else:
1474
1491
  busy = None
1475
- resolution = 10
1492
+ resolution = 10
1476
1493
  for outer in closed_groups:
1477
1494
  for inner in groups:
1478
1495
  current_pass += 1
@@ -1695,7 +1712,13 @@ def short_travel_cutcode(
1695
1712
  for idx, c in enumerate(unordered):
1696
1713
  if isinstance(c, CutGroup):
1697
1714
  c.skip = False
1698
- unordered[idx] = short_travel_cutcode(context=c, kernel=kernel, complete_path=False, grouped_inner=False, channel=channel)
1715
+ unordered[idx] = short_travel_cutcode(
1716
+ context=c,
1717
+ kernel=kernel,
1718
+ complete_path=False,
1719
+ grouped_inner=False,
1720
+ channel=channel,
1721
+ )
1699
1722
  # As these are reversed, we reverse again...
1700
1723
  ordered.extend(reversed(unordered))
1701
1724
  # print (f"And after extension {len(ordered)} items in list")