meerk40t 0.9.7040__py2.py3-none-any.whl → 0.9.7051__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.
@@ -4,12 +4,14 @@ from meerk40t.gui.wxutils import ScrolledPanel, StaticBoxSizer
4
4
 
5
5
  from ...core.units import Length
6
6
  from ..wxutils import TextCtrl, set_ctrl_value, wxCheckBox, wxComboBox
7
- from .attributes import ColorPanel, IdPanel, AutoHidePanel
7
+ from .attributes import AutoHidePanel, ColorPanel, IdPanel
8
8
 
9
9
  _ = wx.GetTranslation
10
10
 
11
11
 
12
12
  class WobblePropertyPanel(ScrolledPanel):
13
+ name = _("Wobble")
14
+
13
15
  def __init__(self, *args, context=None, node=None, **kwds):
14
16
  # super().__init__(parent)
15
17
  kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
@@ -62,7 +64,9 @@ class WobblePropertyPanel(ScrolledPanel):
62
64
  main_sizer.Add(panel_id, 1, wx.EXPAND, 0)
63
65
  self.panels.append(panel_id)
64
66
 
65
- panel_hide = AutoHidePanel(self, id=wx.ID_ANY, context=self.context, node=self.node)
67
+ panel_hide = AutoHidePanel(
68
+ self, id=wx.ID_ANY, context=self.context, node=self.node
69
+ )
66
70
  main_sizer.Add(panel_hide, 1, wx.EXPAND, 0)
67
71
  self.panels.append(panel_hide)
68
72
 
@@ -103,6 +103,8 @@ class SpoolerPanel(wx.Panel):
103
103
  self.context.setting(int, "spooler_sash_position", 0)
104
104
  self.context.setting(bool, "spool_history_clear_on_start", False)
105
105
  self.context.setting(bool, "spool_ignore_helper_jobs", True)
106
+ self.context.setting(bool, "silent_mode", False)
107
+ self.context(f".silent {'on' if self.context.silent_mode else 'off'}\n")
106
108
 
107
109
  self.splitter = wx.SplitterWindow(self, id=wx.ID_ANY, style=wx.SP_LIVE_UPDATE)
108
110
  sty = wx.BORDER_SUNKEN
@@ -131,11 +133,20 @@ class SpoolerPanel(wx.Panel):
131
133
  )
132
134
  )
133
135
  self.button_stop.SetBitmapFocus(
134
- icons8_emergency_stop_button.GetBitmap(resize=0.5 * get_default_icon_size(self.context))
136
+ icons8_emergency_stop_button.GetBitmap(
137
+ resize=0.5 * get_default_icon_size(self.context)
138
+ )
135
139
  )
136
140
  self.button_stop.SetBackgroundColour(self.context.themes.get("stop_bg"))
137
141
  self.button_stop.SetForegroundColour(self.context.themes.get("stop_fg"))
138
142
  self.button_stop.SetFocusColour(self.context.themes.get("stop_fg_focus"))
143
+ self.check_silent = wx.CheckBox(self.win_top, wx.ID_ANY)
144
+ self.check_silent.SetValue(self.context.silent_mode)
145
+ self.check_silent.SetToolTip(
146
+ _(
147
+ "If checked, the spooler will not emit any sound signals while processing jobs"
148
+ )
149
+ )
139
150
 
140
151
  self.list_job_spool = wxListCtrl(
141
152
  self.win_top,
@@ -145,9 +156,7 @@ class SpoolerPanel(wx.Panel):
145
156
  list_name="list_spoolerjobs",
146
157
  )
147
158
 
148
- self.info_label = wxStaticText(
149
- self.win_bottom, wx.ID_ANY, _("Completed jobs:")
150
- )
159
+ self.info_label = wxStaticText(self.win_bottom, wx.ID_ANY, _("Completed jobs:"))
151
160
  self.button_clear_history = wxButton(
152
161
  self.win_bottom, wx.ID_ANY, _("Clear History")
153
162
  )
@@ -184,7 +193,7 @@ class SpoolerPanel(wx.Panel):
184
193
  self.Bind(
185
194
  wx.EVT_LIST_ITEM_RIGHT_CLICK, self.on_item_rightclick, self.list_job_spool
186
195
  )
187
- # end wxGlade
196
+ self.Bind(wx.EVT_CHECKBOX, self.on_check_silent, self.check_silent)
188
197
  self._last_invokation = 0
189
198
  self.dirty = False
190
199
  self.update_buffer_size = False
@@ -298,6 +307,7 @@ class SpoolerPanel(wx.Panel):
298
307
  sizer_combo_cmds.Add(self.combo_device, 1, wx.ALIGN_CENTER_VERTICAL, 0)
299
308
  sizer_combo_cmds.Add(self.button_pause, 0, wx.EXPAND, 0)
300
309
  sizer_combo_cmds.Add(self.button_stop, 0, wx.EXPAND, 0)
310
+ sizer_combo_cmds.Add(self.check_silent, 0, wx.ALIGN_CENTER_VERTICAL, 0)
301
311
 
302
312
  sizer_top.Add(sizer_combo_cmds, 0, wx.EXPAND, 0)
303
313
  sizer_top.Add(self.list_job_spool, 4, wx.EXPAND, 0)
@@ -318,6 +328,10 @@ class SpoolerPanel(wx.Panel):
318
328
  self.Layout()
319
329
  # end wxGlade
320
330
 
331
+ def on_check_silent(self, event):
332
+ self.context.silent_mode = self.check_silent.GetValue()
333
+ self.context(f".silent {'on' if self.context.silent_mode else 'off'}\n")
334
+
321
335
  def on_sash_changed(self, event):
322
336
  position = self.splitter.GetSashPosition()
323
337
  self.context.spooler_sash_position = position
@@ -760,7 +774,10 @@ class SpoolerPanel(wx.Panel):
760
774
  info_s = f"{spool_obj.steps_done}/{spool_obj.steps_total}"
761
775
  if hasattr(spooler, "driver"):
762
776
  if hasattr(spooler.driver, "get_internal_queue_status"):
763
- internal_current, internal_total = spooler.driver.get_internal_queue_status()
777
+ (
778
+ internal_current,
779
+ internal_total,
780
+ ) = spooler.driver.get_internal_queue_status()
764
781
  if internal_current != 0:
765
782
  info_s += f" ({internal_current}/{internal_total})"
766
783
  except AttributeError:
@@ -1113,7 +1130,10 @@ class SpoolerPanel(wx.Panel):
1113
1130
  info_s = f"{spool_obj.steps_done}/{spool_obj.steps_total}"
1114
1131
  if hasattr(spooler, "driver"):
1115
1132
  if hasattr(spooler.driver, "get_internal_queue_status"):
1116
- internal_current, internal_total = spooler.driver.get_internal_queue_status()
1133
+ (
1134
+ internal_current,
1135
+ internal_total,
1136
+ ) = spooler.driver.get_internal_queue_status()
1117
1137
  if internal_current != 0:
1118
1138
  info_s += f" ({internal_current}/{internal_total})"
1119
1139
  except AttributeError:
meerk40t/gui/wxutils.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Mixin functions for wxMeerk40t
3
3
  """
4
+
4
5
  import platform
5
6
  from typing import List
6
7
 
@@ -1250,6 +1251,9 @@ class StaticBoxSizer(wx.StaticBoxSizer):
1250
1251
  def SetLabel(self, label):
1251
1252
  self.sbox.SetLabel(label)
1252
1253
 
1254
+ def GetLabel(self):
1255
+ return self.sbox.GetLabel()
1256
+
1253
1257
  def Refresh(self, *args):
1254
1258
  self.sbox.Refresh(*args)
1255
1259
 
@@ -1582,6 +1586,7 @@ class wxRadioBox(StaticBoxSizer):
1582
1586
  def Show(self, flag):
1583
1587
  for ctrl in self._children + self._labels:
1584
1588
  ctrl.Show(flag)
1589
+ super().Show(flag)
1585
1590
 
1586
1591
  def Bind(self, event_type, routine):
1587
1592
  self.parent.Bind(event_type, routine, self)
@@ -1622,6 +1627,227 @@ class wxListBox(wx.ListBox):
1622
1627
  set_color_according_to_theme(self, "list_bg", "list_fg")
1623
1628
 
1624
1629
 
1630
+ class wxCheckListBox(StaticBoxSizer):
1631
+ """
1632
+ This class recreates the functionality of a wx.CheckListBox, as this class has issues to properly refresh in nested sizers
1633
+
1634
+ Known Limitations:
1635
+ - This custom implementation may not fully replicate all native wx.CheckListBox behaviors.
1636
+ - Keyboard navigation (e.g., arrow keys, space/enter to toggle) may not work as expected.
1637
+ - Accessibility features such as screen reader support may be limited or unavailable.
1638
+ - If your application requires full accessibility or native keyboard handling, consider using the native wx.CheckListBox where possible.
1639
+ """
1640
+
1641
+ def __init__(
1642
+ self,
1643
+ parent=None,
1644
+ id=None,
1645
+ label=None,
1646
+ choices=None,
1647
+ majorDimension=0,
1648
+ style=0,
1649
+ *args,
1650
+ **kwargs,
1651
+ ):
1652
+ self.parent = parent
1653
+ self.choices = choices
1654
+ self._children = []
1655
+ self._tool_tip = None
1656
+ self._help = None
1657
+ super().__init__(
1658
+ parent=parent, id=wx.ID_ANY, label=label, orientation=wx.VERTICAL
1659
+ )
1660
+ self.majorDimension = majorDimension
1661
+ self.style = style
1662
+ self._build_controls()
1663
+
1664
+ def _build_controls(self):
1665
+ """
1666
+ Build the controls for the CheckListBox.
1667
+ This method is called during initialization to create the checkboxes.
1668
+ """
1669
+ if self.choices is None:
1670
+ self.choices = []
1671
+ if self.majorDimension == 0 or self.style == wx.RA_SPECIFY_ROWS:
1672
+ self.majorDimension = 1000
1673
+ container = None
1674
+ for idx, c in enumerate(self.choices):
1675
+ if idx % self.majorDimension == 0:
1676
+ container = wx.BoxSizer(wx.HORIZONTAL)
1677
+ self.Add(container, 0, wx.EXPAND, 0)
1678
+ check_option = wx.CheckBox(self.parent, wx.ID_ANY, label=c)
1679
+ container.Add(check_option, 1, wx.ALIGN_CENTER_VERTICAL, 0)
1680
+ self._children.append(check_option)
1681
+
1682
+ if platform.system() == "Linux":
1683
+
1684
+ def on_mouse_over_check(ctrl):
1685
+ def mouse(event=None):
1686
+ ctrl.SetToolTip(self._tool_tip)
1687
+ event.Skip()
1688
+
1689
+ return mouse
1690
+
1691
+ for ctrl in self._children:
1692
+ ctrl.Bind(wx.EVT_MOTION, on_mouse_over_check(ctrl))
1693
+
1694
+ for ctrl in self._children:
1695
+ ctrl.Bind(wx.EVT_CHECKBOX, self.on_check)
1696
+ ctrl.Bind(wx.EVT_RIGHT_DOWN, self.on_right_click)
1697
+
1698
+ for ctrl in self._children:
1699
+ set_color_according_to_theme(ctrl, "text_bg", "text_fg")
1700
+
1701
+ @property
1702
+ def Children(self):
1703
+ return self._children
1704
+
1705
+ def GetParent(self):
1706
+ return self.parent
1707
+
1708
+ def SetToolTip(self, tooltip):
1709
+ self._tool_tip = tooltip
1710
+ for ctrl in self._children:
1711
+ ctrl.SetToolTip(self._tool_tip)
1712
+
1713
+ def Disable(self):
1714
+ self.Enable(False)
1715
+
1716
+ def EnableItem(self, n, flag):
1717
+ if 0 <= n < len(self._children):
1718
+ self._children[n].Enable(flag)
1719
+
1720
+ def Enable(self, flag):
1721
+ for ctrl in self._children:
1722
+ ctrl.Enable(flag)
1723
+
1724
+ def Hide(self):
1725
+ self.Show(False)
1726
+
1727
+ def Show(self, flag):
1728
+ for ctrl in self._children:
1729
+ ctrl.Show(flag)
1730
+ self.ShowItems(flag)
1731
+
1732
+ # def Bind(self, event_type, routine):
1733
+ # self.parent.Bind(event_type, routine, self)
1734
+
1735
+ def on_check(self, orgevent):
1736
+ #
1737
+ event = orgevent.Clone()
1738
+ event.SetEventType(wx.wxEVT_CHECKLISTBOX)
1739
+ event.SetId(self.Id)
1740
+ event.SetEventObject(self)
1741
+ # event.Int = self.GetSelection()
1742
+ wx.PostEvent(self.parent, event)
1743
+
1744
+ def on_right_click(self, event):
1745
+ menu = wx.Menu()
1746
+ parent = self.parent
1747
+ item = menu.Append(wx.ID_ANY, _("Check all"), "")
1748
+ parent.Bind(
1749
+ wx.EVT_MENU,
1750
+ lambda e: self.SetCheckedItems(range(len(self._children))),
1751
+ id=item.GetId(),
1752
+ )
1753
+ item = menu.Append(wx.ID_ANY, _("Uncheck all"), "")
1754
+ parent.Bind(wx.EVT_MENU, lambda e: self.SetCheckedItems([]), id=item.GetId())
1755
+ item = menu.Append(wx.ID_ANY, _("Invert selection"), "")
1756
+ parent.Bind(
1757
+ wx.EVT_MENU,
1758
+ lambda e: self.SetCheckedItems(
1759
+ [
1760
+ i
1761
+ for i in range(len(self._children))
1762
+ if not self._children[i].GetValue()
1763
+ ]
1764
+ ),
1765
+ id=item.GetId(),
1766
+ )
1767
+ parent.PopupMenu(menu)
1768
+ menu.Destroy()
1769
+
1770
+ def SetForegroundColour(self, wc):
1771
+ for ctrl in self._children:
1772
+ ctrl.SetForegroundColour(wc)
1773
+
1774
+ def SetBackgroundColour(self, wc):
1775
+ for ctrl in self._children:
1776
+ ctrl.SetBackgroundColour(wc)
1777
+
1778
+ def SetHelpText(self, help):
1779
+ self._help = help
1780
+
1781
+ def GetHelpText(self):
1782
+ return self._help
1783
+
1784
+ def Clear(self) -> None:
1785
+ with wx.BusyCursor():
1786
+ for child in self._children:
1787
+ child.Destroy()
1788
+ self._children.clear()
1789
+ self.choices.clear()
1790
+
1791
+ def Check(self, item: int, check: bool = True) -> None:
1792
+ """
1793
+ Check or uncheck an item in the CheckListBox.
1794
+ :param item: The index of the item to check or uncheck.
1795
+ :param check: True to check, False to uncheck.
1796
+ """
1797
+ if 0 <= item < len(self._children):
1798
+ self._children[item].SetValue(check)
1799
+
1800
+ def GetCheckItems(self) -> list:
1801
+ """
1802
+ Get a list of indices of checked items in the CheckListBox.
1803
+ :return: A list of indices of checked items.
1804
+ """
1805
+ return [idx for idx, ctrl in enumerate(self._children) if ctrl.GetValue()]
1806
+
1807
+ def GetCheckedStrings(self) -> list:
1808
+ """
1809
+ Get a list of strings of checked items in the CheckListBox.
1810
+ :return: A list of strings of checked items.
1811
+ """
1812
+ return [self.choices[idx] for idx in self.GetCheckItems()]
1813
+
1814
+ def GetSelections(self) -> list:
1815
+ """
1816
+ Get a list of indices of selected items in the CheckListBox.
1817
+ :return: A list of indices of selected items.
1818
+ """
1819
+ return self.GetCheckItems()
1820
+
1821
+ def SetCheckedStrings(self, choices):
1822
+ """
1823
+ Set the checked items in the CheckListBox based on a list of strings.
1824
+ :param choices: A list of strings to check.
1825
+ """
1826
+ for idx, choice in enumerate(self.choices):
1827
+ if choice in choices:
1828
+ self.Check(idx, True)
1829
+ else:
1830
+ self.Check(idx, False)
1831
+
1832
+ def SetCheckedItems(self, choices):
1833
+ """
1834
+ Set the checked items in the CheckListBox based on a list of indices.
1835
+ :param choices: A list of indices to check.
1836
+ """
1837
+ for idx in range(len(self._children)):
1838
+ self.Check(idx, idx in choices)
1839
+
1840
+ def Set(self, choices):
1841
+ """
1842
+ Set the choices for the CheckListBox.
1843
+ :param choices: A list of strings to set as choices.
1844
+ """
1845
+ # print (f"Setting choices for {self.GetLabel()}: {choices}")
1846
+ self.Clear()
1847
+ self.choices = list(choices)
1848
+ self._build_controls()
1849
+
1850
+
1625
1851
  ##############
1626
1852
  # GUI KEYSTROKE FUNCTIONS
1627
1853
  ##############
meerk40t/kernel/kernel.py CHANGED
@@ -182,6 +182,7 @@ class Kernel(Settings):
182
182
 
183
183
  self.os_information = self._get_environment()
184
184
  self.show_aio_prompt = True
185
+ self.silent_mode = False
185
186
 
186
187
  def __str__(self):
187
188
  return f"Kernel({self.name}, {self.profile}, {self.version})"
@@ -2865,8 +2866,35 @@ class Kernel(Settings):
2865
2866
  _("App: {name} {version}.").format(name=self.name, version=self.version)
2866
2867
  )
2867
2868
 
2869
+ @self.console_command("silent", _("Set/unset silent mode"))
2870
+ def set_silent(channel, _, remainder=None, **kwargs):
2871
+ """
2872
+ Set or unset silent mode. In silent mode no beeps will be played.
2873
+ """
2874
+ if remainder is None:
2875
+ channel(
2876
+ _("Silent mode is currently {mode}.").format(
2877
+ mode="ON" if self.silent_mode else "OFF"
2878
+ )
2879
+ )
2880
+ return
2881
+ if remainder.lower() not in ("on", "off"):
2882
+ channel(_("Please specify 'on' or 'off' to set silent mode."))
2883
+ return
2884
+ if remainder.lower() == "on":
2885
+ self.silent_mode = True
2886
+ else:
2887
+ self.silent_mode = False
2888
+ if self.silent_mode:
2889
+ channel(_("Silent mode is now ON."))
2890
+ else:
2891
+ channel(_("Silent mode is now OFF."))
2892
+
2868
2893
  @self.console_command("beep", _("Perform beep"))
2869
2894
  def beep(channel, _, **kwargs):
2895
+ if self.silent_mode:
2896
+ channel(_("Silent mode is on, no beep will be played."))
2897
+ return
2870
2898
  OS_NAME = self.os_information["OS_NAME"]
2871
2899
  system_sound = {
2872
2900
  "Windows": r"c:\Windows\Media\Sounds\Alarm01.wav",
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.7040"
14
+ APPLICATION_VERSION = "0.9.7051"
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
@@ -229,19 +229,24 @@ def _exe(restarted, args):
229
229
  server_mode = False
230
230
  if command:
231
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)
232
+ server_mode = server_mode or any(
233
+ substring in c
234
+ for substring in (
235
+ "lhyserver",
236
+ "grblserver",
237
+ "ruidacontrol",
238
+ "grblcontrol",
239
+ "webserver",
240
+ )
241
+ )
242
+ nogui = (hasattr(kernel.args, "gui_suppress") and kernel.args.gui_suppress) or (
243
+ hasattr(kernel.args, "no_gui") and kernel.args.no_gui
236
244
  )
237
245
  for idx, attrib in enumerate(("mktablength", "mktabpositions")):
238
246
  kernel.register(f"registered_mk_svg_parameters/tabs{idx}", attrib)
239
247
 
240
248
  require_partial_mode = False
241
- if (
242
- (not console or nogui) and
243
- (auto or daemon or server_mode)
244
- ):
249
+ if (not console or nogui) and (auto or daemon or server_mode):
245
250
  require_partial_mode = True
246
251
  # print (f"Auto: {auto}, Command: {command}, Console: {console}, daemon: {daemon}, nogui:{nogui}, Server: {server_mode} -> {require_partial_mode}")
247
252
  kernel(partial=require_partial_mode)
meerk40t/ruida/loader.py CHANGED
@@ -15,9 +15,11 @@ def data_viewer(data, data_type):
15
15
 
16
16
  if not data:
17
17
  return ""
18
+ magic = determine_magic_via_histogram(data)
18
19
  return BlobNode.hex_view(
19
- data=decode_bytes(data, determine_magic_via_histogram(data)),
20
+ data=decode_bytes(data, magic),
20
21
  data_type=data_type,
22
+ info = f', Magic={magic} (0x{magic:02x})'
21
23
  )
22
24
 
23
25
 
meerk40t/ruida/rdjob.py CHANGED
@@ -256,6 +256,8 @@ def encode_relcoord(coord):
256
256
 
257
257
 
258
258
  def encode_color(color):
259
+ # Scewed on RDC 22.01
260
+ # Maybe 16bit color is used?
259
261
  return encode32(int(color))
260
262
 
261
263
 
@@ -488,6 +490,7 @@ class RDJob:
488
490
  self._stopped = True
489
491
  self.enabled = True
490
492
  self._estimate = 0
493
+ self.offset = 0
491
494
 
492
495
  self.scale = UNITS_PER_uM
493
496
 
@@ -598,7 +601,8 @@ class RDJob:
598
601
  command = self.buffer.pop(0)
599
602
  try:
600
603
  array = list(command)
601
- self.process(array)
604
+ self.process(array, offset=self.offset)
605
+ self.offset += len(command)
602
606
  except IndexError as e:
603
607
  raise RuidaCommandError(
604
608
  f"Could not process Ruida buffer, {self.buffer[:25]} with magic: {self.magic:02}"
@@ -701,7 +705,7 @@ class RDJob:
701
705
  def set_color(self, color):
702
706
  self.color = color
703
707
 
704
- def process(self, array):
708
+ def process(self, array, offset=None):
705
709
  """
706
710
  Parses an individual unswizzled ruida command, updating the emulator state.
707
711
 
@@ -976,7 +980,7 @@ class RDJob:
976
980
  b = (c >> 16) & 0xFF
977
981
  c = Color(red=r, blue=b, green=g)
978
982
  self.set_color(c.hex)
979
- desc = f"{part}, Color {self.color}"
983
+ desc = f"Color Part {part}, {self.color}"
980
984
  elif array[1] == 0x10:
981
985
  value = array[2]
982
986
  desc = f"EnExIO Start {value}"
@@ -1012,12 +1016,44 @@ class RDJob:
1012
1016
  elif array[0] == 0xD8:
1013
1017
  if array[1] == 0x00:
1014
1018
  desc = "Start Process"
1015
- if array[1] == 0x10:
1019
+ elif array[1] == 0x10:
1016
1020
  desc = "Ref Point Mode 2, Machine Zero/Absolute Position"
1017
- if array[1] == 0x11:
1021
+ elif array[1] == 0x11:
1018
1022
  desc = "Ref Point Mode 1, Anchor Point"
1019
- if array[1] == 0x12:
1023
+ elif array[1] == 0x12:
1020
1024
  desc = "Ref Point Mode 0, Current Position"
1025
+ elif array[0] == 0xD9:
1026
+ if array[1] == 0x00:
1027
+ opts = array[2]
1028
+ value = abscoord(array[3:8])
1029
+ desc = f"Rapid move X ({value}μm)"
1030
+ elif array[1] == 0x01:
1031
+ opts = array[2]
1032
+ value = abscoord(array[3:8])
1033
+ desc = f"Rapid move Y ({value}μm)"
1034
+ elif array[1] == 0x02:
1035
+ opts = array[2]
1036
+ value = abscoord(array[3:8])
1037
+ desc = f"Rapid move Z ({value}μm)"
1038
+ elif array[1] == 0x03:
1039
+ opts = array[2]
1040
+ value = abscoord(array[3:8])
1041
+ desc = f"Rapid move U ({value}μm)"
1042
+ elif array[1] == 0x0F:
1043
+ opts = array[2]
1044
+ value = abscoord(array[3:8])
1045
+ desc = f"Rapid move Feed ({value}μm)"
1046
+ elif array[1] == 0x10:
1047
+ opts = array[2]
1048
+ x = abscoord(array[3:8])
1049
+ y = abscoord(array[8:13])
1050
+ desc = f"Rapid move XY ({x}μm, {y}μm)"
1051
+ elif array[1] == 0x30:
1052
+ opts = array[2]
1053
+ x = abscoord(array[3:7])
1054
+ y = abscoord(array[8:13])
1055
+ u = abscoord(array[13:18])
1056
+ desc = f"Rapid move XYU ({x}μm, {y}μm, {u}μm)"
1021
1057
  elif array[0] == 0xDA:
1022
1058
  mem = parse_mem(array[2:4])
1023
1059
  if array[1] == 0x01:
@@ -1240,7 +1276,8 @@ class RDJob:
1240
1276
  else:
1241
1277
  desc = "Unknown Command!"
1242
1278
  if self.channel:
1243
- self.channel(f"-**-> {str(bytes(array).hex())}\t({desc})")
1279
+ prefix = f"{offset:06x}" if offset is not None else ''
1280
+ self.channel(f"{prefix}-**-> {str(bytes(array).hex())}\t({desc})")
1244
1281
 
1245
1282
  def unswizzle(self, data):
1246
1283
  return bytes([self.lut_unswizzle[b] for b in data])
@@ -1325,6 +1362,9 @@ class RDJob:
1325
1362
  color = current_settings.get("line_color", 0)
1326
1363
  frequency = current_settings.get("frequency")
1327
1364
 
1365
+ if color == 0:
1366
+ color = current_settings.get("color", color)
1367
+
1328
1368
  self.speed_laser_1_part(part, speed)
1329
1369
  if frequency:
1330
1370
  self.frequency_part(0, part, frequency)
@@ -1670,7 +1710,7 @@ class RDJob:
1670
1710
  self(LAYER_COLOR, encode_color(color), output=output)
1671
1711
 
1672
1712
  def layer_color_part(self, part, color, output=None):
1673
- self(LAYER_COLOR, encode_part(part), encode_color(color), output=output)
1713
+ self(LAYER_COLOR_PART, encode_part(part), encode_color(color), output=output)
1674
1714
 
1675
1715
  def en_ex_io(self, value, output=None):
1676
1716
  """