plotdeck 1.0.0__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.
- plotdeck/__init__.py +4 -0
- plotdeck/__main__.py +6 -0
- plotdeck/app.py +55 -0
- plotdeck/core/__init__.py +4 -0
- plotdeck/core/controller.py +561 -0
- plotdeck/core/gcode_parser.py +404 -0
- plotdeck/core/grbl_protocol.py +278 -0
- plotdeck/core/status.py +148 -0
- plotdeck/core/streamer.py +328 -0
- plotdeck/core/transport.py +375 -0
- plotdeck/ui/__init__.py +1 -0
- plotdeck/ui/connection_panel.py +139 -0
- plotdeck/ui/console.py +171 -0
- plotdeck/ui/dialogs.py +317 -0
- plotdeck/ui/editor.py +544 -0
- plotdeck/ui/job_panel.py +130 -0
- plotdeck/ui/jog_panel.py +222 -0
- plotdeck/ui/main_window.py +1116 -0
- plotdeck/ui/manual.py +156 -0
- plotdeck/ui/status_panel.py +244 -0
- plotdeck/ui/theme.py +329 -0
- plotdeck/ui/visualizer.py +374 -0
- plotdeck/utils/__init__.py +1 -0
- plotdeck/utils/logging_setup.py +42 -0
- plotdeck/utils/settings.py +86 -0
- plotdeck-1.0.0.dist-info/METADATA +227 -0
- plotdeck-1.0.0.dist-info/RECORD +30 -0
- plotdeck-1.0.0.dist-info/WHEEL +5 -0
- plotdeck-1.0.0.dist-info/entry_points.txt +5 -0
- plotdeck-1.0.0.dist-info/top_level.txt +1 -0
plotdeck/ui/jog_panel.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Jog pad, step chips, zeroing and work-coordinate selection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import Qt, Signal
|
|
6
|
+
from PySide6.QtGui import QKeyEvent
|
|
7
|
+
from PySide6.QtWidgets import (
|
|
8
|
+
QComboBox,
|
|
9
|
+
QDoubleSpinBox,
|
|
10
|
+
QGridLayout,
|
|
11
|
+
QHBoxLayout,
|
|
12
|
+
QLabel,
|
|
13
|
+
QPushButton,
|
|
14
|
+
QVBoxLayout,
|
|
15
|
+
QWidget,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from ..core.grbl_protocol import state_family
|
|
19
|
+
from ..core.status import MachineStatus
|
|
20
|
+
from .theme import tokens
|
|
21
|
+
|
|
22
|
+
STEPS = ["0.01", "0.1", "1", "10", "100"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class JogPanel(QWidget):
|
|
26
|
+
jogRequested = Signal(str, float, float)
|
|
27
|
+
cancelRequested = Signal()
|
|
28
|
+
homeRequested = Signal()
|
|
29
|
+
unlockRequested = Signal()
|
|
30
|
+
zeroRequested = Signal(str)
|
|
31
|
+
gotoZeroRequested = Signal()
|
|
32
|
+
wcsChanged = Signal(int)
|
|
33
|
+
|
|
34
|
+
def __init__(self, token: dict[str, str] | None = None, parent=None) -> None: # noqa: ANN001
|
|
35
|
+
super().__init__(parent)
|
|
36
|
+
self._token = token or tokens()
|
|
37
|
+
self.setObjectName("Panel")
|
|
38
|
+
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
|
39
|
+
|
|
40
|
+
header = QLabel("JOG")
|
|
41
|
+
header.setObjectName("SectionHeader")
|
|
42
|
+
|
|
43
|
+
self._step = QComboBox()
|
|
44
|
+
self._step.addItems(STEPS)
|
|
45
|
+
self._step.setCurrentText("1")
|
|
46
|
+
self._step.setToolTip("Jog step (mm). [ and ] change the step.")
|
|
47
|
+
self._feed = QDoubleSpinBox()
|
|
48
|
+
self._feed.setRange(10, 15000)
|
|
49
|
+
self._feed.setValue(1000)
|
|
50
|
+
self._feed.setDecimals(0)
|
|
51
|
+
self._feed.setSuffix(" mm/min")
|
|
52
|
+
step_row = QHBoxLayout()
|
|
53
|
+
step_row.addWidget(QLabel("Step"))
|
|
54
|
+
step_row.addWidget(self._step, 1)
|
|
55
|
+
step_row.addWidget(self._feed, 1)
|
|
56
|
+
|
|
57
|
+
self._pad: dict[str, QPushButton] = {}
|
|
58
|
+
grid = QGridLayout()
|
|
59
|
+
grid.setSpacing(4)
|
|
60
|
+
positions = {
|
|
61
|
+
"Y+": (0, 1),
|
|
62
|
+
"X-": (1, 0),
|
|
63
|
+
"HOME": (1, 1),
|
|
64
|
+
"X+": (1, 2),
|
|
65
|
+
"Y-": (2, 1),
|
|
66
|
+
}
|
|
67
|
+
for name, (row, column) in positions.items():
|
|
68
|
+
button = QPushButton("⌂" if name == "HOME" else name)
|
|
69
|
+
button.setMinimumSize(44, 44)
|
|
70
|
+
button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
|
71
|
+
if name == "HOME":
|
|
72
|
+
button.setToolTip("Go to work zero (G0 X0 Y0)")
|
|
73
|
+
button.clicked.connect(self.gotoZeroRequested)
|
|
74
|
+
else:
|
|
75
|
+
axis, direction = name[0], 1 if name[1] == "+" else -1
|
|
76
|
+
button.clicked.connect(lambda _=False, a=axis, d=direction: self._jog(a, d))
|
|
77
|
+
grid.addWidget(button, row, column)
|
|
78
|
+
self._pad[name] = button
|
|
79
|
+
|
|
80
|
+
z_column = QVBoxLayout()
|
|
81
|
+
self._z_up = QPushButton("Z+")
|
|
82
|
+
self._z_down = QPushButton("Z-")
|
|
83
|
+
for button in (self._z_up, self._z_down):
|
|
84
|
+
button.setMinimumSize(48, 44)
|
|
85
|
+
button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
|
86
|
+
self._z_up.clicked.connect(lambda: self._jog("Z", 1))
|
|
87
|
+
self._z_down.clicked.connect(lambda: self._jog("Z", -1))
|
|
88
|
+
z_column.addWidget(self._z_up)
|
|
89
|
+
z_column.addWidget(self._z_down)
|
|
90
|
+
z_column.addStretch(1)
|
|
91
|
+
|
|
92
|
+
pad_row = QHBoxLayout()
|
|
93
|
+
pad_row.addLayout(grid, 1)
|
|
94
|
+
pad_row.addLayout(z_column)
|
|
95
|
+
|
|
96
|
+
zero_row = QHBoxLayout()
|
|
97
|
+
zero_row.addWidget(QLabel("Zero"))
|
|
98
|
+
self._zero_buttons: list[QPushButton] = []
|
|
99
|
+
for axis in ("X", "Y", "Z"):
|
|
100
|
+
button = QPushButton(axis)
|
|
101
|
+
button.setToolTip(f"Set work zero for {axis} (G10 L20)")
|
|
102
|
+
button.clicked.connect(lambda _=False, a=axis: self.zeroRequested.emit(a))
|
|
103
|
+
zero_row.addWidget(button)
|
|
104
|
+
self._zero_buttons.append(button)
|
|
105
|
+
all_zero = QPushButton("All")
|
|
106
|
+
all_zero.setToolTip("Zero all axes")
|
|
107
|
+
all_zero.clicked.connect(self._zero_all)
|
|
108
|
+
zero_row.addWidget(all_zero)
|
|
109
|
+
self._zero_buttons.append(all_zero)
|
|
110
|
+
|
|
111
|
+
self._wcs = QComboBox()
|
|
112
|
+
for index in range(1, 7):
|
|
113
|
+
self._wcs.addItem(f"G5{index + 3}", index)
|
|
114
|
+
self._wcs.currentIndexChanged.connect(
|
|
115
|
+
lambda: self.wcsChanged.emit(int(self._wcs.currentData()))
|
|
116
|
+
)
|
|
117
|
+
machine_row = QHBoxLayout()
|
|
118
|
+
self._home_button = QPushButton("Home")
|
|
119
|
+
self._home_button.clicked.connect(self.homeRequested)
|
|
120
|
+
self._unlock_button = QPushButton("Unlock")
|
|
121
|
+
self._unlock_button.clicked.connect(self.unlockRequested)
|
|
122
|
+
self._cancel_button = QPushButton("Cancel jog")
|
|
123
|
+
self._cancel_button.clicked.connect(self.cancelRequested)
|
|
124
|
+
machine_row.addWidget(self._home_button)
|
|
125
|
+
machine_row.addWidget(self._unlock_button)
|
|
126
|
+
machine_row.addWidget(self._cancel_button)
|
|
127
|
+
|
|
128
|
+
wcs_row = QHBoxLayout()
|
|
129
|
+
wcs_row.addWidget(QLabel("WCS"))
|
|
130
|
+
wcs_row.addWidget(self._wcs, 1)
|
|
131
|
+
|
|
132
|
+
self._notice = QLabel("")
|
|
133
|
+
self._notice.setObjectName("Muted")
|
|
134
|
+
|
|
135
|
+
layout = QVBoxLayout(self)
|
|
136
|
+
layout.setContentsMargins(10, 8, 10, 8)
|
|
137
|
+
layout.setSpacing(6)
|
|
138
|
+
layout.addWidget(header)
|
|
139
|
+
layout.addLayout(step_row)
|
|
140
|
+
layout.addLayout(pad_row)
|
|
141
|
+
layout.addLayout(zero_row)
|
|
142
|
+
layout.addLayout(wcs_row)
|
|
143
|
+
layout.addLayout(machine_row)
|
|
144
|
+
layout.addWidget(self._notice)
|
|
145
|
+
layout.addStretch(1)
|
|
146
|
+
|
|
147
|
+
self._connected = False
|
|
148
|
+
self._state = "Unknown"
|
|
149
|
+
self._job_active = False
|
|
150
|
+
self._apply_enabled()
|
|
151
|
+
|
|
152
|
+
# ------------------------------------------------------------------ #
|
|
153
|
+
def _jog(self, axis: str, direction: int) -> None:
|
|
154
|
+
step = float(self._step.currentText())
|
|
155
|
+
self.jogRequested.emit(axis, direction * step, self._feed.value())
|
|
156
|
+
|
|
157
|
+
def _zero_all(self) -> None:
|
|
158
|
+
for axis in ("X", "Y", "Z"):
|
|
159
|
+
self.zeroRequested.emit(axis)
|
|
160
|
+
|
|
161
|
+
def set_machine(self, status: MachineStatus, connected: bool, job_active: bool) -> None:
|
|
162
|
+
self._connected = connected
|
|
163
|
+
self._state = status.state
|
|
164
|
+
self._job_active = job_active
|
|
165
|
+
self._apply_enabled()
|
|
166
|
+
|
|
167
|
+
def set_wcs(self, index: int) -> None:
|
|
168
|
+
if 0 <= index < self._wcs.count():
|
|
169
|
+
self._wcs.setCurrentIndex(index)
|
|
170
|
+
self.wcsChanged.emit(int(self._wcs.itemData(index)))
|
|
171
|
+
|
|
172
|
+
def _apply_enabled(self) -> None:
|
|
173
|
+
family = state_family(self._state) if self._connected else "offline"
|
|
174
|
+
idle = self._connected and not self._job_active and family == "idle"
|
|
175
|
+
allowed = idle
|
|
176
|
+
for button in self._pad.values():
|
|
177
|
+
button.setEnabled(allowed)
|
|
178
|
+
self._z_up.setEnabled(allowed)
|
|
179
|
+
self._z_down.setEnabled(allowed)
|
|
180
|
+
self._step.setEnabled(allowed)
|
|
181
|
+
self._feed.setEnabled(allowed)
|
|
182
|
+
self._wcs.setEnabled(idle)
|
|
183
|
+
for button in self._zero_buttons:
|
|
184
|
+
button.setEnabled(idle)
|
|
185
|
+
self._home_button.setEnabled(
|
|
186
|
+
self._connected and not self._job_active and family in ("idle", "alarm")
|
|
187
|
+
)
|
|
188
|
+
self._unlock_button.setEnabled(self._connected and family == "alarm")
|
|
189
|
+
self._cancel_button.setEnabled(self._connected and family == "run")
|
|
190
|
+
if self._job_active:
|
|
191
|
+
self._notice.setText("Jog is disabled while a job is running.")
|
|
192
|
+
elif family == "alarm":
|
|
193
|
+
self._notice.setText("Alarm: unlock or home before jogging.")
|
|
194
|
+
elif not self._connected:
|
|
195
|
+
self._notice.setText("Connect a machine to jog.")
|
|
196
|
+
else:
|
|
197
|
+
self._notice.setText("")
|
|
198
|
+
|
|
199
|
+
def keyPressEvent(self, event: QKeyEvent) -> None: # noqa: N802
|
|
200
|
+
key = event.key()
|
|
201
|
+
mapping: dict[int, tuple[str, int]] = {
|
|
202
|
+
Qt.Key.Key_Up: ("Y", 1),
|
|
203
|
+
Qt.Key.Key_Down: ("Y", -1),
|
|
204
|
+
Qt.Key.Key_Left: ("X", -1),
|
|
205
|
+
Qt.Key.Key_Right: ("X", 1),
|
|
206
|
+
Qt.Key.Key_PageUp: ("Z", 1),
|
|
207
|
+
Qt.Key.Key_PageDown: ("Z", -1),
|
|
208
|
+
}
|
|
209
|
+
if key in mapping and self._pad["X+"].isEnabled():
|
|
210
|
+
axis, direction = mapping[key]
|
|
211
|
+
self._jog(axis, direction)
|
|
212
|
+
return
|
|
213
|
+
if key == Qt.Key.Key_BracketLeft:
|
|
214
|
+
self._step.setCurrentIndex(max(0, self._step.currentIndex() - 1))
|
|
215
|
+
return
|
|
216
|
+
if key == Qt.Key.Key_BracketRight:
|
|
217
|
+
self._step.setCurrentIndex(min(self._step.count() - 1, self._step.currentIndex() + 1))
|
|
218
|
+
return
|
|
219
|
+
if key == Qt.Key.Key_Space:
|
|
220
|
+
self.cancelRequested.emit()
|
|
221
|
+
return
|
|
222
|
+
super().keyPressEvent(event)
|