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.
@@ -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)