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
meerk40t/gui/wxmmain.py CHANGED
@@ -10,7 +10,7 @@ import wx
10
10
  from PIL import Image
11
11
  from wx import aui
12
12
 
13
- from meerk40t.core.exceptions import BadFileError
13
+ # from meerk40t.core.exceptions import BadFileError
14
14
  from meerk40t.gui.gui_mixins import FormatPainter, Warnings
15
15
  from meerk40t.gui.statusbarwidgets.defaultoperations import DefaultOperationWidget
16
16
  from meerk40t.gui.statusbarwidgets.infowidget import (
@@ -127,12 +127,14 @@ from .mwindow import MWindow
127
127
  _ = wx.GetTranslation
128
128
  MULTIPLE = "<Multiple files loaded>"
129
129
 
130
+
130
131
  class GUIThread:
131
132
  """
132
133
  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
+ This prevents threading & lock issues exhibited by passing along commands
134
135
  via ``consoleserver`` or ``webserver``
135
136
  """
137
+
136
138
  def __init__(self, context, *args, **kwargs):
137
139
  self.context = context
138
140
  self._execution_lock = threading.Lock()
@@ -161,7 +163,8 @@ class GUIThread:
161
163
  def process_command(self, command):
162
164
  with self._execution_lock:
163
165
  self._execution_buffer.append(command)
164
- self.context.kernel.schedule(self._execution_timer)
166
+ self.context.kernel.schedule(self._execution_timer)
167
+
165
168
 
166
169
  class Autosaver:
167
170
  """
@@ -288,10 +291,10 @@ def register_panel_dpi_bug(window, context):
288
291
  pane.hide_menu = True
289
292
  pane.pane_show = True
290
293
  msg = _(
291
- "Your system is using a very high userscale value: {scale}% ! " +
292
- "Unfortunately there is a bug in wxPython (the framework we are using) " +
293
- "that will cause unwanted upscaling of images in this configuration. You will recognize this by looking at very pixely icons.\n" +
294
- "As there is only so much we can do about it, we recommend lowering your userscale value to something below 150%."
294
+ "Your system is using a very high userscale value: {scale}% ! "
295
+ + "Unfortunately there is a bug in wxPython (the framework we are using) "
296
+ + "that will cause unwanted upscaling of images in this configuration. You will recognize this by looking at very pixely icons.\n"
297
+ + "As there is only so much we can do about it, we recommend lowering your userscale value to something below 150%."
295
298
  ).format(scale=context.root.user_scale)
296
299
  panel = wx.StaticText(window, wx.ID_ANY, label=msg)
297
300
  panel.SetBackgroundColour(wx.YELLOW)
@@ -301,6 +304,7 @@ def register_panel_dpi_bug(window, context):
301
304
  window.on_pane_create(pane)
302
305
  context.register("pane/dpi_bug", pane)
303
306
 
307
+
304
308
  class MeerK40t(MWindow):
305
309
  """MeerK40t main window"""
306
310
 
@@ -362,15 +366,27 @@ class MeerK40t(MWindow):
362
366
  self._mgr.SetManagedWindow(self)
363
367
  bg_col = self.context.themes.get("win_bg")
364
368
  fg_col = self.context.themes.get("win_fg")
365
- self._mgr.GetArtProvider().SetColour(aui.AUI_DOCKART_ACTIVE_CAPTION_COLOUR, bg_col)
366
- self._mgr.GetArtProvider().SetColour(aui.AUI_DOCKART_ACTIVE_CAPTION_GRADIENT_COLOUR, bg_col)
367
- self._mgr.GetArtProvider().SetColour(aui.AUI_DOCKART_ACTIVE_CAPTION_TEXT_COLOUR, fg_col)
369
+ self._mgr.GetArtProvider().SetColour(
370
+ aui.AUI_DOCKART_ACTIVE_CAPTION_COLOUR, bg_col
371
+ )
372
+ self._mgr.GetArtProvider().SetColour(
373
+ aui.AUI_DOCKART_ACTIVE_CAPTION_GRADIENT_COLOUR, bg_col
374
+ )
375
+ self._mgr.GetArtProvider().SetColour(
376
+ aui.AUI_DOCKART_ACTIVE_CAPTION_TEXT_COLOUR, fg_col
377
+ )
368
378
 
369
379
  bg_col = self.context.themes.get("inactive_bg")
370
380
  fg_col = self.context.themes.get("inactive_fg")
371
- self._mgr.GetArtProvider().SetColour(aui.AUI_DOCKART_INACTIVE_CAPTION_COLOUR, bg_col)
372
- self._mgr.GetArtProvider().SetColour(aui.AUI_DOCKART_INACTIVE_CAPTION_GRADIENT_COLOUR, bg_col)
373
- self._mgr.GetArtProvider().SetColour(aui.AUI_DOCKART_INACTIVE_CAPTION_TEXT_COLOUR, fg_col)
381
+ self._mgr.GetArtProvider().SetColour(
382
+ aui.AUI_DOCKART_INACTIVE_CAPTION_COLOUR, bg_col
383
+ )
384
+ self._mgr.GetArtProvider().SetColour(
385
+ aui.AUI_DOCKART_INACTIVE_CAPTION_GRADIENT_COLOUR, bg_col
386
+ )
387
+ self._mgr.GetArtProvider().SetColour(
388
+ aui.AUI_DOCKART_INACTIVE_CAPTION_TEXT_COLOUR, fg_col
389
+ )
374
390
 
375
391
  self.__set_panes()
376
392
  self.__set_commands()
@@ -954,7 +970,7 @@ class MeerK40t(MWindow):
954
970
  # print (f"Set Tooltips to {self.tooltips}")
955
971
  try:
956
972
  wx.ToolTip.Enable(self.tooltips)
957
- except Exception as e:
973
+ except Exception:
958
974
  pass
959
975
 
960
976
  # --- Listen to external events to toggle regmark visibility
@@ -1612,9 +1628,11 @@ class MeerK40t(MWindow):
1612
1628
  Addtionally it wraps the command in an undoscope statement to
1613
1629
  make the undo action easier to read and to contain.
1614
1630
  """
1631
+
1615
1632
  def handler(*args):
1616
1633
  with kernel.elements.undoscope(scope):
1617
1634
  kernel.elements(command)
1635
+
1618
1636
  return handler
1619
1637
 
1620
1638
  def exec_plain(command):
@@ -1623,8 +1641,10 @@ class MeerK40t(MWindow):
1623
1641
  This function serves as a command handler that takes a command string
1624
1642
  and forwards them to the kernel's elements execution method on a call.
1625
1643
  """
1644
+
1626
1645
  def handler(*args):
1627
1646
  kernel.elements(command)
1647
+
1628
1648
  return handler
1629
1649
 
1630
1650
  def run_job(*args):
@@ -1758,8 +1778,11 @@ class MeerK40t(MWindow):
1758
1778
 
1759
1779
  def contains_a_param():
1760
1780
  from meerk40t.core.elements.element_types import effect_nodes, elem_nodes
1781
+
1761
1782
  result = False
1762
- for e in kernel.elements.flat(types=elem_nodes + effect_nodes, emphasized=True):
1783
+ for e in kernel.elements.flat(
1784
+ types=elem_nodes + effect_nodes, emphasized=True
1785
+ ):
1763
1786
  if (
1764
1787
  hasattr(e, "functional_parameter")
1765
1788
  and e.functional_parameter is not None
@@ -2173,10 +2196,9 @@ class MeerK40t(MWindow):
2173
2196
  "action": exec_plain("clipboard cut\n"),
2174
2197
  "size": bsize_small,
2175
2198
  "identifier": "editcut",
2176
- "rule_enabled": lambda cond: len(
2177
- list(kernel.elements.elems(emphasized=True))
2178
- )
2179
- > 0,
2199
+ "rule_enabled": lambda cond: any(
2200
+ kernel.elements.elems(emphasized=True)
2201
+ ),
2180
2202
  },
2181
2203
  )
2182
2204
  kernel.register(
@@ -2189,10 +2211,9 @@ class MeerK40t(MWindow):
2189
2211
  "action": exec_plain("clipboard copy\n"),
2190
2212
  "size": bsize_small,
2191
2213
  "identifier": "editcopy",
2192
- "rule_enabled": lambda cond: len(
2193
- list(kernel.elements.elems(emphasized=True))
2194
- )
2195
- > 0,
2214
+ "rule_enabled": lambda cond: any(
2215
+ kernel.elements.elems(emphasized=True)
2216
+ ),
2196
2217
  },
2197
2218
  )
2198
2219
 
@@ -2213,9 +2234,7 @@ class MeerK40t(MWindow):
2213
2234
  "icon": icons8_paste,
2214
2235
  "tip": _("Paste elements from clipboard"),
2215
2236
  "help": "basicediting",
2216
- "action": exec_plain(
2217
- "clipboard paste -dx 3mm -dy 3mm\n"
2218
- ),
2237
+ "action": exec_plain("clipboard paste -dx 3mm -dy 3mm\n"),
2219
2238
  "size": bsize_small,
2220
2239
  "identifier": "editpaste",
2221
2240
  "rule_enabled": lambda cond: clipboard_filled(),
@@ -2244,7 +2263,10 @@ class MeerK40t(MWindow):
2244
2263
  "help": "duplicate",
2245
2264
  "label": _("Grid"),
2246
2265
  "action": lambda v: kernel.console("gui grid\n"),
2247
- "rule_enabled": lambda cond: len(list(kernel.elements.elems(emphasized=True))) > 0,
2266
+ "rule_enabled": lambda cond: len(
2267
+ list(kernel.elements.elems(emphasized=True))
2268
+ )
2269
+ > 0,
2248
2270
  },
2249
2271
  {
2250
2272
  "identifier": "copy_circ",
@@ -2254,7 +2276,10 @@ class MeerK40t(MWindow):
2254
2276
  "label": _("Circular"),
2255
2277
  "action": lambda v: kernel.console("gui circular\n"),
2256
2278
  "action_right": lambda v: kernel.console("gui grid\n"),
2257
- "rule_enabled": lambda cond: len(list(kernel.elements.elems(emphasized=True))) > 0,
2279
+ "rule_enabled": lambda cond: len(
2280
+ list(kernel.elements.elems(emphasized=True))
2281
+ )
2282
+ > 0,
2258
2283
  },
2259
2284
  {
2260
2285
  "identifier": "copy_circ",
@@ -2399,6 +2424,7 @@ class MeerK40t(MWindow):
2399
2424
  ]
2400
2425
  try:
2401
2426
  import pyclipr
2427
+
2402
2428
  primary_commands = [
2403
2429
  "clipper union",
2404
2430
  "clipper difference",
@@ -2415,7 +2441,9 @@ class MeerK40t(MWindow):
2415
2441
  "tip": _("Create a union of the selected elements"),
2416
2442
  "help": "cag",
2417
2443
  "action": exec_in_undo_scope("Union", f"{primary_commands[0]}\n"),
2418
- "action_right": exec_in_undo_scope("Union", f"{secondary_commands[0]}\n"),
2444
+ "action_right": exec_in_undo_scope(
2445
+ "Union", f"{secondary_commands[0]}\n"
2446
+ ),
2419
2447
  "size": bsize_small,
2420
2448
  "rule_enabled": lambda cond: len(
2421
2449
  list(kernel.elements.elems(emphasized=True))
@@ -2431,7 +2459,9 @@ class MeerK40t(MWindow):
2431
2459
  "tip": _("Create a difference of the selected elements"),
2432
2460
  "help": "cag",
2433
2461
  "action": exec_in_undo_scope("Difference", f"{primary_commands[1]}\n"),
2434
- "action_right": exec_in_undo_scope("Difference", f"{secondary_commands[1]}\n"),
2462
+ "action_right": exec_in_undo_scope(
2463
+ "Difference", f"{secondary_commands[1]}\n"
2464
+ ),
2435
2465
  "size": bsize_small,
2436
2466
  "rule_enabled": lambda cond: len(
2437
2467
  list(kernel.elements.elems(emphasized=True))
@@ -2462,8 +2492,12 @@ class MeerK40t(MWindow):
2462
2492
  "icon": icon_cag_common,
2463
2493
  "tip": _("Create a intersection of the selected elements"),
2464
2494
  "help": "cag",
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"),
2495
+ "action": exec_in_undo_scope(
2496
+ "Intersection", f"{primary_commands[3]}\n"
2497
+ ),
2498
+ "action_right": exec_in_undo_scope(
2499
+ "Intersection", f"{secondary_commands[3]}\n"
2500
+ ),
2467
2501
  "size": bsize_small,
2468
2502
  "rule_enabled": lambda cond: len(
2469
2503
  list(kernel.elements.elems(emphasized=True))
@@ -2480,11 +2514,9 @@ class MeerK40t(MWindow):
2480
2514
  my_parent = None
2481
2515
  for node in data:
2482
2516
  this_parent = None
2483
- if hasattr(node, "parent"):
2484
- if hasattr(node.parent, "type"):
2485
- if node.parent.type in ("group", "file"):
2486
- this_parent = node.parent
2487
- if my_parent is None:
2517
+ if hasattr(node, "parent") and hasattr(node.parent, "type"):
2518
+ if node.parent.type in ("group", "file"):
2519
+ this_parent = node.parent
2488
2520
  if this_parent is not None:
2489
2521
  my_parent = this_parent
2490
2522
  else:
@@ -2506,7 +2538,9 @@ class MeerK40t(MWindow):
2506
2538
  with kernel.elements.undoscope("Group"):
2507
2539
  for node in data:
2508
2540
  if group_node is None:
2509
- group_node = node.parent.add(type="group", label="Group", expanded=True)
2541
+ group_node = node.parent.add(
2542
+ type="group", label="Group", expanded=True
2543
+ )
2510
2544
  group_node.append_child(node)
2511
2545
  node.emphasized = True
2512
2546
  if group_node is not None:
@@ -2540,27 +2574,25 @@ class MeerK40t(MWindow):
2540
2574
  with kernel.elements.undoscope("Ungroup"):
2541
2575
  found_some = False
2542
2576
  for node in list(kernel.elements.elems(emphasized=True)):
2543
- if node is not None:
2544
- if node.type in ("group", "file"):
2545
- found_some = True
2546
- release_em(node)
2577
+ if node is not None and node.type in ("group", "file"):
2578
+ found_some = True
2579
+ release_em(node)
2547
2580
  if not found_some:
2548
2581
  # So let's see that we address the parents...
2549
2582
  for node in list(kernel.elements.elems(emphasized=True)):
2550
- if node is not None:
2551
- if hasattr(node, "parent"):
2552
- if hasattr(node.parent, "type"):
2553
- if node.parent.type in ("group", "file"):
2554
- release_em(node.parent)
2583
+ if (
2584
+ node is not None
2585
+ and hasattr(node, "parent")
2586
+ and hasattr(node.parent, "type")
2587
+ and node.parent.type in ("group", "file")
2588
+ ):
2589
+ release_em(node.parent)
2555
2590
 
2556
2591
  def part_of_group():
2557
- result = False
2558
2592
  for node in list(kernel.elements.elems(emphasized=True)):
2559
- if hasattr(node, "parent"):
2560
- if node.parent.type in ("group", "file"):
2561
- result = True
2562
- break
2563
- return result
2593
+ if hasattr(node, "parent") and node.parent.type in ("group", "file"):
2594
+ return True
2595
+ return False
2564
2596
 
2565
2597
  kernel.register(
2566
2598
  "button/group/Ungroup",
@@ -2574,21 +2606,22 @@ class MeerK40t(MWindow):
2574
2606
  "rule_enabled": lambda cond: part_of_group(),
2575
2607
  },
2576
2608
  )
2577
- choices= [
2609
+ choices = [
2578
2610
  {
2579
2611
  "attr": "align_first",
2580
2612
  "object": kernel.root,
2581
2613
  "default": True,
2582
2614
  "type": bool,
2583
2615
  "label": _("Alignment to first element"),
2584
- "tip":
2585
- _("When aligning several elements to each other, they will be aligned to the element...")
2586
- + "\n"
2587
- + _("Ticked: ...that was selected first")
2588
- + "\n"
2589
- + _("Unticked: ...that was selected last")
2590
- + "\n"
2591
- + _("(Requires a restart to take effect)"),
2616
+ "tip": _(
2617
+ "When aligning several elements to each other, they will be aligned to the element..."
2618
+ )
2619
+ + "\n"
2620
+ + _("Ticked: ...that was selected first")
2621
+ + "\n"
2622
+ + _("Unticked: ...that was selected last")
2623
+ + "\n"
2624
+ + _("(Requires a restart to take effect)"),
2592
2625
  "page": "Scene",
2593
2626
  "section": "Alignment",
2594
2627
  "signals": "restart",
@@ -2609,12 +2642,8 @@ class MeerK40t(MWindow):
2609
2642
  "Align selected elements at the leftmost position (right click: of the bed)"
2610
2643
  ),
2611
2644
  "help": "alignment",
2612
- "action": exec_plain(
2613
- f"align push {align_mode} individual left pop\n"
2614
- ),
2615
- "action_right": exec_plain(
2616
- "align push bed group left pop\n"
2617
- ),
2645
+ "action": exec_plain(f"align push {align_mode} individual left pop\n"),
2646
+ "action_right": exec_plain("align push bed group left pop\n"),
2618
2647
  "size": bsize_small,
2619
2648
  "rule_enabled": lambda cond: len(
2620
2649
  list(kernel.elements.elems(emphasized=True))
@@ -2701,13 +2730,10 @@ class MeerK40t(MWindow):
2701
2730
  "action": lambda v: kernel.console("window toggle AutoExec\n"),
2702
2731
  "size": STD_ICON_SIZE,
2703
2732
  },
2704
-
2705
- ]
2733
+ ],
2706
2734
  },
2707
2735
  )
2708
2736
 
2709
-
2710
-
2711
2737
  # Default Size for small buttons
2712
2738
  # buttonsize = STD_ICON_SIZE / 2
2713
2739
 
@@ -2775,7 +2801,9 @@ class MeerK40t(MWindow):
2775
2801
  ),
2776
2802
  "help": "alignment",
2777
2803
  "action": exec_in_undo_scope("Align", f"align {align_mode} centerh\n"),
2778
- "action_right": exec_in_undo_scope("Align", "align bed group centerh\n"),
2804
+ "action_right": exec_in_undo_scope(
2805
+ "Align", "align bed group centerh\n"
2806
+ ),
2779
2807
  "size": bsize_small,
2780
2808
  "rule_enabled": lambda cond: len(
2781
2809
  list(kernel.elements.elems(emphasized=True))
@@ -2793,7 +2821,9 @@ class MeerK40t(MWindow):
2793
2821
  ),
2794
2822
  "help": "alignment",
2795
2823
  "action": exec_in_undo_scope("Align", f"align {align_mode} centerv\n"),
2796
- "action_right": exec_in_undo_scope("Align", "align bed group centerv\n"),
2824
+ "action_right": exec_in_undo_scope(
2825
+ "Align", "align bed group centerv\n"
2826
+ ),
2797
2827
  "size": bsize_small,
2798
2828
  "rule_enabled": lambda cond: len(
2799
2829
  list(kernel.elements.elems(emphasized=True))
@@ -3080,7 +3110,12 @@ class MeerK40t(MWindow):
3080
3110
  version = "default"
3081
3111
  kernel = self.context.kernel
3082
3112
  for saver, save_name, sname in kernel.find("save"):
3083
- for description, extension, mimetype, _version in saver.save_types():
3113
+ for (
3114
+ description,
3115
+ extension,
3116
+ mimetype,
3117
+ _version,
3118
+ ) in saver.save_types():
3084
3119
  if pathname.lower().endswith(extension) and _version == version:
3085
3120
  clear_save = True
3086
3121
  break
@@ -3624,7 +3659,9 @@ class MeerK40t(MWindow):
3624
3659
  self.main_menubar.panereset = self.panes_menu.Append(
3625
3660
  wx.ID_ANY, _("Reset Panes"), ""
3626
3661
  )
3627
- self.main_menubar.panereset.SetHelp(_("Reset pane positions to a default value"))
3662
+ self.main_menubar.panereset.SetHelp(
3663
+ _("Reset pane positions to a default value")
3664
+ )
3628
3665
  self.Bind(
3629
3666
  wx.EVT_MENU,
3630
3667
  self.on_pane_reset,
@@ -3713,7 +3750,9 @@ class MeerK40t(MWindow):
3713
3750
 
3714
3751
  if suppress:
3715
3752
  continue
3716
- menudata.append([submenu_name, caption, name, window, suffix_path, helptext])
3753
+ menudata.append(
3754
+ [submenu_name, caption, name, window, suffix_path, helptext]
3755
+ )
3717
3756
  # Now that we have everything let's sort...
3718
3757
  menudata.sort(key=lambda row: row[0])
3719
3758
 
@@ -3961,17 +4000,23 @@ class MeerK40t(MWindow):
3961
4000
  local_choices = choices
3962
4001
  return handler
3963
4002
 
3964
- def _update_undo_redo_submenu(self):
4003
+ """
4004
+ Old code for separated undo / redo menu entries
4005
+
4006
+ def _update_undo_redo_submenu_splitted(self):
3965
4007
  def undo_jump(index):
3966
4008
  def handler(event):
3967
4009
  self.context(f"undo {index}\n")
4010
+
3968
4011
  return handler
3969
4012
 
3970
4013
  def redo_jump(index):
3971
4014
  def handler(event):
3972
4015
  self.context(f"undo {index + 1}\n")
4016
+
3973
4017
  return handler
3974
4018
 
4019
+ is_windows = platform.system() == "Windows"
3975
4020
  edit_menu = self.edit_menu
3976
4021
  label = _("Undo/Redo States")
3977
4022
  index = edit_menu.FindItem(label)
@@ -3985,20 +4030,86 @@ class MeerK40t(MWindow):
3985
4030
  undo.validate()
3986
4031
  item, redo_index = edit_menu.FindChildItem(wx.ID_REDO)
3987
4032
  submenu = wx.Menu()
3988
- menuitem : wx.MenuItem = submenu.Append(wx.ID_ANY, _("Undo"), "")
4033
+ menuitem = wx.MenuItem(submenu, wx.ID_ANY, _("Undo"), "")
4034
+ if is_windows:
4035
+ font = menuitem.GetFont()
4036
+ font.MakeBold()
4037
+ menuitem.SetFont(font)
4038
+ submenu.Append(menuitem)
3989
4039
  menuitem.Enable(False)
4040
+
3990
4041
  for idx, state in undo.states("undo"):
3991
4042
  # print (f"{idx}{'*' if idx == undo._undo_index else ' '}: {state.message}")
3992
- menuitem = submenu.Append(wx.ID_ANY, _(state.message))
4043
+ menuitem = wx.MenuItem(submenu, wx.ID_ANY, f"{idx}: {_(state.message)}")
4044
+ submenu.Append(menuitem)
3993
4045
  self.Bind(wx.EVT_MENU, undo_jump(idx), id=menuitem.GetId())
3994
4046
  if undo.has_redo():
3995
4047
  submenu.AppendSeparator()
3996
- menuitem : wx.MenuItem = submenu.Append(wx.ID_ANY, _("Redo"), "")
4048
+ menuitem = wx.MenuItem(submenu, wx.ID_ANY, _("Redo"), "")
4049
+ if is_windows:
4050
+ font = menuitem.GetFont()
4051
+ font.MakeBold()
4052
+ menuitem.SetFont(font)
4053
+ submenu.Append(menuitem)
3997
4054
  menuitem.Enable(False)
3998
4055
  for idx, state in undo.states("redo"):
3999
- menuitem = submenu.Append(wx.ID_ANY, _(state.message))
4056
+ menuitem = wx.MenuItem(submenu, wx.ID_ANY, f"{idx}: {_(state.message)}")
4057
+ submenu.Append(menuitem)
4000
4058
  self.Bind(wx.EVT_MENU, redo_jump(idx), id=menuitem.GetId())
4001
4059
  edit_menu.Insert(redo_index + 1, wx.ID_ANY, label, submenu)
4060
+ """
4061
+
4062
+ def _update_undo_redo_submenu(self):
4063
+ def redo_jump(index):
4064
+ def handler(event):
4065
+ self.context(f"undo {index + 1}\n")
4066
+
4067
+ return handler
4068
+
4069
+ is_windows = platform.system() == "Windows"
4070
+ edit_menu = self.edit_menu
4071
+ label = _("Editing History")
4072
+ index = edit_menu.FindItem(label)
4073
+ if index != -1:
4074
+ item = edit_menu.Remove(index)
4075
+ if item:
4076
+ item.Destroy()
4077
+ undo = self.context.elements.undo
4078
+ if not (undo.has_undo() or undo.has_redo()):
4079
+ return
4080
+ undo.validate()
4081
+ # We need the position of the menu to insert
4082
+ item, redo_index = edit_menu.FindChildItem(wx.ID_REDO)
4083
+ submenu = wx.Menu()
4084
+ menuitem = wx.MenuItem(submenu, wx.ID_ANY, _("Recall..."), "")
4085
+ # if is_windows:
4086
+ # font = menuitem.GetFont()
4087
+ # font.MakeBold()
4088
+ # menuitem.SetFont(font)
4089
+ submenu.Append(menuitem)
4090
+ menuitem.Enable(False)
4091
+ has_entries = False
4092
+ for idx in range(1, len(undo._undo_stack) - 1):
4093
+ state = undo._undo_stack[idx]
4094
+ # print (f"{idx}{'*' if idx == undo._undo_index else ' '}: {state.message}")
4095
+ trailer = ""
4096
+ has_entries = True
4097
+ if idx == undo._undo_index - 1:
4098
+ trailer = " (*)"
4099
+ menuitem = wx.MenuItem(
4100
+ submenu, wx.ID_ANY, f"{idx}: {_(state.message)}{trailer}"
4101
+ )
4102
+ if idx == undo._undo_index - 1 and is_windows:
4103
+ font = menuitem.GetFont()
4104
+ font.MakeBold()
4105
+ menuitem.SetFont(font)
4106
+
4107
+ submenu.Append(menuitem)
4108
+ self.Bind(wx.EVT_MENU, redo_jump(idx), id=menuitem.GetId())
4109
+ if has_entries:
4110
+ edit_menu.Insert(redo_index + 1, wx.ID_ANY, label, submenu)
4111
+ else:
4112
+ submenu.Destroy()
4002
4113
 
4003
4114
  def __set_edit_menu(self):
4004
4115
  """
@@ -4950,7 +5061,9 @@ class MeerK40t(MWindow):
4950
5061
  matrix = Matrix(f"scale({UNITS_PER_PIXEL}, {UNITS_PER_PIXEL})")
4951
5062
  # _("Export image")
4952
5063
  with elements.undoscope("Export image"):
4953
- node = elements.elem_branch.add(image=img, matrix=matrix, type="elem image")
5064
+ node = elements.elem_branch.add(
5065
+ image=img, matrix=matrix, type="elem image"
5066
+ )
4954
5067
  if elements.classify_new:
4955
5068
  elements.classify([node])
4956
5069
  self.context.signal("refresh_scene", "Scene")
@@ -5062,7 +5175,7 @@ class MeerK40t(MWindow):
5062
5175
  label = f"1&0 "
5063
5176
  else:
5064
5177
  label = f"{idx} "
5065
- recents.append( (fname, label) )
5178
+ recents.append((fname, label))
5066
5179
 
5067
5180
  for item in self.recent_file_menu.GetMenuItems():
5068
5181
  self.recent_file_menu.Remove(item)
@@ -5331,12 +5444,15 @@ class MeerK40t(MWindow):
5331
5444
 
5332
5445
  self.set_file_as_recently_used(pathname)
5333
5446
  self.set_working_file_name(pathname)
5334
- if old_note != self.context.elements.note and self.context.elements.auto_note:
5447
+ if (
5448
+ old_note != self.context.elements.note
5449
+ and self.context.elements.auto_note
5450
+ ):
5335
5451
  self.context("window open Notes\n") # open/not toggle.
5336
5452
  if (
5337
- execution and
5338
- self.context.elements.last_file_autoexec and
5339
- self.context.elements.last_file_autoexec_active
5453
+ execution
5454
+ and self.context.elements.last_file_autoexec
5455
+ and self.context.elements.last_file_autoexec_active
5340
5456
  ):
5341
5457
  flag = False
5342
5458
  if self.context.elements.auto_startup == 0:
@@ -5345,7 +5461,9 @@ class MeerK40t(MWindow):
5345
5461
  elif self.context.elements.auto_startup == 1:
5346
5462
  # ask
5347
5463
  flag = self.context.kernel.yesno(
5348
- _("This file contains an active autostart sequence!\nDo you wish to execute it?"),
5464
+ _(
5465
+ "This file contains an active autostart sequence!\nDo you wish to execute it?"
5466
+ ),
5349
5467
  option_yes=_("Execute"),
5350
5468
  option_no=_("Ignore"),
5351
5469
  caption=_("Startup-sequence found"),
@@ -5594,32 +5712,43 @@ class MeerK40t(MWindow):
5594
5712
  self.check_for_crash()
5595
5713
 
5596
5714
  def check_for_crash(self):
5597
-
5598
- safe_dir:str = os.path.realpath(get_safe_path(APPLICATION_NAME))
5599
- crash_indicator:str = os.path.join(safe_dir, "_crash")
5600
- recovery_file:str = self.autosave.autosave_file
5715
+ safe_dir: str = os.path.realpath(get_safe_path(APPLICATION_NAME))
5716
+ crash_indicator: str = os.path.join(safe_dir, "_crash")
5717
+ recovery_file: str = self.autosave.autosave_file
5601
5718
  # Is there a crash-indicator? The we look for the latest autosave - file
5602
5719
  if os.path.exists(crash_indicator) and os.path.exists(recovery_file):
5603
5720
  try:
5604
- filedate = datetime.datetime.fromtimestamp(os.path.getmtime(recovery_file))
5721
+ filedate = datetime.datetime.fromtimestamp(
5722
+ os.path.getmtime(recovery_file)
5723
+ )
5605
5724
  recovery_date = filedate.isoformat(" ")
5606
5725
  except (
5607
- PermissionError,
5608
- OSError,
5609
- RuntimeError,
5610
- FileExistsError,
5611
- FileNotFoundError,
5612
- ) as e:
5613
- # print (f"Error happened: {e}")
5614
- pass
5726
+ PermissionError,
5727
+ OSError,
5728
+ RuntimeError,
5729
+ FileExistsError,
5730
+ FileNotFoundError,
5731
+ ) as e:
5732
+ # print (f"Error happened: {e}")
5733
+ pass
5615
5734
  except Exception as e:
5616
5735
  recovery_date = "???"
5617
5736
 
5618
- message = _("Apparently MeerK40t did crash during the last session, we apologize for this invconvenience.") + "\n"
5619
- message += _("There is an autosave file ({filename}),\nthat was last saved at {filedate}.").format(filename=recovery_file, filedate=recovery_date) + "\n"
5737
+ message = (
5738
+ _(
5739
+ "Apparently MeerK40t did crash during the last session, we apologize for this invconvenience."
5740
+ )
5741
+ + "\n"
5742
+ )
5743
+ message += (
5744
+ _(
5745
+ "There is an autosave file ({filename}),\nthat was last saved at {filedate}."
5746
+ ).format(filename=recovery_file, filedate=recovery_date)
5747
+ + "\n"
5748
+ )
5620
5749
  message += _("Do you want to load this file?")
5621
5750
  caption = _("Crash-Recovery")
5622
- recover = self.context.kernel.yesno(
5751
+ recover = self.context.kernel.yesno(
5623
5752
  message,
5624
5753
  option_yes=_("Load work"),
5625
5754
  option_no=_("Start fresh"),
@@ -5629,16 +5758,15 @@ class MeerK40t(MWindow):
5629
5758
  try:
5630
5759
  os.remove(crash_indicator)
5631
5760
  except (
5632
- PermissionError,
5633
- OSError,
5634
- RuntimeError,
5635
- FileExistsError,
5636
- FileNotFoundError,
5637
- ) as e:
5638
- # print (f"Error happened: {e}")
5639
- pass
5761
+ PermissionError,
5762
+ OSError,
5763
+ RuntimeError,
5764
+ FileExistsError,
5765
+ FileNotFoundError,
5766
+ ) as e:
5767
+ # print (f"Error happened: {e}")
5768
+ pass
5640
5769
  if recover:
5641
5770
  # Load file
5642
5771
  self.context(f'load "{recovery_file}"\n')
5643
5772
  self.set_needs_save_status(True)
5644
-