Skip to content

Commit 232fa42

Browse files
authored
Merge branch 'dev' into refactor/filament-tab-logic
2 parents 386b0ba + de5a95e commit 232fa42

6 files changed

Lines changed: 517 additions & 474 deletions

File tree

BlocksScreen/lib/moonrakerComm.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,10 @@ def on_message(self, *args) -> None:
280280
metadata=_entry,
281281
)
282282
elif "method" in response:
283-
if (
284-
str(response["method"]).lower() == "notify_klippy_disconnected"
285-
): # Checkout for notify_klippy_disconnect
283+
if str(response["method"]).lower() in (
284+
"notify_klippy_disconnected",
285+
"notify_klippy_shutdown",
286+
):
286287
self.evaluate_klippy_status()
287288

288289
message_event = (

BlocksScreen/lib/panels/controlTab.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class ControlTab(QtWidgets.QStackedWidget):
4040
disable_popups: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
4141
bool, name="disable-popups"
4242
)
43+
lock_ui: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
44+
bool, name="lock-ui"
45+
)
4346
request_numpad: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
4447
[str, int, "PyQt_PyObject"],
4548
[str, int, "PyQt_PyObject", int, int],
@@ -82,6 +85,7 @@ def __init__(
8285
self.probe_helper_page = ProbeHelper(self)
8386
self.probe_helper_page.toggle_conn_page.connect(self.toggle_conn_page)
8487
self.probe_helper_page.disable_popups.connect(self.disable_popups)
88+
self.probe_helper_page.lock_ui.connect(self.lock_ui)
8589
self.addWidget(self.probe_helper_page)
8690
self.probe_helper_page.call_load_panel.connect(self.call_load_panel)
8791
self.printcores_page = SwapPrintcorePage(self)

BlocksScreen/lib/panels/mainWindow.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@
3434

3535
_logger = logging.getLogger(__name__)
3636

37+
_GCODE_POPUP_MESSAGES: tuple[tuple[str, str], ...] = (
38+
("filament runout", "Filament Runout"),
39+
("no filament", "No Filament Detected"),
40+
("sensor not in valid range", "Eddy Current Sensor:\nnot in valid range"),
41+
)
42+
3743

3844
def api_handler(func):
3945
"""Decorator for methods that handle api responses"""
@@ -112,6 +118,7 @@ def __init__(self):
112118
self.ui.setupUi(self)
113119
self.screensaver = ScreenSaver(self)
114120
self._popup_toggle: bool = False
121+
self._klippy_ready: bool = False
115122
self.ui.main_content_widget.setCurrentIndex(0)
116123

117124
usb_config = self.config.get_section("usb_manager", fallback=None)
@@ -145,6 +152,7 @@ def __init__(self):
145152
self.conn_window.on_websocket_connection_achieved
146153
)
147154
self.ws.connection_lost.connect(self.conn_window.on_websocket_connection_lost)
155+
self.ws.klippy_state_signal.connect(self._on_klippy_state)
148156
self.printer.webhooks_update.connect(self.conn_window.webhook_update)
149157
self.printPanel.request_back.connect(slot=self.global_back)
150158
self.printPanel.on_cancel_print.connect(slot=self.on_cancel_print)
@@ -214,7 +222,11 @@ def __init__(self):
214222
self.handle_error_response.connect(
215223
self.controlPanel.probe_helper_page.handle_error_response
216224
)
225+
self.controlPanel.probe_helper_page.show_notifications.connect(
226+
self.notiPage.new_notication
227+
)
217228
self.controlPanel.disable_popups.connect(self.popup_toggle)
229+
self.controlPanel.lock_ui.connect(self.set_ui_lock)
218230
self.on_update_message.connect(self.update_page.handle_update_message)
219231
self.update_page.request_full_update.connect(self.ws.api.full_update)
220232
self.update_page.request_recover_repo[str].connect(
@@ -418,6 +430,24 @@ def popup_toggle(self, toggle: bool) -> None:
418430
"""Toggles app popups"""
419431
self._popup_toggle = toggle
420432

433+
@QtCore.pyqtSlot(bool, name="set-ui-lock")
434+
def set_ui_lock(self, locked: bool) -> None:
435+
"""Lock or unlock navigation during calibration.
436+
437+
Disables all tabs except controlTab (where calibration lives) and
438+
the header, so the user cannot navigate away mid-calibration.
439+
"""
440+
for tab in (self.ui.printTab, self.ui.filamentTab, self.ui.utilitiesTab):
441+
self.ui.main_content_widget.setTabEnabled(
442+
self.ui.main_content_widget.indexOf(tab), not locked
443+
)
444+
self.ui.header_main_layout.setEnabled(not locked)
445+
446+
@QtCore.pyqtSlot(str, name="on-klippy-state")
447+
def _on_klippy_state(self, state: str) -> None:
448+
"""Track Klippy readiness to suppress spurious error popups during disconnect."""
449+
self._klippy_ready = state == "ready"
450+
421451
def reset_tab_indexes(self):
422452
"""
423453
Used to grantee all tabs reset to their
@@ -717,10 +747,18 @@ def _handle_notify_gcode_response_message(self, method, data, metadata) -> None:
717747
if self._popup_toggle:
718748
return
719749
_gcode_msg_type, _message = str(_gcode_response[0]).split(" ", maxsplit=1)
720-
popupWhitelist = ["filament runout", "no filament"]
721-
if _message.lower() not in popupWhitelist or _gcode_msg_type != "!!":
750+
_msg_lower = _message.lower()
751+
_display = next(
752+
(
753+
fmt
754+
for pattern, fmt in _GCODE_POPUP_MESSAGES
755+
if pattern in _msg_lower
756+
),
757+
None,
758+
)
759+
if _gcode_msg_type != "!!" or _display is None:
722760
return
723-
self.show_notifications.emit("mainwindow", _message, 3, True)
761+
self.show_notifications.emit("mainwindow", _display, 3, True)
724762

725763
@api_handler
726764
def _handle_error_message(self, method, data, metadata) -> None:
@@ -729,6 +767,11 @@ def _handle_error_message(self, method, data, metadata) -> None:
729767
if self._popup_toggle:
730768
return
731769

770+
# Suppress error popups while Klippy is disconnected/shutting down.
771+
# Those errors are side-effects of the disconnect, not actionable by the user.
772+
if not self._klippy_ready:
773+
return
774+
732775
text = data.get("message", str(data)) if isinstance(data, dict) else str(data)
733776
lower_text = text.lower()
734777

@@ -880,4 +923,4 @@ def event(self, event: QtCore.QEvent) -> bool:
880923
def sizeHint(self) -> QtCore.QSize:
881924
"""Sets default size for the widget"""
882925
self.adjustSize()
883-
return super().sizeHint(QtCore.QSize(800, 480))
926+
return QtCore.QSize(800, 480)

BlocksScreen/lib/panels/printTab.py

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,12 @@ class PrintTab(QtWidgets.QStackedWidget):
6666
on_cancel_print: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
6767
name="on_cancel_print"
6868
)
69-
call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel")
70-
71-
call_cancel_panel = QtCore.pyqtSignal(bool, name="call-load-panel")
69+
call_load_panel: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
70+
bool, str, name="call-load-panel"
71+
)
72+
call_cancel_panel: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
73+
bool, name="call-load-panel"
74+
)
7275

7376
def __init__(
7477
self,
@@ -79,7 +82,9 @@ def __init__(
7982
) -> None:
8083
super().__init__(parent)
8184
self._active_z_offset: float = 0.0
85+
self._pending_save_offset: float = 0.0
8286
self._finish_print_handled: bool = False
87+
self._cancel_z_snapshot: float = 0.0
8388
self._z_apply_command: str = "Z_OFFSET_APPLY_ENDSTOP"
8489

8590
self.setupMainPrintPage()
@@ -231,6 +236,9 @@ def __init__(
231236
self.printer.gcode_move_update[str, list].connect(
232237
self.babystepPage.on_gcode_move_update
233238
)
239+
self.printer.print_stats_update[str, str].connect(
240+
self.babystepPage.on_print_state_update
241+
)
234242
self.printer.gcode_move_update[str, list].connect(self.activate_save_button)
235243
self.tune_page.run_gcode.connect(self.ws.api.run_gcode)
236244
self.tune_page.request_sliderPage[str, int, "PyQt_PyObject"].connect(
@@ -275,6 +283,7 @@ def __init__(
275283
self.confirmPage_widget.on_delete.connect(self.delete_file)
276284
self.change_page(self.indexOf(self.print_page)) # force set the initial page
277285
self.save_config_btn.clicked.connect(self.save_config)
286+
self.ws.klippy_state_signal.connect(self.on_klippy_state)
278287

279288
@QtCore.pyqtSlot(str, dict, name="on_print_stats_update")
280289
@QtCore.pyqtSlot(str, float, name="on_print_stats_update")
@@ -283,10 +292,14 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None:
283292
"""
284293
unblocks tabs if on standby
285294
"""
286-
if isinstance(value, str):
287-
if "state" in field:
288-
if value in ("standby"):
289-
self.on_cancel_print.emit()
295+
if isinstance(value, str) and "state" in field and value == "standby":
296+
self.on_cancel_print.emit()
297+
if not self._finish_print_handled and self._cancel_z_snapshot != 0:
298+
self._active_z_offset = self._cancel_z_snapshot
299+
self.save_config()
300+
self._finish_print_handled = True
301+
self.save_config_btn.setVisible(True)
302+
self._cancel_z_snapshot = 0.0
290303

291304
@QtCore.pyqtSlot(str, int, "PyQt_PyObject", name="on_numpad_request")
292305
@QtCore.pyqtSlot(str, int, "PyQt_PyObject", int, int, name="on_numpad_request")
@@ -299,6 +312,10 @@ def on_numpad_request(
299312
max_value: int = 100,
300313
) -> None:
301314
"""Handle numpad request"""
315+
try:
316+
self.numpadPage.value_selected.disconnect()
317+
except (RuntimeError, TypeError):
318+
pass
302319
self.numpadPage.value_selected.connect(callback)
303320
self.numpadPage.set_name(name)
304321
self.numpadPage.set_value(current_value)
@@ -318,6 +335,10 @@ def on_slidePage_request(
318335
max_value: int = 100,
319336
) -> None:
320337
"""Handle slider page request"""
338+
try:
339+
self.sliderPage.value_selected.disconnect()
340+
except (RuntimeError, TypeError):
341+
pass
321342
self.sliderPage.value_selected.connect(callback)
322343
self.sliderPage.set_name(name)
323344
self.sliderPage.set_slider_position(int(current_value))
@@ -341,12 +362,9 @@ def delete_file(self, filename: str, directory: str = "gcodes") -> None:
341362

342363
def save_config(self) -> None:
343364
"""Handle Save configuration behaviour, shows confirmation dialog"""
344-
345-
self.babystepPage.bbp_z_offset_title_label.setText(
346-
f"Z: {self._active_z_offset:.3f}mm"
347-
)
365+
self._pending_save_offset = self._active_z_offset
348366
self.BasePopup_z_offset.set_message(
349-
f"The Z-Offset is now {self._active_z_offset:.3f} mm.\n"
367+
f"The Z-Offset is now {self._pending_save_offset + 0.0:.3f} mm.\n"
350368
"Would you like to save this change permanently?\n"
351369
"The machine will restart."
352370
)
@@ -359,28 +377,37 @@ def save_config(self) -> None:
359377
self.BasePopup_z_offset.open()
360378

361379
def update_configuration_file(self) -> None:
362-
"""Restore the captured offset, apply it to the probe config, then save."""
380+
"""Runs the `SAVE_CONFIG` gcode"""
363381
try:
364382
self.BasePopup_z_offset.accepted.disconnect(self.update_configuration_file)
365383
except (RuntimeError, TypeError):
366384
pass
367385
self.run_gcode_signal.emit(
368-
f"SET_GCODE_OFFSET Z={self._active_z_offset:.3f} MOVE=0"
386+
f"SET_GCODE_OFFSET Z={self._pending_save_offset:.3f} MOVE=0"
369387
)
370388
self.run_gcode_signal.emit(self._z_apply_command)
371389
self.run_gcode_signal.emit("SAVE_CONFIG")
390+
self.babystepPage.bbp_z_offset_title_label.setText(
391+
f"Z: {self._pending_save_offset + 0.0:.3f}mm"
392+
)
372393
self.save_config_btn.setVisible(False)
373394

395+
@QtCore.pyqtSlot(str, name="on_klippy_state")
396+
def on_klippy_state(self, state: str) -> None:
397+
"""Dismiss the Z-offset save popup and reset save state on unexpected shutdown."""
398+
if state in ("ready", "startup"):
399+
return
400+
self.BasePopup_z_offset.reject()
401+
self.save_config_btn.setVisible(False)
402+
self.babystepPage.baby_stepchange = False
403+
374404
@QtCore.pyqtSlot(str, list, name="activate_save_button")
375405
def activate_save_button(self, name: str, value: list) -> None:
376406
"""Sync the `Save config` popup with the save_config_pending state"""
377-
if not value:
407+
if not value or name != "homing_origin" or len(value) <= 2:
378408
return
379-
380-
if name == "homing_origin":
381-
if len(value) > 2:
382-
self._active_z_offset = value[2]
383-
self.save_config_btn.setVisible(value[2] != 0)
409+
self._active_z_offset = value[2]
410+
self.save_config_btn.setVisible(round(value[2], 3) != 0)
384411

385412
def _on_delete_file_confirmed(self, filename: str, directory: str) -> None:
386413
"""Handle confirmed file deletion after user accepted the dialog."""
@@ -408,6 +435,12 @@ def setProperty(self, name: str, value: typing.Any) -> bool:
408435

409436
def handle_cancel_print(self) -> None:
410437
"""Handles the print cancel action"""
438+
if (
439+
not self._finish_print_handled
440+
and self._active_z_offset != 0
441+
and self.babystepPage.baby_stepchange
442+
):
443+
self._cancel_z_snapshot = self._active_z_offset
411444
self.ws.api.cancel_print()
412445
self.call_load_panel.emit(True, "Cancelling print...\nPlease wait")
413446

@@ -429,6 +462,7 @@ def klipper_ready_signal(self) -> None:
429462
"""React to klipper ready signal"""
430463
self.babystepPage.baby_stepchange = False
431464
self._finish_print_handled = False
465+
self._cancel_z_snapshot = 0.0
432466
self.printer.on_subscribe_config("stepper_z", self._on_stepper_z_config)
433467

434468
def _on_stepper_z_config(self, config: dict | list) -> None:
@@ -449,6 +483,7 @@ def finish_print_signal(self) -> None:
449483
if self._active_z_offset != 0 and self.babystepPage.baby_stepchange:
450484
self.save_config()
451485
self._finish_print_handled = True
486+
self.save_config_btn.setVisible(round(self._active_z_offset, 3) != 0)
452487

453488
def setupMainPrintPage(self) -> None:
454489
"""Setup UI for print page"""

0 commit comments

Comments
 (0)