meerk40t 0.9.7010__py2.py3-none-any.whl → 0.9.7030__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 (77) hide show
  1. meerk40t/balormk/galvo_commands.py +1 -2
  2. meerk40t/core/cutcode/cutcode.py +1 -1
  3. meerk40t/core/cutplan.py +70 -2
  4. meerk40t/core/elements/branches.py +18 -4
  5. meerk40t/core/elements/element_treeops.py +43 -7
  6. meerk40t/core/elements/elements.py +49 -63
  7. meerk40t/core/elements/grid.py +8 -1
  8. meerk40t/core/elements/offset_clpr.py +4 -3
  9. meerk40t/core/elements/offset_mk.py +2 -1
  10. meerk40t/core/elements/shapes.py +379 -260
  11. meerk40t/core/elements/testcases.py +105 -0
  12. meerk40t/core/node/node.py +6 -3
  13. meerk40t/core/node/op_cut.py +9 -8
  14. meerk40t/core/node/op_dots.py +8 -8
  15. meerk40t/core/node/op_engrave.py +7 -7
  16. meerk40t/core/node/op_raster.py +8 -8
  17. meerk40t/core/planner.py +23 -0
  18. meerk40t/core/undos.py +1 -1
  19. meerk40t/core/wordlist.py +1 -0
  20. meerk40t/dxf/dxf_io.py +6 -0
  21. meerk40t/extra/encode_detect.py +8 -2
  22. meerk40t/extra/hershey.py +2 -3
  23. meerk40t/extra/inkscape.py +3 -5
  24. meerk40t/extra/mk_potrace.py +1959 -0
  25. meerk40t/extra/outerworld.py +2 -3
  26. meerk40t/extra/param_functions.py +2 -2
  27. meerk40t/extra/potrace.py +14 -10
  28. meerk40t/grbl/device.py +4 -1
  29. meerk40t/grbl/gui/grblcontroller.py +2 -2
  30. meerk40t/grbl/interpreter.py +1 -1
  31. meerk40t/gui/about.py +3 -5
  32. meerk40t/gui/basicops.py +3 -3
  33. meerk40t/gui/busy.py +75 -13
  34. meerk40t/gui/choicepropertypanel.py +365 -379
  35. meerk40t/gui/consolepanel.py +3 -3
  36. meerk40t/gui/gui_mixins.py +4 -1
  37. meerk40t/gui/hersheymanager.py +13 -3
  38. meerk40t/gui/laserpanel.py +12 -7
  39. meerk40t/gui/materialmanager.py +33 -6
  40. meerk40t/gui/plugin.py +9 -3
  41. meerk40t/gui/propertypanels/operationpropertymain.py +1 -1
  42. meerk40t/gui/ribbon.py +4 -1
  43. meerk40t/gui/scene/widget.py +1 -1
  44. meerk40t/gui/scenewidgets/rectselectwidget.py +19 -16
  45. meerk40t/gui/scenewidgets/selectionwidget.py +26 -20
  46. meerk40t/gui/simpleui.py +13 -8
  47. meerk40t/gui/simulation.py +22 -2
  48. meerk40t/gui/spoolerpanel.py +8 -11
  49. meerk40t/gui/themes.py +7 -1
  50. meerk40t/gui/tips.py +2 -3
  51. meerk40t/gui/toolwidgets/toolmeasure.py +4 -1
  52. meerk40t/gui/wxmeerk40t.py +32 -3
  53. meerk40t/gui/wxmmain.py +72 -6
  54. meerk40t/gui/wxmscene.py +95 -6
  55. meerk40t/gui/wxmtree.py +17 -11
  56. meerk40t/gui/wxutils.py +1 -1
  57. meerk40t/image/imagetools.py +21 -6
  58. meerk40t/kernel/kernel.py +31 -6
  59. meerk40t/kernel/settings.py +2 -0
  60. meerk40t/lihuiyu/device.py +9 -3
  61. meerk40t/main.py +22 -5
  62. meerk40t/network/console_server.py +52 -14
  63. meerk40t/network/web_server.py +15 -1
  64. meerk40t/ruida/device.py +5 -1
  65. meerk40t/ruida/gui/gui.py +6 -6
  66. meerk40t/ruida/gui/ruidaoperationproperties.py +1 -10
  67. meerk40t/ruida/rdjob.py +3 -3
  68. meerk40t/tools/geomstr.py +88 -0
  69. meerk40t/tools/polybool.py +2 -1
  70. meerk40t/tools/shxparser.py +92 -34
  71. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/METADATA +1 -1
  72. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/RECORD +77 -75
  73. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/WHEEL +1 -1
  74. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/LICENSE +0 -0
  75. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/entry_points.txt +0 -0
  76. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/top_level.txt +0 -0
  77. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/zip-safe +0 -0
@@ -3,7 +3,7 @@ from math import atan2, cos, sin, sqrt, tau
3
3
  import wx
4
4
 
5
5
  from meerk40t.core.units import Length
6
- from meerk40t.gui.wxutils import get_matrix_scale, get_gc_scale
6
+ from meerk40t.gui.wxutils import get_matrix_scale, get_gc_scale, dip_size
7
7
 
8
8
  from .toolpointlistbuilder import PointListTool
9
9
 
@@ -24,6 +24,8 @@ class MeasureTool(PointListTool):
24
24
  self.line_pen.SetColour(self.scene.colors.color_measure_line)
25
25
  self.line_pen.SetStyle(wx.PENSTYLE_DOT)
26
26
  self.line_pen.SetWidth(1000)
27
+ fact = dip_size(self.scene.pane, 100, 100)
28
+ self.font_size_factor = (fact[0] + fact[1]) / 100 * 0.5
27
29
 
28
30
  def create_node(self):
29
31
  # No need to create anything
@@ -43,6 +45,7 @@ class MeasureTool(PointListTool):
43
45
  font_size = 5000
44
46
  if font_size > 1e8:
45
47
  font_size = 5000
48
+ font_size *= self.font_size_factor
46
49
  # print ("Fontsize=%.3f, " % self.font_size)
47
50
  if font_size < 1.0:
48
51
  font_size = 1.0 # Mac does not allow values lower than 1.
@@ -24,6 +24,7 @@ from meerk40t.gui.wxmscene import SceneWindow
24
24
  from meerk40t.gui.wxutils import TextCtrl, wxButton, wxStaticText
25
25
  from meerk40t.kernel import CommandSyntaxError, Module, get_safe_path
26
26
  from meerk40t.kernel.kernel import Job
27
+ from meerk40t.core.units import Length
27
28
 
28
29
  from ..main import APPLICATION_NAME, APPLICATION_VERSION
29
30
  from ..tools.kerftest import KerfTool
@@ -500,7 +501,8 @@ class wxMeerK40t(wx.App, Module):
500
501
 
501
502
  def OnInit(self):
502
503
  self.name = f"MeerK40t-{wx.GetUserId()}"
503
- self.instance = wx.SingleInstanceChecker(self.name)
504
+ mkdir = self.context.kernel.os_information["OS_TEMPDIR"]
505
+ self.instance = wx.SingleInstanceChecker(self.name, path=mkdir)
504
506
  self.context.setting(bool, "single_instance_only", True)
505
507
  if self.context.kernel._was_restarted:
506
508
  return True
@@ -560,15 +562,17 @@ class wxMeerK40t(wx.App, Module):
560
562
  def MacOpenFile(self, filename):
561
563
  try:
562
564
  if self.context is not None:
563
- self.context.elements.load(os.path.realpath(filename))
565
+ channel = self.context.kernel.channel("console")
566
+ self.context.elements.load(os.path.realpath(filename), svg_ppi=self.context.elements.svg_ppi, channel=channel)
564
567
  except AttributeError:
565
568
  pass
566
569
 
567
570
  def MacOpenFiles(self, filenames):
568
571
  try:
569
572
  if self.context is not None:
573
+ channel = self.context.kernel.channel("console")
570
574
  for filename in filenames:
571
- self.context.elements.load(os.path.realpath(filename))
575
+ self.context.elements.load(os.path.realpath(filename), svg_ppi=self.context.elements.svg_ppi, channel=channel)
572
576
  except AttributeError:
573
577
  pass
574
578
 
@@ -832,6 +836,31 @@ class wxMeerK40t(wx.App, Module):
832
836
  dialog.cancel_it()
833
837
  dialog.Destroy()
834
838
 
839
+ @kernel.console_argument("info", type=str, help=_("Unit to translate"))
840
+ @kernel.console_command("unit", help=_("Translate units"))
841
+ def show_unit_info(command, channel, _, info=None, **kwargs):
842
+ if info is None:
843
+ channel(_("You need to provide a value to translate"))
844
+ device = kernel.root.device
845
+ try:
846
+ valuex = Length(info, relative_length=device.view.width, digits=4)
847
+ except ValueError:
848
+ channel(f"Invalid value: '{info}'")
849
+ return
850
+ channel(f"{info} translates to:")
851
+ channel(f"tat : {float(valuex):.4f}")
852
+ channel(f"mil : {valuex.mil}")
853
+ channel(f"um : {valuex.um}")
854
+ channel(f"nm : {valuex.nm}")
855
+ channel(f"mm : {valuex.mm}")
856
+ channel(f"cm : {valuex.cm}")
857
+ channel(f"Pixels : {valuex.pixels}")
858
+ channel(f"Point : {valuex.pt}")
859
+ channel(f"spx (screen) : {valuex.spx}")
860
+ channel(f"inch : {valuex.inches}")
861
+ channel(f"Device units : {float(valuex) / device.view.native_scale_x:.4f}")
862
+
863
+
835
864
  def module_open(self, *args, **kwargs):
836
865
  context = self.context
837
866
  kernel = context.kernel
meerk40t/gui/wxmmain.py CHANGED
@@ -2,6 +2,7 @@ import datetime
2
2
  import os
3
3
  import platform
4
4
  import sys
5
+ import threading
5
6
  from functools import partial
6
7
  from math import isinf
7
8
 
@@ -126,6 +127,42 @@ from .mwindow import MWindow
126
127
  _ = wx.GetTranslation
127
128
  MULTIPLE = "<Multiple files loaded>"
128
129
 
130
+ class GUIThread:
131
+ """
132
+ This will take from any thread a command to be executed and inserts it into the main thread
133
+ This prevents threading & lock issues exhibited by passing along commands
134
+ via ``consoleserver`` or ``webserver``
135
+ """
136
+ def __init__(self, context, *args, **kwargs):
137
+ self.context = context
138
+ self._execution_lock = threading.Lock()
139
+ self._execution_buffer = []
140
+ self._execution_timer = Job(
141
+ process=self.execute_command,
142
+ job_name="console-execute",
143
+ interval=0.1,
144
+ run_main=True,
145
+ )
146
+ self.context.kernel.register("gui/handover", self.process_command)
147
+
148
+ def execute_command(self):
149
+ cmd = ""
150
+ another = False
151
+ with self._execution_lock:
152
+ if self._execution_buffer:
153
+ cmd = self._execution_buffer[0]
154
+ self._execution_buffer.pop(0)
155
+ another = len(self._execution_buffer) > 0
156
+ if cmd:
157
+ self.context(cmd + "\n")
158
+ if another:
159
+ self.context.kernel.schedule(self._execution_timer)
160
+
161
+ def process_command(self, command):
162
+ with self._execution_lock:
163
+ self._execution_buffer.append(command)
164
+ self.context.kernel.schedule(self._execution_timer)
165
+
129
166
  class Autosaver:
130
167
  """
131
168
  Minimal autosave functionality.
@@ -137,7 +174,7 @@ class Autosaver:
137
174
  def __init__(self, context, *args, **kwargs):
138
175
  self.context = context
139
176
  self.needs_saving = False
140
- safe_dir = os.path.realpath(get_safe_path(self.context.kernel.name))
177
+ safe_dir = self.context.kernel.os_information["WORKDIR"]
141
178
  self.autosave_file = os.path.join(safe_dir, "_autosave.svg")
142
179
 
143
180
  choices = [
@@ -394,6 +431,7 @@ class MeerK40t(MWindow):
394
431
  self.tips_at_startup()
395
432
  self.parametric_info = None
396
433
  self.autosave = Autosaver(self.context)
434
+ self.handover = GUIThread(self.context)
397
435
  kernel = self.context.kernel
398
436
  if hasattr(kernel.args, "maximized") and kernel.args.maximized:
399
437
  self.Maximize()
@@ -538,10 +576,18 @@ class MeerK40t(MWindow):
538
576
  myPilImage = Image.new(
539
577
  "RGB", (myWxImage.GetWidth(), myWxImage.GetHeight())
540
578
  )
541
- myPilImage.frombytes(myWxImage.GetData())
579
+ try:
580
+ byte_data = bytes(myWxImage.GetData())
581
+ myPilImage.frombytes(byte_data)
582
+ except TypeError as e:
583
+ console = self.context.root.channel("console")
584
+ console(f"Error while pasting image: {e}")
585
+ return None
542
586
  return myPilImage
543
587
 
544
588
  image = imageToPil(WxBitmapToWxImage(bmp))
589
+ if image is None:
590
+ return
545
591
  dpi = DEFAULT_PPI
546
592
  matrix = Matrix(f"scale({UNITS_PER_PIXEL})")
547
593
  # _("Paste image")
@@ -2345,6 +2391,22 @@ class MeerK40t(MWindow):
2345
2391
  > 0,
2346
2392
  },
2347
2393
  )
2394
+ secondary_commands = [
2395
+ "element union",
2396
+ "element difference",
2397
+ "element xor",
2398
+ "element intersection",
2399
+ ]
2400
+ try:
2401
+ import pyclipr
2402
+ primary_commands = [
2403
+ "clipper union",
2404
+ "clipper difference",
2405
+ "clipper xor",
2406
+ "clipper intersection",
2407
+ ]
2408
+ except ImportError:
2409
+ primary_commands = list(secondary_commands)
2348
2410
  kernel.register(
2349
2411
  "button/geometry/Union",
2350
2412
  {
@@ -2352,7 +2414,8 @@ class MeerK40t(MWindow):
2352
2414
  "icon": icon_cag_union,
2353
2415
  "tip": _("Create a union of the selected elements"),
2354
2416
  "help": "cag",
2355
- "action": exec_in_undo_scope("Union", "element union\n"),
2417
+ "action": exec_in_undo_scope("Union", f"{primary_commands[0]}\n"),
2418
+ "action_right": exec_in_undo_scope("Union", f"{secondary_commands[0]}\n"),
2356
2419
  "size": bsize_small,
2357
2420
  "rule_enabled": lambda cond: len(
2358
2421
  list(kernel.elements.elems(emphasized=True))
@@ -2367,7 +2430,8 @@ class MeerK40t(MWindow):
2367
2430
  "icon": icon_cag_subtract,
2368
2431
  "tip": _("Create a difference of the selected elements"),
2369
2432
  "help": "cag",
2370
- "action": exec_in_undo_scope("Difference", "element difference\n"),
2433
+ "action": exec_in_undo_scope("Difference", f"{primary_commands[1]}\n"),
2434
+ "action_right": exec_in_undo_scope("Difference", f"{secondary_commands[1]}\n"),
2371
2435
  "size": bsize_small,
2372
2436
  "rule_enabled": lambda cond: len(
2373
2437
  list(kernel.elements.elems(emphasized=True))
@@ -2382,7 +2446,8 @@ class MeerK40t(MWindow):
2382
2446
  "icon": icon_cag_xor,
2383
2447
  "tip": _("Create a xor of the selected elements"),
2384
2448
  "help": "cag",
2385
- "action": exec_in_undo_scope("Xor", "element xor\n"),
2449
+ "action": exec_in_undo_scope("XOR", f"{primary_commands[2]}\n"),
2450
+ "action_right": exec_in_undo_scope("XOR", f"{secondary_commands[2]}\n"),
2386
2451
  "size": bsize_small,
2387
2452
  "rule_enabled": lambda cond: len(
2388
2453
  list(kernel.elements.elems(emphasized=True))
@@ -2397,7 +2462,8 @@ class MeerK40t(MWindow):
2397
2462
  "icon": icon_cag_common,
2398
2463
  "tip": _("Create a intersection of the selected elements"),
2399
2464
  "help": "cag",
2400
- "action": exec_in_undo_scope("Intersection", "element intersection\n"),
2465
+ "action": exec_in_undo_scope("Intersection", f"{primary_commands[3]}\n"),
2466
+ "action_right": exec_in_undo_scope("Intersection", f"{secondary_commands[3]}\n"),
2401
2467
  "size": bsize_small,
2402
2468
  "rule_enabled": lambda cond: len(
2403
2469
  list(kernel.elements.elems(emphasized=True))
meerk40t/gui/wxmscene.py CHANGED
@@ -156,13 +156,9 @@ class MeerK40tScenePanel(wx.Panel):
156
156
  self.context.setting(bool, "clear_magnets", True)
157
157
 
158
158
  # Save / Load the content of magnets
159
- from os.path import join, realpath
159
+ from os.path import join
160
160
 
161
- from meerk40t.kernel.functions import get_safe_path
162
-
163
- self._magnet_file = join(
164
- realpath(get_safe_path(self.context.kernel.name)), "magnets.cfg"
165
- )
161
+ self._magnet_file = join(self.context.kernel.os_information["WORKDIR"], "magnets.cfg")
166
162
  self.load_magnets()
167
163
  # Add a plugin routine to be called at the time of a full new start
168
164
  context.kernel.register(
@@ -872,6 +868,99 @@ class MeerK40tScenePanel(wx.Panel):
872
868
  else:
873
869
  channel(_("Target needs to be one of primary, secondary, circular"))
874
870
 
871
+ # Establishes magnet commands
872
+ @context.console_argument(
873
+ "action", type=str, help=_("Action: clear or set / delete with coordinate")
874
+ )
875
+ @context.console_argument(
876
+ "axis", type=str, help=_("Axis (X or Y)")
877
+ )
878
+ @context.console_argument("pos", type=str, help=_("Position for magnetline"))
879
+ @context.console_command(
880
+ "magnet",
881
+ help=_("magnet <action> <axis> <position>"),
882
+ input_type="scene",
883
+ )
884
+ def magnet_set(
885
+ command,
886
+ channel,
887
+ _,
888
+ action=None,
889
+ axis=None,
890
+ pos=None,
891
+ **kwarg,
892
+ ):
893
+ def info(opt_msg):
894
+ channel(
895
+ _("You need to provide the intended action:") + "\n" +
896
+ _("clear x - clear y : will clear all magnets on the given axis") + "\n" +
897
+ _("set x <pos> - set y <pos>: will set a magnet line on the given axis") + "\n" +
898
+ _("delete x <pos> - delete y <pos>: will delete the magnet line on the given axis")
899
+ )
900
+ if opt_msg:
901
+ channel(opt_msg)
902
+
903
+ if action is None or axis is None or axis.upper() not in ("X", "Y"):
904
+ info("")
905
+ return
906
+ action = action.lower()
907
+ axis = axis.upper()
908
+ value = None
909
+ if pos:
910
+ try:
911
+ rel_len = self.context.device.view.width if axis == "X" else self.context.device.view.height
912
+ value = float(Length(pos, relative_length=rel_len))
913
+ except ValueError:
914
+ info (f"Invalid length: {pos}")
915
+ return
916
+
917
+ if action != "clear" and value is None:
918
+ info(_("You need to provide a position"))
919
+ return
920
+
921
+ if action == "clear":
922
+ if axis == "X":
923
+ count = len(self.magnet_x)
924
+ self.magnet_x.clear()
925
+ else:
926
+ count = len(self.magnet_y)
927
+ self.magnet_y.clear()
928
+ self.save_magnets()
929
+ self.context.signal("refresh_scene", "Scene")
930
+ channel (_("Deleted {count} magnet lines on axis {axis}").format(axis=axis, count=count))
931
+ elif action == "set":
932
+ done = False
933
+ if axis == "X":
934
+ if not value in self.magnet_x:
935
+ done = True
936
+ self.magnet_x.append(value)
937
+ else:
938
+ if not value in self.magnet_y:
939
+ done = True
940
+ self.magnet_y.append(value)
941
+ self.save_magnets()
942
+ self.context.signal("refresh_scene", "Scene")
943
+ if done:
944
+ channel(_("Magnetline appended at {pos} on axis {axis}").format(pos=pos, axis=axis))
945
+ else:
946
+ channel(_("Magnetline was already present"))
947
+ elif action.startswith("del"):
948
+ done = False
949
+ if axis == "X":
950
+ if value in self.magnet_x:
951
+ done = True
952
+ self.magnet_x.remove(value)
953
+ else:
954
+ if value in self.magnet_y:
955
+ done = True
956
+ self.magnet_y.remove(value)
957
+ self.save_magnets()
958
+ self.context.signal("refresh_scene", "Scene")
959
+ if done:
960
+ channel(_("Magnetline removed at {pos} on axis {axis}").format(pos=pos, axis=axis))
961
+ else:
962
+ channel(_("Magnetline was not existing"))
963
+
875
964
  def toggle_ref_obj(self):
876
965
  for e in self.scene.context.elements.flat(types=elem_nodes, emphasized=True):
877
966
  if self.reference_object == e:
meerk40t/gui/wxmtree.py CHANGED
@@ -580,7 +580,7 @@ class ShadowTree:
580
580
  self.tree_images = None
581
581
  self.name = "Project"
582
582
  self._freeze = False
583
- testsize = dip_size(self, 20, 20)
583
+ testsize = dip_size(self.wxtree, 20, 20)
584
584
  self.iconsize = testsize[1]
585
585
  self.iconstates = {}
586
586
  self.last_call = 0
@@ -1430,17 +1430,23 @@ class ShadowTree:
1430
1430
  @param kwargs:
1431
1431
  @return:
1432
1432
  """
1433
- parent = node.parent
1434
- parent_item = parent._item
1435
- if parent_item is None:
1436
- # We are appending items in tree before registration.
1433
+ try:
1434
+ parent = node.parent
1435
+ parent_item = parent._item
1436
+ if parent_item is None:
1437
+ # We are appending items in tree before registration.
1438
+ return
1439
+ tree = self.wxtree
1440
+ if pos is None:
1441
+ node._item = tree.AppendItem(parent_item, self.name)
1442
+ else:
1443
+ node._item = tree.InsertItem(parent_item, pos, self.name)
1444
+ tree.SetItemData(node._item, node)
1445
+ except Exception as e:
1446
+ # Invalid tree?
1447
+ self.context.signal("rebuild_tree", "all")
1448
+ print (f"We encountered an error at node registration: {e}")
1437
1449
  return
1438
- tree = self.wxtree
1439
- if pos is None:
1440
- node._item = tree.AppendItem(parent_item, self.name)
1441
- else:
1442
- node._item = tree.InsertItem(parent_item, pos, self.name)
1443
- tree.SetItemData(node._item, node)
1444
1450
  self.update_decorations(node, False)
1445
1451
  wxcolor = self.wxtree.GetForegroundColour()
1446
1452
  attribute_to_try = "fill" if node.type == "elem text" else "stroke"
meerk40t/gui/wxutils.py CHANGED
@@ -1222,7 +1222,7 @@ class StaticBoxSizer(wx.StaticBoxSizer):
1222
1222
  if label is None:
1223
1223
  label = ""
1224
1224
  self.sbox = wx.StaticBox(parent, id, label=label)
1225
- self.sbox.SetMinSize(dip_size(self, 50, 50))
1225
+ self.sbox.SetMinSize(dip_size(self.sbox, 50, 50))
1226
1226
  super().__init__(self.sbox, orientation)
1227
1227
  self.parent = parent
1228
1228
 
@@ -43,7 +43,12 @@ def img_to_polygons(
43
43
  _, th2 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
44
44
 
45
45
  # Find contours in the binary image
46
- contours, hierarchies = cv2.findContours(th2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
46
+ try:
47
+ contours, hierarchies = cv2.findContours(th2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
48
+ except ValueError:
49
+ # Invalid data
50
+ return ([], [])
51
+
47
52
  # print(f"Found {len(contours)} contours and {len(hierarchies)} hierarchies")
48
53
  width, height = node_image.size
49
54
  minarea = int(minimal / 100.0 * width * height)
@@ -147,9 +152,14 @@ def do_innerwhite(
147
152
  _, thresh = cv2.threshold(gray, 250, 255, cv2.THRESH_BINARY)
148
153
 
149
154
  # Find contours
150
- contours, hierarchy = cv2.findContours(
151
- thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE
152
- )
155
+ try:
156
+ contours, hierarchy = cv2.findContours(
157
+ thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE
158
+ )
159
+ except ValueError:
160
+ # Invalid data
161
+ continue
162
+
153
163
  linecandidates = list()
154
164
 
155
165
  minarea = int(minimal / 100.0 * width * height)
@@ -402,7 +412,12 @@ def img_to_rectangles(
402
412
  _, th2 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
403
413
 
404
414
  # Find contours in the binary image
405
- contours, hierarchies = cv2.findContours(th2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
415
+ try:
416
+ contours, hierarchies = cv2.findContours(th2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
417
+ except ValueError:
418
+ # Invalid data
419
+ return ([], [])
420
+
406
421
  # print(f"Found {len(contours)} contours and {len(hierarchies)} hierarchies")
407
422
  width, height = node_image.size
408
423
  minarea = int(minimal / 100.0 * width * height)
@@ -1480,7 +1495,7 @@ def plugin(kernel, lifecycle=None):
1480
1495
  )
1481
1496
  @context.console_command(
1482
1497
  "linecut",
1483
- help=_("Cuts and image with a line"),
1498
+ help=_("Cuts an image with a line"),
1484
1499
  input_type="image",
1485
1500
  output_type="image",
1486
1501
  hidden=True,
meerk40t/kernel/kernel.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import functools
2
2
  import inspect
3
3
  import os
4
- import platform
5
4
  import re
6
5
  import subprocess
7
6
  import threading
@@ -181,9 +180,20 @@ class Kernel(Settings):
181
180
  # Arguments Objects
182
181
  self.args = None
183
182
 
183
+ self.os_information = self._get_environment()
184
+
184
185
  def __str__(self):
185
186
  return f"Kernel({self.name}, {self.profile}, {self.version})"
186
187
 
188
+ def _get_environment(self):
189
+ from platform import system
190
+ from tempfile import gettempdir
191
+ return {
192
+ "OS_NAME": system(),
193
+ "OS_TEMPDIR": os.path.realpath(gettempdir()),
194
+ "WORKDIR": os.path.realpath(get_safe_path(self.name, create=True)),
195
+ }
196
+
187
197
  def set_language(self, language, localedir="locale"):
188
198
  from . import set_language
189
199
 
@@ -244,15 +254,21 @@ class Kernel(Settings):
244
254
  kwargs_repr = [f"{k}={v}" for k, v in kwargs.items()]
245
255
  signature = ", ".join(args_repr + kwargs_repr)
246
256
  start = f"Calling {str(obj)}.{func.__name__}({signature})"
247
- debug_file.write(start + "\n")
257
+ try:
258
+ debug_file.write(start + "\n")
259
+ except (ValueError, OSError):
260
+ pass
248
261
  print(start)
249
262
  t = time.time()
250
263
  value = func(*args, **kwargs)
251
264
  t = time.time() - t
252
265
  finish = f" {func.__name__} returned {value} after {t * 1000}ms"
253
266
  print(finish)
254
- debug_file.write(finish + "\n")
255
- debug_file.flush()
267
+ try:
268
+ debug_file.write(finish + "\n")
269
+ debug_file.flush()
270
+ except (ValueError, OSError):
271
+ pass
256
272
  return value
257
273
 
258
274
  return wrapper_debug
@@ -1226,7 +1242,7 @@ class Kernel(Settings):
1226
1242
 
1227
1243
  line = await loop.run_in_executor(None, sys.stdin.readline)
1228
1244
  line = line.strip()
1229
- if line in ("quit", "shutdown", "restart"):
1245
+ if line in ("quit", "shutdown", "exit", "restart"):
1230
1246
  self._quit = True
1231
1247
  if line == "restart":
1232
1248
  self._restart = True
@@ -1906,6 +1922,8 @@ class Kernel(Settings):
1906
1922
  # Could be recurring job. Reset on reschedule.
1907
1923
  except AttributeError:
1908
1924
  pass
1925
+ if job.job_name is None:
1926
+ job.job_name = f"job_{id(job)}"
1909
1927
  self.jobs[job.job_name] = job
1910
1928
  return job
1911
1929
 
@@ -2667,6 +2685,9 @@ class Kernel(Settings):
2667
2685
  channel(_("----------"))
2668
2686
  channel(_("Scheduled Processes:"))
2669
2687
  for i, job_name in enumerate(self.jobs):
2688
+ if job_name is None:
2689
+ channel(_("Empty job definition..."))
2690
+ continue
2670
2691
  job = self.jobs[job_name]
2671
2692
  parts = list()
2672
2693
  parts.append(f"{i + 1}:")
@@ -2737,6 +2758,8 @@ class Kernel(Settings):
2737
2758
  channel(_("Timers:"))
2738
2759
  i = 0
2739
2760
  for job_name in self.jobs:
2761
+ if job_name is None:
2762
+ continue
2740
2763
  if not job_name.startswith("timer"):
2741
2764
  continue
2742
2765
  i += 1
@@ -2772,6 +2795,8 @@ class Kernel(Settings):
2772
2795
  skipped = False
2773
2796
  canceled = False
2774
2797
  for job_name in list(self.jobs):
2798
+ if job_name is None:
2799
+ continue
2775
2800
  if not job_name.startswith("timer"):
2776
2801
  continue
2777
2802
  timer_name = job_name[5:]
@@ -2830,7 +2855,7 @@ class Kernel(Settings):
2830
2855
 
2831
2856
  @self.console_command("beep", _("Perform beep"))
2832
2857
  def beep(channel, _, **kwargs):
2833
- OS_NAME = platform.system()
2858
+ OS_NAME = self.os_information["OS_NAME"]
2834
2859
  system_sound = {
2835
2860
  "Windows": r"c:\Windows\Media\Sounds\Alarm01.wav",
2836
2861
  "Darwin": "/System/Library/Sounds/Ping.aiff",
@@ -54,6 +54,8 @@ class Settings:
54
54
  FileNotFoundError,
55
55
  ):
56
56
  return
57
+ except UnicodeDecodeError as e:
58
+ print ("The config file contained unsupported characters, please share the file with the dev team")
57
59
  except (configparser.DuplicateOptionError, configparser.DuplicateSectionError) as e:
58
60
  print (f"We had a duplication error in the config, try to recover from {e}")
59
61
  for section in parser.sections():
@@ -6,14 +6,16 @@ the given device type.
6
6
  """
7
7
 
8
8
  from hashlib import md5
9
+ import platform
10
+
9
11
  import meerk40t.constants as mkconst
10
12
  from meerk40t.core.laserjob import LaserJob
11
13
  from meerk40t.core.spoolers import Spooler
12
14
  from meerk40t.core.view import View
13
15
  from meerk40t.kernel import CommandSyntaxError, Service, signal_listener
14
16
 
15
- from ..core.units import UNITS_PER_MIL, Length
16
- from ..device.mixins import Status
17
+ from meerk40t.core.units import UNITS_PER_MIL, Length
18
+ from meerk40t.device.mixins import Status
17
19
  from .controller import LihuiyuController
18
20
  from .driver import LihuiyuDriver
19
21
  from .tcp_connection import TCPOutput
@@ -514,7 +516,10 @@ class LihuiyuDevice(Service, Status):
514
516
  self.setting(str, "serial", None)
515
517
  self.setting(bool, "serial_enable", False)
516
518
 
517
- self.setting(int, "port", 1022)
519
+ # Linux prevents ports below 1024 to be used in an non-root context
520
+ def_port = 1022 if platform.system() == "Windows" else "1025"
521
+
522
+ self.setting(int, "port", def_port)
518
523
  self.setting(str, "address", "localhost")
519
524
 
520
525
  self.driver = LihuiyuDriver(self)
@@ -978,6 +983,7 @@ class LihuiyuDevice(Service, Status):
978
983
  server = self.open_as("module/TCPServer", server_name, port=port)
979
984
  if quit:
980
985
  self.close(server_name)
986
+ channel(_("TCP Server for lihuiyu has been closed"))
981
987
  return
982
988
  channel(_("TCP Server for lihuiyu on port: {port}").format(port=port))
983
989
  if verbose:
meerk40t/main.py CHANGED
@@ -11,7 +11,7 @@ import os.path
11
11
  import sys
12
12
 
13
13
  APPLICATION_NAME = "MeerK40t"
14
- APPLICATION_VERSION = "0.9.7010"
14
+ APPLICATION_VERSION = "0.9.7030"
15
15
 
16
16
  if not getattr(sys, "frozen", False):
17
17
  # If .git directory does not exist we are running from a package like pypi
@@ -151,6 +151,9 @@ parser.add_argument(
151
151
  action="store_true",
152
152
  help="start window maximized",
153
153
  )
154
+ parser.add_argument(
155
+ "-d", "--daemon", action="store_true", help="keep MeerK40t in background"
156
+ )
154
157
 
155
158
 
156
159
  def run():
@@ -220,12 +223,26 @@ def _exe(restarted, args):
220
223
  kernel.add_plugin(internal_plugins)
221
224
  kernel.add_plugin(external_plugins)
222
225
  auto = hasattr(kernel.args, "auto") and kernel.args.auto
226
+ command = hasattr(kernel.args, "execute") and kernel.args.execute
223
227
  console = hasattr(kernel.args, "console") and kernel.args.console
228
+ daemon = hasattr(kernel.args, "daemon") and kernel.args.daemon
229
+ server_mode = False
230
+ if command:
231
+ for c in command:
232
+ server_mode = server_mode or any(substring in c for substring in ("lhyserver", "grblserver", "ruidacontrol", "grblcontrol", "webserver"))
233
+ nogui = (
234
+ (hasattr(kernel.args, "gui_suppress") and kernel.args.gui_suppress) or
235
+ (hasattr(kernel.args, "no_gui") and kernel.args.no_gui)
236
+ )
224
237
  for idx, attrib in enumerate(("mktablength", "mktabpositions")):
225
238
  kernel.register(f"registered_mk_svg_parameters/tabs{idx}", attrib)
226
239
 
227
- if auto and not console:
228
- kernel(partial=True)
229
- else:
230
- kernel()
240
+ require_partial_mode = False
241
+ if (
242
+ (not console or nogui) and
243
+ (auto or daemon or server_mode)
244
+ ):
245
+ require_partial_mode = True
246
+ # print (f"Auto: {auto}, Command: {command}, Console: {console}, daemon: {daemon}, nogui:{nogui}, Server: {server_mode} -> {require_partial_mode}")
247
+ kernel(partial=require_partial_mode)
231
248
  return hasattr(kernel, "restart") and kernel.restart