meerk40t 0.9.7050__py2.py3-none-any.whl → 0.9.7900__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.
- meerk40t/balormk/controller.py +3 -3
- meerk40t/balormk/device.py +7 -0
- meerk40t/balormk/driver.py +23 -14
- meerk40t/balormk/galvo_commands.py +18 -3
- meerk40t/balormk/gui/balorconfig.py +6 -0
- meerk40t/balormk/livelightjob.py +36 -14
- meerk40t/camera/camera.py +1 -0
- meerk40t/camera/gui/camerapanel.py +154 -58
- meerk40t/camera/plugin.py +46 -5
- meerk40t/core/elements/branches.py +90 -20
- meerk40t/core/elements/elements.py +59 -37
- meerk40t/core/elements/trace.py +10 -6
- meerk40t/core/node/node.py +2 -0
- meerk40t/core/plotplanner.py +7 -4
- meerk40t/device/gui/defaultactions.py +78 -14
- meerk40t/dxf/dxf_io.py +42 -0
- meerk40t/grbl/controller.py +245 -35
- meerk40t/grbl/device.py +102 -26
- meerk40t/grbl/driver.py +8 -2
- meerk40t/grbl/gui/grblconfiguration.py +6 -0
- meerk40t/grbl/gui/grblcontroller.py +1 -1
- meerk40t/gui/about.py +7 -0
- meerk40t/gui/choicepropertypanel.py +20 -30
- meerk40t/gui/devicepanel.py +27 -16
- meerk40t/gui/icons.py +15 -0
- meerk40t/gui/laserpanel.py +102 -54
- meerk40t/gui/materialtest.py +12 -2
- meerk40t/gui/mkdebug.py +268 -9
- meerk40t/gui/navigationpanels.py +65 -7
- meerk40t/gui/propertypanels/operationpropertymain.py +185 -91
- meerk40t/gui/scenewidgets/elementswidget.py +7 -1
- meerk40t/gui/scenewidgets/selectionwidget.py +24 -9
- meerk40t/gui/simulation.py +1 -1
- meerk40t/gui/statusbarwidgets/shapepropwidget.py +50 -40
- meerk40t/gui/statusbarwidgets/statusbar.py +2 -2
- meerk40t/gui/toolwidgets/toolmeasure.py +1 -1
- meerk40t/gui/toolwidgets/toolnodeedit.py +4 -1
- meerk40t/gui/toolwidgets/tooltabedit.py +9 -7
- meerk40t/gui/wxmeerk40t.py +2 -0
- meerk40t/gui/wxmmain.py +23 -9
- meerk40t/gui/wxmribbon.py +36 -0
- meerk40t/gui/wxutils.py +66 -42
- meerk40t/kernel/inhibitor.py +120 -0
- meerk40t/kernel/kernel.py +38 -0
- meerk40t/lihuiyu/controller.py +33 -3
- meerk40t/lihuiyu/device.py +99 -4
- meerk40t/lihuiyu/driver.py +62 -5
- meerk40t/lihuiyu/gui/lhycontrollergui.py +69 -24
- meerk40t/lihuiyu/gui/lhydrivergui.py +6 -0
- meerk40t/lihuiyu/laserspeed.py +17 -10
- meerk40t/lihuiyu/parser.py +23 -0
- meerk40t/main.py +1 -1
- meerk40t/moshi/gui/moshidrivergui.py +7 -0
- meerk40t/newly/controller.py +3 -2
- meerk40t/newly/device.py +23 -2
- meerk40t/newly/driver.py +8 -3
- meerk40t/newly/gui/newlyconfig.py +7 -0
- meerk40t/ruida/gui/ruidaconfig.py +7 -0
- meerk40t/tools/geomstr.py +68 -48
- meerk40t/tools/rasterplotter.py +0 -5
- meerk40t/tools/ttfparser.py +155 -82
- {meerk40t-0.9.7050.dist-info → meerk40t-0.9.7900.dist-info}/METADATA +1 -1
- {meerk40t-0.9.7050.dist-info → meerk40t-0.9.7900.dist-info}/RECORD +68 -67
- {meerk40t-0.9.7050.dist-info → meerk40t-0.9.7900.dist-info}/LICENSE +0 -0
- {meerk40t-0.9.7050.dist-info → meerk40t-0.9.7900.dist-info}/WHEEL +0 -0
- {meerk40t-0.9.7050.dist-info → meerk40t-0.9.7900.dist-info}/entry_points.txt +0 -0
- {meerk40t-0.9.7050.dist-info → meerk40t-0.9.7900.dist-info}/top_level.txt +0 -0
- {meerk40t-0.9.7050.dist-info → meerk40t-0.9.7900.dist-info}/zip-safe +0 -0
meerk40t/gui/wxutils.py
CHANGED
@@ -1650,58 +1650,31 @@ class wxCheckListBox(StaticBoxSizer):
|
|
1650
1650
|
**kwargs,
|
1651
1651
|
):
|
1652
1652
|
self.parent = parent
|
1653
|
-
self.choices =
|
1653
|
+
self.choices = []
|
1654
1654
|
self._children = []
|
1655
|
-
self._tool_tip =
|
1655
|
+
self._tool_tip = ""
|
1656
1656
|
self._help = None
|
1657
1657
|
super().__init__(
|
1658
1658
|
parent=parent, id=wx.ID_ANY, label=label, orientation=wx.VERTICAL
|
1659
1659
|
)
|
1660
1660
|
self.majorDimension = majorDimension
|
1661
1661
|
self.style = style
|
1662
|
-
|
1663
|
-
|
1664
|
-
|
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")
|
1662
|
+
if choices is None:
|
1663
|
+
choices = []
|
1664
|
+
self.Set(choices)
|
1700
1665
|
|
1701
1666
|
@property
|
1702
1667
|
def Children(self):
|
1703
1668
|
return self._children
|
1704
1669
|
|
1670
|
+
def on_mouse_over_check(self, event):
|
1671
|
+
"""
|
1672
|
+
Handle mouse over events to show tooltips on Linux.
|
1673
|
+
"""
|
1674
|
+
if self._tool_tip:
|
1675
|
+
event.GetEventObject().SetToolTip(self._tool_tip)
|
1676
|
+
event.Skip()
|
1677
|
+
|
1705
1678
|
def GetParent(self):
|
1706
1679
|
return self.parent
|
1707
1680
|
|
@@ -1764,6 +1737,11 @@ class wxCheckListBox(StaticBoxSizer):
|
|
1764
1737
|
),
|
1765
1738
|
id=item.GetId(),
|
1766
1739
|
)
|
1740
|
+
# Test routines
|
1741
|
+
# item = menu.Append(wx.ID_ANY, "Test-Routine to set few items", "")
|
1742
|
+
# parent.Bind(wx.EVT_MENU, lambda e: self.Set(["A", "B", "C"]), id=item.GetId())
|
1743
|
+
# item = menu.Append(wx.ID_ANY, "Test-Routine to set many items", "")
|
1744
|
+
# parent.Bind(wx.EVT_MENU, lambda e: self.Set([f"Item {i}" for i in range(20)]), id=item.GetId())
|
1767
1745
|
parent.PopupMenu(menu)
|
1768
1746
|
menu.Destroy()
|
1769
1747
|
|
@@ -1843,9 +1821,55 @@ class wxCheckListBox(StaticBoxSizer):
|
|
1843
1821
|
:param choices: A list of strings to set as choices.
|
1844
1822
|
"""
|
1845
1823
|
# print (f"Setting choices for {self.GetLabel()}: {choices}")
|
1846
|
-
|
1824
|
+
"""
|
1825
|
+
This is more efficient than clearing and rebuilding the controls.
|
1826
|
+
Update the labels of existing controls to match new_choices.
|
1827
|
+
If there are more controls than choices, extra controls are destroyed.
|
1828
|
+
If there are fewer controls than choices, new controls are created.
|
1829
|
+
"""
|
1830
|
+
# Update existing controls
|
1831
|
+
if self.majorDimension == 0 or self.style == wx.RA_SPECIFY_ROWS:
|
1832
|
+
self.majorDimension = 1000
|
1833
|
+
last_container = None
|
1834
|
+
for idx, choice in enumerate(choices):
|
1835
|
+
if idx < len(self._children):
|
1836
|
+
self._children[idx].SetLabel(choice)
|
1837
|
+
# self._children[idx].Show(True)
|
1838
|
+
else:
|
1839
|
+
# Add new controls if needed
|
1840
|
+
check_option = wx.CheckBox(self.parent, wx.ID_ANY, label=choice)
|
1841
|
+
if self._tool_tip:
|
1842
|
+
check_option.SetToolTip(self._tool_tip)
|
1843
|
+
check_option.Bind(wx.EVT_CHECKBOX, self.on_check)
|
1844
|
+
check_option.Bind(wx.EVT_RIGHT_DOWN, self.on_right_click)
|
1845
|
+
if platform.system() == "Linux":
|
1846
|
+
check_option.Bind(wx.EVT_MOTION, self.on_mouse_over_check)
|
1847
|
+
set_color_according_to_theme(check_option, "text_bg", "text_fg")
|
1848
|
+
self._children.append(check_option)
|
1849
|
+
# Add to layout
|
1850
|
+
# Find or create the appropriate container
|
1851
|
+
if idx % self.majorDimension == 0:
|
1852
|
+
last_container = wx.BoxSizer(wx.HORIZONTAL)
|
1853
|
+
self.Add(last_container, 0, wx.EXPAND, 0)
|
1854
|
+
else:
|
1855
|
+
# Find the last container
|
1856
|
+
if last_container is None:
|
1857
|
+
for c in self.GetChildren():
|
1858
|
+
if c.IsSizer():
|
1859
|
+
last_container = c.GetSizer()
|
1860
|
+
if last_container is None:
|
1861
|
+
# fallback: create new container
|
1862
|
+
last_container = wx.BoxSizer(wx.HORIZONTAL)
|
1863
|
+
self.Add(last_container, 0, wx.EXPAND, 0)
|
1864
|
+
last_container.Add(check_option, 1, wx.ALIGN_CENTER_VERTICAL, 0)
|
1865
|
+
# Remove extra controls
|
1866
|
+
if len(self._children) > len(choices):
|
1867
|
+
for idx in range(len(choices), len(self._children)):
|
1868
|
+
self._children[idx].Destroy()
|
1869
|
+
self._children = self._children[: len(choices)]
|
1870
|
+
self.Layout()
|
1871
|
+
self.parent.Layout()
|
1847
1872
|
self.choices = list(choices)
|
1848
|
-
self._build_controls()
|
1849
1873
|
|
1850
1874
|
|
1851
1875
|
##############
|
@@ -0,0 +1,120 @@
|
|
1
|
+
"""
|
2
|
+
This modules prevents the OS from sleeping / hibernating.
|
3
|
+
It is used to ensure that the system remains active during long-running operations.
|
4
|
+
It is not intended to be used for general-purpose tasks.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import ctypes
|
8
|
+
import platform
|
9
|
+
import subprocess
|
10
|
+
|
11
|
+
_ES_CONTINUOUS = 0x80000000
|
12
|
+
_ES_SYSTEM_REQUIRED = 0x00000001
|
13
|
+
|
14
|
+
# Extract common target list up top
|
15
|
+
_SYSTEMCTL_TARGETS = [
|
16
|
+
"sleep.target",
|
17
|
+
"suspend.target",
|
18
|
+
"hibernate.target",
|
19
|
+
"hybrid-sleep.target",
|
20
|
+
]
|
21
|
+
|
22
|
+
|
23
|
+
def _run_systemctl(action: str):
|
24
|
+
try:
|
25
|
+
# Check if systemctl is available
|
26
|
+
proc = subprocess.run(["systemctl", action] + _SYSTEMCTL_TARGETS)
|
27
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
28
|
+
print("systemctl is not available on this system.")
|
29
|
+
return False
|
30
|
+
except Exception as e:
|
31
|
+
print(f"An unexpected error occurred: {e}")
|
32
|
+
return False
|
33
|
+
# print(f"systemctl {action} returned {proc.returncode} [{proc}]")
|
34
|
+
return proc.returncode == 0
|
35
|
+
|
36
|
+
|
37
|
+
def _darwin_inhibit():
|
38
|
+
try:
|
39
|
+
proc = subprocess.run(["caffeinate", "-i"])
|
40
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
41
|
+
print("caffeinate is not available on this system.")
|
42
|
+
return False
|
43
|
+
except Exception as e:
|
44
|
+
print(f"An unexpected error occurred: {e}")
|
45
|
+
return False
|
46
|
+
return proc.returncode == 0
|
47
|
+
|
48
|
+
|
49
|
+
def _darwin_release():
|
50
|
+
try:
|
51
|
+
# Use killall to stop caffeinate
|
52
|
+
proc = subprocess.run(["killall", "caffeinate"])
|
53
|
+
except Exception as e:
|
54
|
+
print(f"An unexpected error occurred: {e}")
|
55
|
+
return False
|
56
|
+
return proc.returncode == 0
|
57
|
+
|
58
|
+
|
59
|
+
def _linux_inhibit():
|
60
|
+
return _run_systemctl("mask")
|
61
|
+
|
62
|
+
|
63
|
+
def _linux_release():
|
64
|
+
return _run_systemctl("unmask")
|
65
|
+
|
66
|
+
|
67
|
+
def _windows_inhibit():
|
68
|
+
try:
|
69
|
+
# Set the thread execution state to prevent sleep
|
70
|
+
ctypes.windll.kernel32.SetThreadExecutionState(
|
71
|
+
_ES_CONTINUOUS | _ES_SYSTEM_REQUIRED
|
72
|
+
)
|
73
|
+
except Exception as e:
|
74
|
+
print(f"An error occurred while setting thread execution state: {e}")
|
75
|
+
return False
|
76
|
+
return True
|
77
|
+
|
78
|
+
|
79
|
+
def _windows_release():
|
80
|
+
try:
|
81
|
+
# Reset the thread execution state to allow sleep
|
82
|
+
ctypes.windll.kernel32.SetThreadExecutionState(_ES_CONTINUOUS)
|
83
|
+
except Exception as e:
|
84
|
+
print(f"An error occurred while resetting thread execution state: {e}")
|
85
|
+
return False
|
86
|
+
return True
|
87
|
+
|
88
|
+
|
89
|
+
# Darwin does not have a systemctl, but uses caffeinate
|
90
|
+
# Linux uses systemctl to mask/unmask sleep targets
|
91
|
+
# Windows uses SetThreadExecutionState to prevent sleep
|
92
|
+
# NB: Darwin does not to work relaibly on my testsystem, so has been disabled
|
93
|
+
_ACTIONS = {
|
94
|
+
# "Darwin": {"inhibit": _darwin_inhibit, "release": _darwin_release},
|
95
|
+
"Linux": {"inhibit": _linux_inhibit, "release": _linux_release},
|
96
|
+
"Windows": {"inhibit": _windows_inhibit, "release": _windows_release},
|
97
|
+
}
|
98
|
+
|
99
|
+
|
100
|
+
class Inhibitor:
|
101
|
+
def __init__(self):
|
102
|
+
self._os = platform.system()
|
103
|
+
self.active = False
|
104
|
+
self._actions = _ACTIONS.get(self._os)
|
105
|
+
|
106
|
+
@property
|
107
|
+
def available(self) -> bool:
|
108
|
+
return self._actions is not None
|
109
|
+
|
110
|
+
def inhibit(self):
|
111
|
+
if not self.available or self.active:
|
112
|
+
return
|
113
|
+
if self._actions["inhibit"]():
|
114
|
+
self.active = True
|
115
|
+
|
116
|
+
def release(self):
|
117
|
+
if not self.available or not self.active:
|
118
|
+
return
|
119
|
+
if self._actions["release"]():
|
120
|
+
self.active = False
|
meerk40t/kernel/kernel.py
CHANGED
@@ -19,6 +19,7 @@ from .functions import (
|
|
19
19
|
console_option,
|
20
20
|
get_safe_path,
|
21
21
|
)
|
22
|
+
from .inhibitor import Inhibitor
|
22
23
|
from .jobs import ConsoleFunction, Job
|
23
24
|
from .lifecycles import *
|
24
25
|
from .module import Module
|
@@ -183,6 +184,7 @@ class Kernel(Settings):
|
|
183
184
|
self.os_information = self._get_environment()
|
184
185
|
self.show_aio_prompt = True
|
185
186
|
self.silent_mode = False
|
187
|
+
self.inhibitor = Inhibitor()
|
186
188
|
|
187
189
|
def __str__(self):
|
188
190
|
return f"Kernel({self.name}, {self.profile}, {self.version})"
|
@@ -2855,6 +2857,42 @@ class Kernel(Settings):
|
|
2855
2857
|
channel(_("Syntax Error: timer<name> <times> <interval> <command>"))
|
2856
2858
|
return
|
2857
2859
|
|
2860
|
+
# ==========
|
2861
|
+
# Sleep Commands
|
2862
|
+
# ==========
|
2863
|
+
@self.console_argument(
|
2864
|
+
"mode", type=str, help=_("Mode to set: prevent/allow"), default=None
|
2865
|
+
)
|
2866
|
+
@self.console_command(
|
2867
|
+
"system_hibernate", _("Prevent/allow system hibernation.")
|
2868
|
+
)
|
2869
|
+
def system_hibernate(channel, _, mode=None, **kwargs):
|
2870
|
+
"""
|
2871
|
+
Prevent or allow system hibernation. This is a toggle command.
|
2872
|
+
If no mode is specified, it will print the current state.
|
2873
|
+
"""
|
2874
|
+
if not self.inhibitor.available:
|
2875
|
+
channel(_("Inhibitor is not available on this system."))
|
2876
|
+
return
|
2877
|
+
if mode is not None:
|
2878
|
+
if mode.lower() not in ("prevent", "allow"):
|
2879
|
+
channel(_("Please specify 'prevent' or 'allow'."))
|
2880
|
+
return
|
2881
|
+
if mode.lower() == "prevent":
|
2882
|
+
self.inhibitor.inhibit()
|
2883
|
+
else:
|
2884
|
+
self.inhibitor.release()
|
2885
|
+
sudo_msg = _("You might need system administrator priviliges.")
|
2886
|
+
if self.inhibitor.active:
|
2887
|
+
channel(_("System hibernation is prevented."))
|
2888
|
+
else:
|
2889
|
+
channel(_("System hibernation is allowed."))
|
2890
|
+
if self.os_information["OS_NAME"] == "Linux" and (
|
2891
|
+
(mode == "prevent" and not self.inhibitor.active)
|
2892
|
+
or (mode == "allow" and self.inhibitor.active)
|
2893
|
+
):
|
2894
|
+
channel(sudo_msg)
|
2895
|
+
|
2858
2896
|
# ==========
|
2859
2897
|
# CORE OBJECTS COMMANDS
|
2860
2898
|
# ==========
|
meerk40t/lihuiyu/controller.py
CHANGED
@@ -124,10 +124,21 @@ def onewire_crc_lookup(line):
|
|
124
124
|
@param line: line to be CRC'd
|
125
125
|
@return: 8 bit crc of line.
|
126
126
|
"""
|
127
|
+
|
127
128
|
crc = 0
|
128
129
|
for i in range(0, 30):
|
129
130
|
crc = line[i] ^ crc
|
130
131
|
crc = crc_table[crc & 0x0F] ^ crc_table[16 + ((crc >> 4) & 0x0F)]
|
132
|
+
|
133
|
+
""" Print the line in hex and ascii format for debugging purposes.
|
134
|
+
def hex_repr(data):
|
135
|
+
return " ".join(f"{x:02x}" for x in data)
|
136
|
+
|
137
|
+
def ascii_repr(data):
|
138
|
+
return "".join(chr(x) if 32 <= x < 127 else "." for x in data)
|
139
|
+
|
140
|
+
print (f"Line ({len(line)} bytes): {hex_repr(line)} {ascii_repr(line)} CRC: {hex(crc)}")
|
141
|
+
"""
|
131
142
|
return crc
|
132
143
|
|
133
144
|
|
@@ -618,6 +629,16 @@ class LihuiyuController:
|
|
618
629
|
self._preempt.clear()
|
619
630
|
self.update_buffer()
|
620
631
|
|
632
|
+
def debug_packet(self, packet):
|
633
|
+
"""
|
634
|
+
Debugging function to print the packet in a readable format.
|
635
|
+
We will output both hex and ascii representation of the packet.
|
636
|
+
@param packet: Packet to debug.
|
637
|
+
"""
|
638
|
+
hex_packet = " ".join(f"{b:02x}" for b in packet)
|
639
|
+
ascii_packet = "".join(chr(b) if 32 <= b < 127 else "." for b in packet)
|
640
|
+
print(f"Packet: {hex_packet} | ASCII: {ascii_packet} (len={len(packet)})")
|
641
|
+
|
621
642
|
def process_queue(self):
|
622
643
|
"""
|
623
644
|
Attempts to process the buffer/queue
|
@@ -662,6 +683,13 @@ class LihuiyuController:
|
|
662
683
|
post_send_command = None
|
663
684
|
default_checksum = True
|
664
685
|
|
686
|
+
if packet.startswith(b"AT"):
|
687
|
+
# This is as special case for the M3 only:
|
688
|
+
# AT command packages are padded with 0x00 and not 'F' as usal
|
689
|
+
if packet.endswith(b"\n"):
|
690
|
+
packet = packet[:-1]
|
691
|
+
c = b"\x00"
|
692
|
+
packet += c * (30 - len(packet)) # Padding with 0 character
|
665
693
|
# find pipe commands.
|
666
694
|
if packet.endswith(b"\n"):
|
667
695
|
packet = packet[:-1]
|
@@ -691,7 +719,7 @@ class LihuiyuController:
|
|
691
719
|
self.update_state("terminate")
|
692
720
|
self.is_shutdown = True
|
693
721
|
packet = packet[:-1]
|
694
|
-
if packet.startswith(b"A"):
|
722
|
+
if packet.startswith(b"A") and not packet.startswith(b"AT"):
|
695
723
|
# This is a challenge code. A is only used for serial challenges.
|
696
724
|
post_send_command = self._confirm_serial
|
697
725
|
if len(packet) != 0:
|
@@ -703,13 +731,14 @@ class LihuiyuController:
|
|
703
731
|
c = b"F" # Packet was simply #. We can do nothing.
|
704
732
|
packet += bytes([c]) * (30 - len(packet)) # Padding. '\n'
|
705
733
|
else:
|
706
|
-
|
734
|
+
padder = b"\x00" if packet.startswith(b"AT") else b"F"
|
735
|
+
packet += padder * (30 - len(packet)) # Padding. '\n'
|
707
736
|
if not realtime and self.state in ("pause", "busy"):
|
708
737
|
return False # Processing normal queue, PAUSE and BUSY apply.
|
709
738
|
|
710
739
|
# Packet is prepared and ready to send. Open Channel.
|
711
740
|
self.open()
|
712
|
-
|
741
|
+
# print (f"Packet: {packet!r} (len={len(packet)})" )
|
713
742
|
if len(packet) == 30:
|
714
743
|
# We have a sendable packet.
|
715
744
|
if not self.pre_ok:
|
@@ -718,6 +747,7 @@ class LihuiyuController:
|
|
718
747
|
packet = b"\x00" + packet + bytes([onewire_crc_lookup(packet)])
|
719
748
|
else:
|
720
749
|
packet = b"\x00" + packet + bytes([onewire_crc_lookup(packet) ^ 0xFF])
|
750
|
+
# self.debug_packet(packet)
|
721
751
|
self.connection.write(packet)
|
722
752
|
self.pre_ok = False
|
723
753
|
|
meerk40t/lihuiyu/device.py
CHANGED
@@ -129,6 +129,13 @@ class LihuiyuDevice(Service, Status):
|
|
129
129
|
]
|
130
130
|
self.register_choices("bed_dim", choices)
|
131
131
|
|
132
|
+
def get_max_range():
|
133
|
+
"""
|
134
|
+
Returns the maximum range of the device.
|
135
|
+
"""
|
136
|
+
dev_mode = getattr(self.kernel.root, "developer_mode", False)
|
137
|
+
return 100 if dev_mode else 50
|
138
|
+
|
132
139
|
choices = [
|
133
140
|
{
|
134
141
|
"attr": "label",
|
@@ -153,6 +160,52 @@ class LihuiyuDevice(Service, Status):
|
|
153
160
|
"section": "_10_" + _("Configuration"),
|
154
161
|
"subsection": _("Board Setup"),
|
155
162
|
},
|
163
|
+
{
|
164
|
+
"attr": "supports_pwm",
|
165
|
+
"object": self,
|
166
|
+
"default": False,
|
167
|
+
"type": bool,
|
168
|
+
"label": _("Hardware-PWM"),
|
169
|
+
"tip": _(
|
170
|
+
"Does the board support Hardware-PWM. Only M3 and fireware >= 2024.01.18g support PWM. Earlier M3 revisions are just M2+."
|
171
|
+
),
|
172
|
+
"section": "_10_" + _("Configuration"),
|
173
|
+
"subsection": _("Hardware-Laser-Power"),
|
174
|
+
"conditional": (self, "board", "M3"),
|
175
|
+
"signals": "pwm_mode_changed",
|
176
|
+
},
|
177
|
+
{
|
178
|
+
"attr": "pwm_speedcode",
|
179
|
+
"object": self,
|
180
|
+
"default": False,
|
181
|
+
"type": bool,
|
182
|
+
"label": _("Use PWM-Speedcode"),
|
183
|
+
"tip": _("PWM Method: set power as well in LHY-speedcodes."),
|
184
|
+
"section": "_10_" + _("Configuration"),
|
185
|
+
"subsection": _("Hardware-Laser-Power"),
|
186
|
+
"conditional": (self, "supports_pwm"),
|
187
|
+
"hidden": True,
|
188
|
+
},
|
189
|
+
{
|
190
|
+
"attr": "power_scale",
|
191
|
+
"object": self,
|
192
|
+
"default": 30,
|
193
|
+
"type": int,
|
194
|
+
"style": "slider",
|
195
|
+
"min": 1,
|
196
|
+
"max": get_max_range,
|
197
|
+
"label": _("Maximum Laser strength"),
|
198
|
+
"trailer": "%",
|
199
|
+
"tip": _(
|
200
|
+
"Set the maximum laser power level, any operation power will be a fraction of this"
|
201
|
+
)
|
202
|
+
+ "\n"
|
203
|
+
+ _("Setting this too high may cause damage to your laser tube!"),
|
204
|
+
"section": "_10_" + _("Configuration"),
|
205
|
+
"subsection": _("Hardware-Laser-Power"),
|
206
|
+
"conditional": (self, "supports_pwm"),
|
207
|
+
"signals": "pwm_mode_changed",
|
208
|
+
},
|
156
209
|
{
|
157
210
|
"attr": "flip_x",
|
158
211
|
"object": self,
|
@@ -547,12 +600,15 @@ class LihuiyuDevice(Service, Status):
|
|
547
600
|
action="store_true",
|
548
601
|
help=_("override one second laser fire pulse duration"),
|
549
602
|
)
|
603
|
+
@self.console_option("power", "p", type=str, help=_("Power level"))
|
550
604
|
@self.console_argument("time", type=float, help=_("laser fire pulse duration"))
|
551
605
|
@self.console_command(
|
552
606
|
"pulse",
|
553
607
|
help=_("pulse <time>: Pulse the laser in place."),
|
554
608
|
)
|
555
|
-
def pulse(
|
609
|
+
def pulse(
|
610
|
+
command, channel, _, time=None, power=None, idonotlovemyhouse=False, **kwgs
|
611
|
+
):
|
556
612
|
if time is None:
|
557
613
|
channel(_("Must specify a pulse time in milliseconds."))
|
558
614
|
return
|
@@ -568,16 +624,33 @@ class LihuiyuDevice(Service, Status):
|
|
568
624
|
except IndexError:
|
569
625
|
return
|
570
626
|
|
571
|
-
def timed_fire():
|
627
|
+
def timed_fire(withpower=None):
|
572
628
|
yield "wait_finish"
|
573
|
-
|
629
|
+
if withpower is not None:
|
630
|
+
yield "laser_on", withpower
|
631
|
+
else:
|
632
|
+
yield "laser_on"
|
574
633
|
yield "wait", time
|
575
634
|
yield "laser_off"
|
576
635
|
|
636
|
+
if power is not None:
|
637
|
+
try:
|
638
|
+
if power.endswith("%"):
|
639
|
+
power = power[:-1]
|
640
|
+
power = float(power) * 10
|
641
|
+
else:
|
642
|
+
power = float(power)
|
643
|
+
except ValueError:
|
644
|
+
channel(_("Power must be valid value."))
|
645
|
+
return
|
646
|
+
if not (0 <= power <= 1000):
|
647
|
+
channel(_("Power must be between 0 and 1000."))
|
648
|
+
return
|
649
|
+
|
577
650
|
if self.spooler.is_idle:
|
578
651
|
label = _("Pulse laser for {time}ms").format(time=time)
|
579
652
|
self.spooler.laserjob(
|
580
|
-
list(timed_fire()),
|
653
|
+
list(timed_fire(withpower=power)),
|
581
654
|
label=label,
|
582
655
|
helper=True,
|
583
656
|
outline=[self.native] * 4,
|
@@ -892,6 +965,27 @@ class LihuiyuDevice(Service, Status):
|
|
892
965
|
code = b"A%s\n" % challenge
|
893
966
|
self.output.write(code)
|
894
967
|
|
968
|
+
def _validate_board(channel, board):
|
969
|
+
"""
|
970
|
+
Validates the board type
|
971
|
+
"""
|
972
|
+
if self.board != board:
|
973
|
+
channel(
|
974
|
+
_(
|
975
|
+
"This command is only available for {target} boards. This is a {board}."
|
976
|
+
).format(target=board, board=self.board)
|
977
|
+
)
|
978
|
+
return False
|
979
|
+
return True
|
980
|
+
|
981
|
+
@self.console_command(
|
982
|
+
"get_m3nano_info",
|
983
|
+
help=_("Request M3Nano+ board info"),
|
984
|
+
)
|
985
|
+
def get_m3nano_info(command, channel, _, remainder=None, **kwgs):
|
986
|
+
if _validate_board(channel, "M3"):
|
987
|
+
self.driver.get_m3_hardware_info()
|
988
|
+
|
895
989
|
@self.console_command("start", help=_("Start Pipe to Controller"))
|
896
990
|
def pipe_start(command, channel, _, **kwgs):
|
897
991
|
self.controller.update_state("active")
|
@@ -1050,6 +1144,7 @@ class LihuiyuDevice(Service, Status):
|
|
1050
1144
|
@signal_listener("plot_shift")
|
1051
1145
|
@signal_listener("plot_phase_type")
|
1052
1146
|
@signal_listener("plot_phase_value")
|
1147
|
+
@signal_listener("supports_pwm")
|
1053
1148
|
def plot_attributes_update(self, origin=None, *args):
|
1054
1149
|
self.driver.plot_attribute_update()
|
1055
1150
|
|