diff --git a/BlocksScreen.cfg b/BlocksScreen.cfg
index 42ab150b..065a900b 100644
--- a/BlocksScreen.cfg
+++ b/BlocksScreen.cfg
@@ -7,3 +7,7 @@ timeout: 5000
[usb_manager]
gcodes_dir: ~/printer_data/gcodes/
+
+[filament_presence]
+object: cutter_sensor
+name: extruder_cutter
\ No newline at end of file
diff --git a/BlocksScreen/lib/moonrakerComm.py b/BlocksScreen/lib/moonrakerComm.py
index 5f889d9f..73920e72 100644
--- a/BlocksScreen/lib/moonrakerComm.py
+++ b/BlocksScreen/lib/moonrakerComm.py
@@ -280,9 +280,10 @@ def on_message(self, *args) -> None:
metadata=_entry,
)
elif "method" in response:
- if (
- str(response["method"]).lower() == "notify_klippy_disconnected"
- ): # Checkout for notify_klippy_disconnect
+ if str(response["method"]).lower() in (
+ "notify_klippy_disconnected",
+ "notify_klippy_shutdown",
+ ):
self.evaluate_klippy_status()
message_event = (
diff --git a/BlocksScreen/lib/panels/controlTab.py b/BlocksScreen/lib/panels/controlTab.py
index be84c8bc..bf9c5f64 100644
--- a/BlocksScreen/lib/panels/controlTab.py
+++ b/BlocksScreen/lib/panels/controlTab.py
@@ -1,8 +1,8 @@
from __future__ import annotations
-
import re
import typing
from functools import partial
+import logging
from helper_methods import normalize
from lib.moonrakerComm import MoonWebSocket
@@ -18,6 +18,9 @@
from PyQt6 import QtCore, QtGui, QtWidgets
+_logger = logging.getLogger(__name__)
+
+
class ControlTab(QtWidgets.QStackedWidget):
"""Printer Control Stacked Widget"""
@@ -37,6 +40,9 @@ class ControlTab(QtWidgets.QStackedWidget):
disable_popups: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
bool, name="disable-popups"
)
+ lock_ui: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ bool, name="lock-ui"
+ )
request_numpad: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
[str, int, "PyQt_PyObject"],
[str, int, "PyQt_PyObject", int, int],
@@ -79,6 +85,7 @@ def __init__(
self.probe_helper_page = ProbeHelper(self)
self.probe_helper_page.toggle_conn_page.connect(self.toggle_conn_page)
self.probe_helper_page.disable_popups.connect(self.disable_popups)
+ self.probe_helper_page.lock_ui.connect(self.lock_ui)
self.addWidget(self.probe_helper_page)
self.probe_helper_page.call_load_panel.connect(self.call_load_panel)
self.printcores_page = SwapPrintcorePage(self)
@@ -252,24 +259,6 @@ def __init__(
self.numpadPage.request_back.connect(self.request_back_button)
self.addWidget(self.numpadPage)
- self.panel.extruder_temp_display.clicked.connect(
- lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit(
- "Extruder Temperature",
- int(round(float(self.panel.extruder_temp_display.secondary_text))),
- self.on_numpad_change,
- 0,
- 370, # TODO: Get this value from printer objects
- )
- )
- self.panel.bed_temp_display.clicked.connect(
- lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit(
- "Bed Temperature",
- int(round(float(self.panel.bed_temp_display.secondary_text))),
- self.on_numpad_change,
- 0,
- 120, # TODO: Get this value from printer objects
- )
- )
self.request_numpad[str, int, "PyQt_PyObject", int, int].connect(
self.on_numpad_request
)
@@ -304,6 +293,8 @@ def __init__(
self.printer.fan_update[str, str, float].connect(self.on_fan_object_update)
self.printer.fan_update[str, str, int].connect(self.on_fan_object_update)
+ self.printer.printer_config.connect(self.on_printer_config)
+
def _handle_z_tilt_object_update(self, value, state):
if state:
self.call_load_panel.emit(False, "")
@@ -470,6 +461,44 @@ def _handle_gcode_response(self, messages: list):
f"Retries: {retries_done}/{retries_total} | Range: {probed_range:.6f} | Tolerance: {tolerance:.6f}",
)
+ @QtCore.pyqtSlot(dict, name="printer_config")
+ def on_printer_config(self, config: dict) -> None:
+ """Slot that receives the full printer configuration,
+
+ Additionally, this method configures the signal connections
+ between controllable heaters and numpad calls
+ """
+ try:
+ self.panel.extruder_temp_display.clicked.disconnect()
+ self.panel.bed_temp_display.clicked.disconnect()
+ except Exception:
+ _logger.debug("Signals were not connected")
+ extruder = config.get("extruder", None) or {}
+ bed = config.get("heater_bed", None) or {}
+ e_min_temp = extruder.get("min_temp", 0)
+ e_max_temp = extruder.get("max_temp", 300)
+ b_max_temp = bed.get("max_temp", 100)
+ b_min_temp = bed.get("min_temp", 0)
+ # Configure numpads
+ self.panel.extruder_temp_display.clicked.connect(
+ lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit(
+ "Extruder Temperature",
+ int(round(float(self.panel.extruder_temp_display.secondary_text))),
+ self.on_numpad_change,
+ int(e_min_temp),
+ int(e_max_temp),
+ )
+ )
+ self.panel.bed_temp_display.clicked.connect(
+ lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit(
+ "Bed Temperature",
+ int(round(float(self.panel.bed_temp_display.secondary_text))),
+ self.on_numpad_change,
+ int(b_min_temp),
+ int(b_max_temp),
+ )
+ )
+
def handle_ztilt(self):
"""Handle Z-Tilt Adjustment"""
self.call_load_panel.emit(True, "Please wait, performing Z-axis calibration.")
diff --git a/BlocksScreen/lib/panels/filamentTab.py b/BlocksScreen/lib/panels/filamentTab.py
index 04fc5ef4..06c5d23b 100644
--- a/BlocksScreen/lib/panels/filamentTab.py
+++ b/BlocksScreen/lib/panels/filamentTab.py
@@ -1,27 +1,36 @@
import enum
from functools import partial
-
+import logging
from lib.printer import Printer
from lib.filament import Filament
from lib.ui.filamentStackedWidget_ui import Ui_filamentStackedWidget
-
from lib.panels.widgets.popupDialogWidget import Popup
from PyQt6 import QtCore, QtGui, QtWidgets
+logger = logging.getLogger(__name__)
+
+
+class FilamentTypes(enum.Enum):
+ PLA = Filament(name="PLA", temperature=220)
+ PETG = Filament(name="PETG", temperature=240)
+ ABS = Filament(name="ABS", temperature=250)
+ HIPS = Filament(name="HIPS", temperature=250)
+ NYLON = Filament(name="NYLON", temperature=270)
+ TPU = Filament(name="TPU", temperature=230)
+ UNKNOWN = Filament(name="UNKNOWN", temperature=250)
+
class FilamentTab(QtWidgets.QStackedWidget):
request_filament_change_page = QtCore.pyqtSignal(name="filament_change_page")
request_filament_load = QtCore.pyqtSignal(name="filament_load_t1")
request_back = QtCore.pyqtSignal(name="request_back")
request_change_page = QtCore.pyqtSignal(int, int, name="request_change_page")
+ request_change_tab = QtCore.pyqtSignal(int, name="request_change_tab")
request_toolhead_count = QtCore.pyqtSignal(int, name="toolhead_number_received")
run_gcode = QtCore.pyqtSignal(str, name="run_gcode")
call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel")
- class FilamentTypes(enum.Enum):
- PLA = Filament(name="PLA", temperature=220)
-
class FilamentStates(enum.Enum):
LOADED = enum.auto()
UNLOADED = enum.auto()
@@ -30,7 +39,7 @@ class FilamentStates(enum.Enum):
def __repr__(self) -> str:
return "<%s.%s>" % (self.__class__.__name__, self._name_)
- def __init__(self, parent: QtWidgets.QWidget, printer: Printer, ws, /) -> None:
+ def __init__(self, parent, printer: Printer, ws, config, /) -> None:
super().__init__(parent)
self.panel = Ui_filamentStackedWidget()
self.panel.setupUi(self)
@@ -43,8 +52,14 @@ def __init__(self, parent: QtWidgets.QWidget, printer: Printer, ws, /) -> None:
self.popup = Popup(self)
self.has_load_unload_objects = None
self._filament_state = self.FilamentStates.UNKNOWN
- self._sensor_states = {}
- self.filament_type: Filament | None = None
+ self.filament_type = FilamentTypes.UNKNOWN
+
+ cfg = config
+ if cfg.has_section("filament_presence"):
+ i = cfg.get_section("filament_presence", None)
+ self.filament_sensor = i.get("name", str, None)
+ else:
+ self.filament_sensor = None
self.panel.filament_page_load_btn.clicked.connect(
partial(self.change_page, self.indexOf(self.panel.load_page))
)
@@ -52,28 +67,30 @@ def __init__(self, parent: QtWidgets.QWidget, printer: Printer, ws, /) -> None:
self.panel.load_custom_btn.hide()
self.panel.load_header_back_button.clicked.connect(self.back_button)
self.panel.load_pla_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=220)
+ partial(self.load_filament, toolhead=0, filament=FilamentTypes.PLA)
)
self.panel.load_petg_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=240)
+ partial(self.load_filament, toolhead=0, filament=FilamentTypes.PETG)
)
self.panel.load_abs_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=250)
+ partial(self.load_filament, toolhead=0, filament=FilamentTypes.ABS)
)
self.panel.load_hips_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=250)
+ partial(self.load_filament, toolhead=0, filament=FilamentTypes.HIPS)
)
self.panel.load_nylon_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=270)
+ partial(self.load_filament, toolhead=0, filament=FilamentTypes.NYLON)
)
self.panel.load_tpu_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=230)
+ partial(self.load_filament, toolhead=0, filament=FilamentTypes.TPU)
)
self.panel.filament_page_unload_btn.clicked.connect(
lambda: self.unload_filament(toolhead=0, temp=250)
)
+ self.panel.main_back_button.clicked.connect(
+ lambda: self.request_change_tab.emit(0)
+ )
self.run_gcode.connect(self.ws.api.run_gcode)
- self.printer.extruder_update.connect(self.on_extruder_update)
self.printer.unload_filament_update.connect(self.on_unload_filament)
self.printer.load_filament_update.connect(self.on_load_filament)
self.printer.filament_switch_sensor_update.connect(
@@ -84,8 +101,18 @@ def __init__(self, parent: QtWidgets.QWidget, printer: Printer, ws, /) -> None:
self.printer.print_stats_update[str, dict].connect(self.on_print_stats_update)
self.printer.print_stats_update[str, float].connect(self.on_print_stats_update)
- self.loadignore = True
- self.unloadignore = True
+ self.printer.save_variables_update.connect(self.on_save_variables_update)
+ self.state = "standby"
+
+ def on_save_variables_update(self, save_variables: dict):
+ """Handle query response"""
+ for i in FilamentTypes:
+ if i.value.name in save_variables["variables"]["filament_type"]:
+ self.filament_type = i
+ break
+ else:
+ self.filament_type = FilamentTypes.UNKNOWN
+ self.panel.label_2.setText(self.filament_type.value.name)
@QtCore.pyqtSlot(str, dict, name="on_print_stats_update")
@QtCore.pyqtSlot(str, float, name="on_print_stats_update")
@@ -94,9 +121,23 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None:
"""Handle print stats object update"""
if isinstance(value, str):
if "state" in field:
+ self.state = value
+ if value in ("printing", "pausing", "paused", "resuming"):
+ self.panel.main_back_button.show()
+ self.panel.spacerItem1.changeSize(
+ 60,
+ 0,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ )
if value in ("standby"):
- self.loadignore = True
- self.unloadignore = True
+ self.panel.main_back_button.hide()
+ self.panel.spacerItem1.changeSize(
+ 0,
+ 0,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ )
@QtCore.pyqtSlot(str, str, bool, name="on_filament_sensor_update")
def on_filament_sensor_update(self, sensor_name: str, parameter: str, value: bool):
@@ -106,78 +147,46 @@ def on_filament_sensor_update(self, sensor_name: str, parameter: str, value: boo
self._filament_state = self.FilamentStates.UNKNOWN
self.handle_filament_state()
return
- self._sensor_states[sensor_name] = value
- if not self._sensor_states:
- new_state = self.FilamentStates.UNKNOWN
- elif all(self._sensor_states.values()):
- new_state = self.FilamentStates.LOADED
- else:
- new_state = self.FilamentStates.UNLOADED
- if self._filament_state != new_state:
- self._filament_state = new_state
- self.handle_filament_state()
+ if sensor_name == self.filament_sensor:
+ if value:
+ self._filament_state = self.FilamentStates.LOADED
+ else:
+ self._filament_state = self.FilamentStates.UNLOADED
+ return
+ self.handle_filament_state()
- @QtCore.pyqtSlot(str, str, float, name="on_extruder_update")
- def on_extruder_update(
- self, extruder_name: str, field: str, new_value: float
- ) -> None:
- """Handle extruder update"""
- if not self.isVisible:
- return
- if not self.loadignore or not self.unloadignore:
- if self.target_temp != 0:
- if self.current_temp == self.target_temp:
- if self.isVisible:
- self.call_load_panel.emit(
- True, "Extruder heated up \n Please wait"
- )
- return
- if field == "temperature":
- self.current_temp = round(new_value, 0)
- if self.isVisible:
- self.call_load_panel.emit(
- True,
- f"Heating up ({new_value}/{self.target_temp}) \n Please wait",
- )
- if field == "target":
- self.target_temp = round(new_value, 0)
- if self.isVisible:
- self.call_load_panel.emit(True, "Heating up \n Please wait")
-
- @QtCore.pyqtSlot(bool, name="on_load_filament")
- def on_load_filament(self, status: bool):
+ @QtCore.pyqtSlot(dict, name="on_load_filament")
+ def on_load_filament(self, status: dict):
"""Handle load filament object updated"""
- if not self.isVisible:
- return
- if self.loadignore:
- return
- if status:
- self.call_load_panel.emit(True, "Loading Filament")
- else:
- self.loadignore = True
- self.target_temp = 0
- self.call_load_panel.emit(False, "")
- self._filament_state = self.FilamentStates.LOADED
+ if "state" in status.keys():
+ if not status["state"]:
+ self.target_temp = 0
+ self.call_load_panel.emit(False, "")
+ if self.state == "paused":
+ self.request_change_tab.emit(0)
+ return
+ self.call_load_panel.emit(
+ True, f"Loading Filament\n{status['step'].capitalize()}"
+ )
self.handle_filament_state()
- @QtCore.pyqtSlot(bool, name="on_unload_filament")
- def on_unload_filament(self, status: bool):
+ @QtCore.pyqtSlot(dict, name="on_unload_filament")
+ def on_unload_filament(self, status: dict):
"""Handle unload filament object updated"""
- if not self.isVisible:
- return
- if self.unloadignore:
- return
- if status:
- self.call_load_panel.emit(True, "Unloading Filament")
- else:
- self.unloadignore = True
- self.call_load_panel.emit(False, "")
- self.target_temp = 0
- self._filament_state = self.FilamentStates.UNLOADED
+ if "state" in status.keys():
+ if not status["state"]:
+ self.target_temp = 0
+ self.call_load_panel.emit(False, "")
+ return
+ self.call_load_panel.emit(
+ True, f"Unloading Filament\n{status['step'].capitalize()}"
+ )
self.handle_filament_state()
@QtCore.pyqtSlot(int, int, name="load_filament")
- def load_filament(self, toolhead: int = 0, temp: int = 220) -> None:
+ def load_filament(
+ self, toolhead: int = 0, filament: FilamentTypes = FilamentTypes.UNKNOWN
+ ) -> None:
"""Handle load filament buttons clicked"""
if not self.isVisible:
return
@@ -194,9 +203,11 @@ def load_filament(self, toolhead: int = 0, temp: int = 220) -> None:
message="Filament is already loaded.",
)
return
- self.loadignore = False
self.call_load_panel.emit(True, "Loading Filament")
- self.run_gcode.emit(f"LOAD_FILAMENT TOOLHEAD=load_toolhead TEMPERATURE={temp}")
+ self.run_gcode.emit(
+ f"""SAVE_VARIABLE VARIABLE=filament_type VALUE='"{filament.value.name}"'"""
+ )
+ self.run_gcode.emit(f"LOAD_FILAMENT TEMPERATURE={filament.value.temperature}")
@QtCore.pyqtSlot(str, int, name="unload_filament")
def unload_filament(self, toolhead: int = 0, temp: int = 220) -> None:
@@ -218,21 +229,23 @@ def unload_filament(self, toolhead: int = 0, temp: int = 220) -> None:
return
self.find_routine_objects()
- self.unloadignore = False
self.call_load_panel.emit(True, "Unloading Filament")
+ self.run_gcode.emit(
+ f"""SAVE_VARIABLE VARIABLE=filament_type VALUE='"{FilamentTypes.UNKNOWN.value.name}"'"""
+ )
self.run_gcode.emit(f"UNLOAD_FILAMENT TEMPERATURE={temp}")
def handle_filament_state(self):
"""Handle ui changes on filament states"""
if self._filament_state == self.FilamentStates.LOADED:
- self.panel.filament_page_load_btn.setDisabled(True)
- self.panel.filament_page_load_btn.setDisabled(False)
+ self.panel.filament_page_unload_btn.setEnabled(True)
+ self.panel.filament_page_load_btn.setEnabled(False)
elif self._filament_state == self.FilamentStates.UNLOADED:
- self.panel.filament_page_unload_btn.setDisabled(True)
- self.panel.filament_page_unload_btn.setDisabled(False)
+ self.panel.filament_page_unload_btn.setEnabled(False)
+ self.panel.filament_page_load_btn.setEnabled(True)
else:
- self.panel.filament_page_load_btn.setDisabled(False)
- self.panel.filament_page_unload_btn.setDisabled(False)
+ self.panel.filament_page_load_btn.setEnabled(True)
+ self.panel.filament_page_unload_btn.setEnabled(True)
@property
def filament_state(self):
diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py
index 12fbc628..1a84effe 100644
--- a/BlocksScreen/lib/panels/mainWindow.py
+++ b/BlocksScreen/lib/panels/mainWindow.py
@@ -34,6 +34,12 @@
_logger = logging.getLogger(__name__)
+_GCODE_POPUP_MESSAGES: tuple[tuple[str, str], ...] = (
+ ("filament runout", "Filament Runout"),
+ ("no filament", "No Filament Detected"),
+ ("sensor not in valid range", "Eddy Current Sensor:\nnot in valid range"),
+)
+
def api_handler(func):
"""Decorator for methods that handle api responses"""
@@ -112,6 +118,7 @@ def __init__(self):
self.ui.setupUi(self)
self.screensaver = ScreenSaver(self)
self._popup_toggle: bool = False
+ self._klippy_ready: bool = False
self.ui.main_content_widget.setCurrentIndex(0)
usb_config = self.config.get_section("usb_manager", fallback=None)
@@ -135,7 +142,9 @@ def __init__(self):
self.ui.printTab, self.file_data, self.ws, self.printer
)
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.CursorShape.BlankCursor)
- self.filamentPanel = FilamentTab(self.ui.filamentTab, self.printer, self.ws)
+ self.filamentPanel = FilamentTab(
+ self.ui.filamentTab, self.printer, self.ws, self.config
+ )
self.controlPanel = ControlTab(self.ui.controlTab, self.ws, self.printer)
self.utilitiesPanel = UtilitiesTab(self.ui.utilitiesTab, self.ws, self.printer)
self.networkPanel = NetworkControlWindow(self)
@@ -145,6 +154,7 @@ def __init__(self):
self.conn_window.on_websocket_connection_achieved
)
self.ws.connection_lost.connect(self.conn_window.on_websocket_connection_lost)
+ self.ws.klippy_state_signal.connect(self._on_klippy_state)
self.printer.webhooks_update.connect(self.conn_window.webhook_update)
self.printPanel.request_back.connect(slot=self.global_back)
self.printPanel.on_cancel_print.connect(slot=self.on_cancel_print)
@@ -175,7 +185,7 @@ def __init__(self):
self.ui.filament_type_icon.clicked.connect(
lambda: self.global_change_page(
self.ui.main_content_widget.indexOf(self.ui.filamentTab),
- self.filamentPanel.indexOf(self.filamentPanel.panel.load_page),
+ self.filamentPanel.indexOf(self.filamentPanel),
)
)
self.ui.filament_type_icon.setText("PLA")
@@ -214,7 +224,11 @@ def __init__(self):
self.handle_error_response.connect(
self.controlPanel.probe_helper_page.handle_error_response
)
+ self.controlPanel.probe_helper_page.show_notifications.connect(
+ self.notiPage.new_notication
+ )
self.controlPanel.disable_popups.connect(self.popup_toggle)
+ self.controlPanel.lock_ui.connect(self.set_ui_lock)
self.on_update_message.connect(self.update_page.handle_update_message)
self.update_page.request_full_update.connect(self.ws.api.full_update)
self.update_page.request_recover_repo[str].connect(
@@ -241,11 +255,14 @@ def __init__(self):
self.ui.extruder_temp_display.display_format = "upper_downer"
self.ui.bed_temp_display.display_format = "upper_downer"
- self.controlPanel.call_load_panel.connect(self.show_LoadScreen)
- self.filamentPanel.call_load_panel.connect(self.show_LoadScreen)
- self.printPanel.call_load_panel.connect(self.show_LoadScreen)
- self.utilitiesPanel.call_load_panel.connect(self.show_LoadScreen)
- self.conn_window.call_load_panel.connect(self.show_LoadScreen)
+ self.controlPanel.call_load_panel.connect(self.show_loadscreen)
+ self.filamentPanel.call_load_panel.connect(self.show_loadscreen)
+ self.printPanel.call_load_panel.connect(self.show_loadscreen)
+ self.utilitiesPanel.call_load_panel.connect(self.show_loadscreen)
+ self.conn_window.call_load_panel.connect(self.show_loadscreen)
+
+ self.filamentPanel.request_change_tab.connect(self.global_change_tab)
+ self.printPanel.request_change_tab.connect(self.global_change_tab)
self.loadscreen = BasePopup(self, floating=False, dialog=False)
self.loadwidget = LoadingOverlayWidget(
@@ -268,6 +285,8 @@ def __init__(self):
self.file_data.fileinfo.connect(self.cancelpage._show_screen_thumbnail)
self.printPanel.call_cancel_panel.connect(self.handle_cancel_print)
+ self.print_status = "idle"
+
if self.config.has_section("server"):
self.bo_ws_startup.emit()
self.reset_tab_indexes()
@@ -286,13 +305,9 @@ def handle_cancel_print(self, show: bool = True):
self.cancelpage.show()
@QtCore.pyqtSlot(bool, str, name="show-load-page")
- def show_LoadScreen(self, show: bool = True, msg: str = ""):
+ def show_loadscreen(self, show: bool = True, msg: str = ""):
"""Show or hide the loading overlay, guarded by the calling panel's visibility."""
_sender = self.sender()
-
- if _sender == self.filamentPanel:
- if not self.filamentPanel.isVisible():
- return
if _sender == self.controlPanel:
if not self.controlPanel.isVisible():
return
@@ -302,7 +317,6 @@ def show_LoadScreen(self, show: bool = True, msg: str = ""):
if _sender == self.utilitiesPanel:
if not self.utilitiesPanel.isVisible():
return
-
self.loadwidget.set_status_message(msg)
if show:
self.loadscreen.show()
@@ -336,8 +350,6 @@ def on_cancel_print(self):
self.enable_tab_bar()
self.ui.extruder_temp_display.clicked.disconnect()
self.ui.bed_temp_display.clicked.disconnect()
- self.ui.filament_type_icon.setDisabled(False)
- self.ui.nozzle_size_icon.setDisabled(False)
self.ui.extruder_temp_display.clicked.connect(
lambda: self.global_change_page(
self.ui.main_content_widget.indexOf(self.ui.controlTab),
@@ -366,9 +378,6 @@ def enable_tab_bar(self) -> bool:
bool: True if the TabBar was disabled
"""
- self.ui.main_content_widget.setTabEnabled(
- self.ui.main_content_widget.indexOf(self.ui.filamentTab), True
- )
self.ui.main_content_widget.setTabEnabled(
self.ui.main_content_widget.indexOf(self.ui.controlTab), True
)
@@ -378,9 +387,6 @@ def enable_tab_bar(self) -> bool:
self.ui.header_main_layout.setEnabled(True)
return all(
[
- not self.ui.main_content_widget.isTabEnabled(
- self.ui.main_content_widget.indexOf(self.ui.filamentTab)
- ),
not self.ui.main_content_widget.isTabEnabled(
self.ui.main_content_widget.indexOf(self.ui.controlTab)
),
@@ -402,9 +408,6 @@ def disable_tab_bar(self) -> bool:
Returns:
boolean: True if the TabBar was disabled
"""
- self.ui.main_content_widget.setTabEnabled(
- self.ui.main_content_widget.indexOf(self.ui.filamentTab), False
- )
self.ui.main_content_widget.setTabEnabled(
self.ui.main_content_widget.indexOf(self.ui.controlTab), False
)
@@ -414,9 +417,6 @@ def disable_tab_bar(self) -> bool:
self.ui.header_main_layout.setEnabled(False)
return all(
[
- not self.ui.main_content_widget.isTabEnabled(
- self.ui.main_content_widget.indexOf(self.ui.filamentTab)
- ),
not self.ui.main_content_widget.isTabEnabled(
self.ui.main_content_widget.indexOf(self.ui.controlTab)
),
@@ -432,17 +432,41 @@ def popup_toggle(self, toggle: bool) -> None:
"""Toggles app popups"""
self._popup_toggle = toggle
+ @QtCore.pyqtSlot(bool, name="set-ui-lock")
+ def set_ui_lock(self, locked: bool) -> None:
+ """Lock or unlock navigation during calibration.
+
+ Disables all tabs except controlTab (where calibration lives) and
+ the header, so the user cannot navigate away mid-calibration.
+ """
+ for tab in (self.ui.printTab, self.ui.filamentTab, self.ui.utilitiesTab):
+ self.ui.main_content_widget.setTabEnabled(
+ self.ui.main_content_widget.indexOf(tab), not locked
+ )
+ self.ui.header_main_layout.setEnabled(not locked)
+
+ @QtCore.pyqtSlot(str, name="on-klippy-state")
+ def _on_klippy_state(self, state: str) -> None:
+ """Track Klippy readiness to suppress spurious error popups during disconnect."""
+ self._klippy_ready = state == "ready"
+
def reset_tab_indexes(self):
"""
Used to grantee all tabs reset to their
first page once the user leaves the tab
"""
- self.update_page.hide()
- self.printPanel.setCurrentIndex(0)
self.filamentPanel.setCurrentIndex(0)
+
+ if self.print_status == "printing":
+ self.printPanel.setCurrentIndex(
+ self.printPanel.indexOf(self.printPanel.jobStatusPage_widget)
+ )
+ return
+ self.printPanel.setCurrentIndex(0)
self.controlPanel.setCurrentIndex(0)
self.utilitiesPanel.setCurrentIndex(0)
self.networkPanel.setCurrentIndex(0)
+ self.update_page.hide()
def current_panel_index(self) -> int:
"""Helper function to get the index of the current page in the current tab
@@ -503,7 +527,7 @@ def global_change_page(self, tab_index: int, panel_index: int) -> None:
"Panel page index expected type int, %s", str(type(panel_index))
)
- self.show_LoadScreen(False)
+ self.show_loadscreen(False)
current_page = [
self.ui.main_content_widget.currentIndex(),
self.current_panel_index(),
@@ -519,6 +543,22 @@ def global_change_page(self, tab_index: int, panel_index: int) -> None:
f"Requested page change -> Tab index : {requested_page[0]} | panel index : {requested_page[1]}",
)
+ def global_change_tab(self, tab_index: int) -> None:
+ """Changes the current tab while keeping the current panel page index if possible
+
+ Args:
+ tab_index (int): The index of the tab to change to
+ """
+ if not isinstance(tab_index, int):
+ _logger.debug(
+ "Tab index argument expected type int, got %s", str(type(tab_index))
+ )
+ return
+ self.ui.main_content_widget.setCurrentIndex(tab_index)
+ _logger.debug(
+ f"Requested tab change -> Tab index : {tab_index}",
+ )
+
@QtCore.pyqtSlot(name="request-back")
def global_back(self) -> None:
"""Requests to go back a page globally"""
@@ -709,10 +749,18 @@ def _handle_notify_gcode_response_message(self, method, data, metadata) -> None:
if self._popup_toggle:
return
_gcode_msg_type, _message = str(_gcode_response[0]).split(" ", maxsplit=1)
- popupWhitelist = ["filament runout", "no filament"]
- if _message.lower() not in popupWhitelist or _gcode_msg_type != "!!":
+ _msg_lower = _message.lower()
+ _display = next(
+ (
+ fmt
+ for pattern, fmt in _GCODE_POPUP_MESSAGES
+ if pattern in _msg_lower
+ ),
+ None,
+ )
+ if _gcode_msg_type != "!!" or _display is None:
return
- self.show_notifications.emit("mainwindow", _message, 3, True)
+ self.show_notifications.emit("mainwindow", _display, 3, True)
@api_handler
def _handle_error_message(self, method, data, metadata) -> None:
@@ -721,6 +769,11 @@ def _handle_error_message(self, method, data, metadata) -> None:
if self._popup_toggle:
return
+ # Suppress error popups while Klippy is disconnected/shutting down.
+ # Those errors are side-effects of the disconnect, not actionable by the user.
+ if not self._klippy_ready:
+ return
+
text = data.get("message", str(data)) if isinstance(data, dict) else str(data)
lower_text = text.lower()
@@ -825,11 +878,10 @@ def event(self, event: QtCore.QEvent) -> bool:
return True
return False
if event.type() == events.PrintStart.type():
+ self.print_status = "printing"
self.disable_tab_bar()
self.ui.extruder_temp_display.clicked.disconnect()
self.ui.bed_temp_display.clicked.disconnect()
- self.ui.filament_type_icon.setDisabled(True)
- self.ui.nozzle_size_icon.setDisabled(True)
self.ui.extruder_temp_display.clicked.connect(
lambda: self.global_change_page(
self.ui.main_content_widget.indexOf(self.ui.printTab),
@@ -849,13 +901,12 @@ def event(self, event: QtCore.QEvent) -> bool:
events.PrintComplete.type(),
events.PrintCancelled.type(),
):
+ self.print_status = "idle"
if event.type() == events.PrintCancelled.type():
self.handle_cancel_print()
self.enable_tab_bar()
self.ui.extruder_temp_display.clicked.disconnect()
self.ui.bed_temp_display.clicked.disconnect()
- self.ui.filament_type_icon.setDisabled(False)
- self.ui.nozzle_size_icon.setDisabled(False)
self.ui.extruder_temp_display.clicked.connect(
lambda: self.global_change_page(
self.ui.main_content_widget.indexOf(self.ui.controlTab),
@@ -874,4 +925,4 @@ def event(self, event: QtCore.QEvent) -> bool:
def sizeHint(self) -> QtCore.QSize:
"""Sets default size for the widget"""
self.adjustSize()
- return super().sizeHint(QtCore.QSize(800, 480))
+ return QtCore.QSize(800, 480)
diff --git a/BlocksScreen/lib/panels/printTab.py b/BlocksScreen/lib/panels/printTab.py
index 65927aae..8d63c8ed 100644
--- a/BlocksScreen/lib/panels/printTab.py
+++ b/BlocksScreen/lib/panels/printTab.py
@@ -56,18 +56,22 @@ class PrintTab(QtWidgets.QStackedWidget):
int, int, name="request_change_page"
)
+ request_change_tab: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ int, name="request_change_tab"
+ )
+
run_gcode_signal: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
str, name="run_gcode"
)
on_cancel_print: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
name="on_cancel_print"
)
- call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel")
-
- call_cancel_panel = QtCore.pyqtSignal(bool, name="call-load-panel")
- _z_offset: float = 0.0
- _active_z_offset: float = 0.0
- _finish_print_handled: bool = False
+ call_load_panel: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ bool, str, name="call-load-panel"
+ )
+ call_cancel_panel: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ bool, name="call-load-panel"
+ )
def __init__(
self,
@@ -77,6 +81,11 @@ def __init__(
printer: Printer,
) -> None:
super().__init__(parent)
+ self._active_z_offset: float = 0.0
+ self._pending_save_offset: float = 0.0
+ self._finish_print_handled: bool = False
+ self._cancel_z_snapshot: float = 0.0
+ self._z_apply_command: str = "Z_OFFSET_APPLY_ENDSTOP"
self.setupMainPrintPage()
self.ws: MoonWebSocket = ws
@@ -201,10 +210,14 @@ def __init__(
self.addWidget(self.babystepPage)
self.tune_page = TuneWidget(self)
self.addWidget(self.tune_page)
+ self.tune_page.tune_change_filament_btn.clicked.connect(
+ lambda: self.request_change_tab.emit(1)
+ )
self.jobStatusPage_widget.tune_clicked.connect(
lambda: self.change_page(self.indexOf(self.tune_page))
)
self.tune_page.request_back.connect(self.back_button)
+ self.printer.printer_config.connect(self.tune_page.on_printer_config)
self.printer.extruder_update.connect(
self.tune_page.on_extruder_temperature_change
)
@@ -223,6 +236,9 @@ def __init__(
self.printer.gcode_move_update[str, list].connect(
self.babystepPage.on_gcode_move_update
)
+ self.printer.print_stats_update[str, str].connect(
+ self.babystepPage.on_print_state_update
+ )
self.printer.gcode_move_update[str, list].connect(self.activate_save_button)
self.tune_page.run_gcode.connect(self.ws.api.run_gcode)
self.tune_page.request_sliderPage[str, int, "PyQt_PyObject"].connect(
@@ -267,7 +283,7 @@ def __init__(
self.confirmPage_widget.on_delete.connect(self.delete_file)
self.change_page(self.indexOf(self.print_page)) # force set the initial page
self.save_config_btn.clicked.connect(self.save_config)
- self.BasePopup_z_offset.accepted.connect(self.update_configuration_file)
+ self.ws.klippy_state_signal.connect(self.on_klippy_state)
@QtCore.pyqtSlot(str, dict, name="on_print_stats_update")
@QtCore.pyqtSlot(str, float, name="on_print_stats_update")
@@ -276,10 +292,14 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None:
"""
unblocks tabs if on standby
"""
- if isinstance(value, str):
- if "state" in field:
- if value in ("standby"):
- self.on_cancel_print.emit()
+ if isinstance(value, str) and "state" in field and value == "standby":
+ self.on_cancel_print.emit()
+ if not self._finish_print_handled and self._cancel_z_snapshot != 0:
+ self._active_z_offset = self._cancel_z_snapshot
+ self.save_config()
+ self._finish_print_handled = True
+ self.save_config_btn.setVisible(True)
+ self._cancel_z_snapshot = 0.0
@QtCore.pyqtSlot(str, int, "PyQt_PyObject", name="on_numpad_request")
@QtCore.pyqtSlot(str, int, "PyQt_PyObject", int, int, name="on_numpad_request")
@@ -292,6 +312,10 @@ def on_numpad_request(
max_value: int = 100,
) -> None:
"""Handle numpad request"""
+ try:
+ self.numpadPage.value_selected.disconnect()
+ except (RuntimeError, TypeError):
+ pass
self.numpadPage.value_selected.connect(callback)
self.numpadPage.set_name(name)
self.numpadPage.set_value(current_value)
@@ -311,6 +335,10 @@ def on_slidePage_request(
max_value: int = 100,
) -> None:
"""Handle slider page request"""
+ try:
+ self.sliderPage.value_selected.disconnect()
+ except (RuntimeError, TypeError):
+ pass
self.sliderPage.value_selected.connect(callback)
self.sliderPage.set_name(name)
self.sliderPage.set_slider_position(int(current_value))
@@ -321,8 +349,12 @@ def on_slidePage_request(
@QtCore.pyqtSlot(str, str, name="delete_file")
@QtCore.pyqtSlot(str, name="delete_file")
def delete_file(self, filename: str, directory: str = "gcodes") -> None:
- """Handle Delete file signal, shows confirmation dialog"""
+ """Handle Delete file signal, shows confirmation dialog."""
self.BasePopup.set_message("Are you sure you want to delete this file?")
+ try:
+ self.BasePopup.accepted.disconnect()
+ except (RuntimeError, TypeError):
+ pass
self.BasePopup.accepted.connect(
lambda: self._on_delete_file_confirmed(filename, directory)
)
@@ -330,42 +362,62 @@ def delete_file(self, filename: str, directory: str = "gcodes") -> None:
def save_config(self) -> None:
"""Handle Save configuration behaviour, shows confirmation dialog"""
- if self._finish_print_handled:
- self.run_gcode_signal.emit("Z_OFFSET_APPLY_PROBE")
- self._z_offset = self._active_z_offset
- self.babystepPage.bbp_z_offset_title_label.setText(
- f"Z: {self._z_offset:.3f}mm"
- )
+ self._pending_save_offset = self._active_z_offset
self.BasePopup_z_offset.set_message(
- f"The Z‑Offset is now {self._active_z_offset:.3f} mm.\n"
+ f"The Z-Offset is now {self._pending_save_offset + 0.0:.3f} mm.\n"
"Would you like to save this change permanently?\n"
"The machine will restart."
)
self.BasePopup_z_offset.cancel_button_text("Later")
+ try:
+ self.BasePopup_z_offset.accepted.disconnect(self.update_configuration_file)
+ except (RuntimeError, TypeError):
+ pass
+ self.BasePopup_z_offset.accepted.connect(self.update_configuration_file)
self.BasePopup_z_offset.open()
- def update_configuration_file(self):
+ def update_configuration_file(self) -> None:
"""Runs the `SAVE_CONFIG` gcode"""
- self.run_gcode_signal.emit("Z_OFFSET_APPLY_PROBE")
+ try:
+ self.BasePopup_z_offset.accepted.disconnect(self.update_configuration_file)
+ except (RuntimeError, TypeError):
+ pass
+ self.run_gcode_signal.emit(
+ f"SET_GCODE_OFFSET Z={self._pending_save_offset:.3f} MOVE=0"
+ )
+ self.run_gcode_signal.emit(self._z_apply_command)
self.run_gcode_signal.emit("SAVE_CONFIG")
- self.BasePopup_z_offset.disconnect()
+ self.babystepPage.bbp_z_offset_title_label.setText(
+ f"Z: {self._pending_save_offset + 0.0:.3f}mm"
+ )
+ self.save_config_btn.setVisible(False)
+
+ @QtCore.pyqtSlot(str, name="on_klippy_state")
+ def on_klippy_state(self, state: str) -> None:
+ """Dismiss the Z-offset save popup and reset save state on unexpected shutdown."""
+ if state in ("ready", "startup"):
+ return
+ self.BasePopup_z_offset.reject()
+ self.save_config_btn.setVisible(False)
+ self.babystepPage.baby_stepchange = False
@QtCore.pyqtSlot(str, list, name="activate_save_button")
def activate_save_button(self, name: str, value: list) -> None:
"""Sync the `Save config` popup with the save_config_pending state"""
- if not value:
+ if not value or name != "homing_origin" or len(value) <= 2:
return
-
- if name == "homing_origin":
- self._active_z_offset = value[2]
- self.save_config_btn.setVisible(value[2] != 0)
+ self._active_z_offset = value[2]
+ self.save_config_btn.setVisible(round(value[2], 3) != 0)
def _on_delete_file_confirmed(self, filename: str, directory: str) -> None:
- """Handle confirmed file deletion after user accepted the dialog"""
+ """Handle confirmed file deletion after user accepted the dialog."""
self.file_data.on_request_delete_file(filename, directory)
self.request_back.emit()
self.filesPage_widget.reset_dir()
- self.BasePopup.disconnect()
+ try:
+ self.BasePopup.accepted.disconnect()
+ except (RuntimeError, TypeError):
+ pass
def setProperty(self, name: str, value: typing.Any) -> bool:
"""Intercept the set property method
@@ -383,6 +435,12 @@ def setProperty(self, name: str, value: typing.Any) -> bool:
def handle_cancel_print(self) -> None:
"""Handles the print cancel action"""
+ if (
+ not self._finish_print_handled
+ and self._active_z_offset != 0
+ and self.babystepPage.baby_stepchange
+ ):
+ self._cancel_z_snapshot = self._active_z_offset
self.ws.api.cancel_print()
self.call_load_panel.emit(True, "Cancelling print...\nPlease wait")
@@ -404,6 +462,18 @@ def klipper_ready_signal(self) -> None:
"""React to klipper ready signal"""
self.babystepPage.baby_stepchange = False
self._finish_print_handled = False
+ self._cancel_z_snapshot = 0.0
+ self.printer.on_subscribe_config("stepper_z", self._on_stepper_z_config)
+
+ def _on_stepper_z_config(self, config: dict | list) -> None:
+ """Select the correct Z-offset apply command based on endstop type."""
+ if not isinstance(config, dict):
+ return
+ stepper_z = config.get("stepper_z", {})
+ if stepper_z.get("endstop_pin") == "probe:z_virtual_endstop":
+ self._z_apply_command = "Z_OFFSET_APPLY_PROBE"
+ else:
+ self._z_apply_command = "Z_OFFSET_APPLY_ENDSTOP"
@QtCore.pyqtSlot(name="finish_print_signal")
def finish_print_signal(self) -> None:
@@ -413,6 +483,7 @@ def finish_print_signal(self) -> None:
if self._active_z_offset != 0 and self.babystepPage.baby_stepchange:
self.save_config()
self._finish_print_handled = True
+ self.save_config_btn.setVisible(round(self._active_z_offset, 3) != 0)
def setupMainPrintPage(self) -> None:
"""Setup UI for print page"""
diff --git a/BlocksScreen/lib/panels/widgets/babystepPage.py b/BlocksScreen/lib/panels/widgets/babystepPage.py
index 273e8f9c..1b632c17 100644
--- a/BlocksScreen/lib/panels/widgets/babystepPage.py
+++ b/BlocksScreen/lib/panels/widgets/babystepPage.py
@@ -1,3 +1,4 @@
+import logging
import typing
from lib.utils.blocks_label import BlocksLabel
@@ -5,8 +6,20 @@
from lib.utils.icon_button import IconButton
from PyQt6 import QtCore, QtGui, QtWidgets
+logger = logging.getLogger(__name__)
+
+# Button definitions: (label, value, object_name, initially_checked)
+_OFFSET_STEPS: list[tuple[str, float, str, bool]] = [
+ ("0.010 mm", 0.01, "bbp_nozzle_offset_01", True),
+ ("0.025 mm", 0.025, "bbp_nozzle_offset_025", False),
+ ("0.050 mm", 0.05, "bbp_nozzle_offset_05", False),
+ ("0.100 mm", 0.1, "bbp_nozzle_offset_1", False),
+]
+
class BabystepPage(QtWidgets.QWidget):
+ """Page for adjusting Z offset in small increments during a print."""
+
request_back: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
name="request_back"
)
@@ -14,117 +27,131 @@ class BabystepPage(QtWidgets.QWidget):
str, name="run_gcode"
)
- _z_offset: float = 0.1
-
def __init__(self, parent) -> None:
super().__init__(parent)
+ self._z_offset: float = _OFFSET_STEPS[0][1]
self.setObjectName("babystepPage")
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True)
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_MouseTracking, True)
self.setTabletTracking(True)
self.setMouseTracking(True)
- self.setupUI()
+ self._baby_stepchange = False
+ self._z_offset_text: float = 0.0
+ self._pending_z_offset: float = 0.0
+
+ self._setupUI()
self.bbp_mvup.clicked.connect(self.on_move_nozzle_close)
self.bbp_mvdown.clicked.connect(self.on_move_nozzle_away)
+ self.bbp_mvup.setEnabled(False)
+ self.bbp_mvdown.setEnabled(False)
self.babystep_back_btn.clicked.connect(self.request_back.emit)
- self.bbp_nozzle_offset_01.toggled.connect(self.handle_z_offset_change)
- self.bbp_nozzle_offset_025.toggled.connect(self.handle_z_offset_change)
- self.bbp_nozzle_offset_05.toggled.connect(self.handle_z_offset_change)
- self.bbp_nozzle_offset_1.toggled.connect(self.handle_z_offset_change)
- self._baby_stepchange = False
@property
def baby_stepchange(self):
- """Returns if the babystep was changed during print"""
+ """Returns if the babystep was changed during print."""
return self._baby_stepchange
@baby_stepchange.setter
def baby_stepchange(self, value: bool) -> None:
- if not isinstance(value, bool):
- raise ValueError("Value must be a bool")
+ """Set the babystep-changed flag."""
self._baby_stepchange = value
@QtCore.pyqtSlot(name="on_move_nozzle_close")
def on_move_nozzle_close(self) -> None:
- """Move the nozzle closer to the print plate
- by the amount set in **` self._z_offset`**
- """
- self.run_gcode.emit(
- f"SET_GCODE_OFFSET Z_ADJUST=-{self._z_offset} MOVE=1" # Z_ADJUST adds the value to the existing offset
+ """Move the nozzle closer to the print plate."""
+ self.run_gcode.emit(f"SET_GCODE_OFFSET Z_ADJUST=-{self._z_offset} MOVE=1")
+ self._pending_z_offset -= self._z_offset
+ self.bbp_z_offset_current_value.setText(
+ f"Z: {round(self._pending_z_offset, 3) or 0.0:.3f} mm"
)
self._baby_stepchange = True
@QtCore.pyqtSlot(name="on_move_nozzle_away")
def on_move_nozzle_away(self) -> None:
- """Slot for Babystep button to get far from the
- bed by **` self._z_offset`** amount
- """
- self.run_gcode.emit(
- f"SET_GCODE_OFFSET Z_ADJUST=+{self._z_offset} MOVE=1" # Z_ADJUST adds the value to the existing offset
+ """Move the nozzle away from the print plate."""
+ self.run_gcode.emit(f"SET_GCODE_OFFSET Z_ADJUST=+{self._z_offset} MOVE=1")
+ self._pending_z_offset += self._z_offset
+ self.bbp_z_offset_current_value.setText(
+ f"Z: {round(self._pending_z_offset, 3) or 0.0:.3f} mm"
)
self._baby_stepchange = True
- @QtCore.pyqtSlot(name="handle_z_offset_change")
- def handle_z_offset_change(self) -> None:
- """Helper method for changing the value for Babystep.
-
- When a button is clicked, and the button has the mm value i the text,
- it'll change the internal value **z_offset** to the same has the button
+ @QtCore.pyqtSlot(str, str, name="on_print_state_update")
+ def on_print_state_update(self, field: str, value: str) -> None:
+ """Enable move buttons only while the printer is actively printing."""
+ if "state" in field:
+ printing = value == "printing"
+ self.bbp_mvup.setEnabled(printing)
+ self.bbp_mvdown.setEnabled(printing)
- ***
-
- Possible values are: 0.01, 0.025, 0.05, 0.1 **mm**
- """
- _sender: QtCore.QObject | None = self.sender()
- if self._z_offset == float(_sender.text()[:-3]):
- return
- self._z_offset = float(_sender.text()[:-3])
+ def _set_z_offset(self, value: float) -> None:
+ """Update the active step size."""
+ if self._z_offset != value:
+ self._z_offset = value
+ @QtCore.pyqtSlot(str, list, name="on_gcode_move_update")
def on_gcode_move_update(self, name: str, value: list) -> None:
- """Handle gcode move updates"""
+ """Handle gcode move updates from Klipper."""
if not value:
return
- if name == "homing_origin":
- self._z_offset_text = value[2]
- self.bbp_z_offset_current_value.setText(f"Z: {self._z_offset_text:.3f}mm")
-
- def setupUI(self):
- """Setup babystep page ui"""
- self.bbp_offset_value_selector_group = QtWidgets.QButtonGroup(self)
- self.bbp_offset_value_selector_group.setExclusive(True)
- sizePolicy = QtWidgets.QSizePolicy(
+ if name == "homing_origin" and len(value) > 2:
+ confirmed = value[2]
+ self._z_offset_text = confirmed
+ self.bbp_z_offset_title_label.setText(
+ f"Z: {round(confirmed, 3) or 0.0:.3f} mm"
+ )
+ # Always sync pending offset to Klipper's confirmed value
+ self._pending_z_offset = confirmed
+ self.bbp_z_offset_current_value.setText(
+ f"Z: {round(confirmed, 3) or 0.0:.3f} mm"
+ )
+
+ def _create_offset_button(
+ self,
+ parent: QtWidgets.QWidget,
+ label: str,
+ obj_name: str,
+ checked: bool,
+ font: QtGui.QFont,
+ ) -> BlocksCustomCheckButton:
+ """Create a single offset-step check button."""
+ btn = BlocksCustomCheckButton(parent=parent)
+ btn.setMinimumSize(QtCore.QSize(100, 70))
+ btn.setMaximumSize(QtCore.QSize(100, 70))
+ btn.setText(label)
+ btn.setFont(font)
+ btn.setCheckable(True)
+ btn.setChecked(checked)
+ btn.setFlat(True)
+ btn.setProperty("button_type", "")
+ btn.setObjectName(obj_name)
+ return btn
+
+ def _setupUI(self) -> None:
+ """Setup babystep page UI."""
+ btn_group = QtWidgets.QButtonGroup(self)
+ btn_group.setExclusive(True)
+
+ size_policy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
)
- sizePolicy.setHorizontalStretch(1)
- sizePolicy.setVerticalStretch(1)
- sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
- self.setSizePolicy(sizePolicy)
+ size_policy.setHorizontalStretch(1)
+ size_policy.setVerticalStretch(1)
+ size_policy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
+ self.setSizePolicy(size_policy)
self.setMinimumSize(QtCore.QSize(710, 400))
- self.setMaximumSize(
- QtCore.QSize(720, 420)
- ) # This sets the maximum width of the entire page
+ self.setMaximumSize(QtCore.QSize(720, 420))
self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
- # Main Vertical Layout for the entire page
- self.verticalLayout = QtWidgets.QVBoxLayout(self)
- self.verticalLayout.setObjectName("verticalLayout")
+ main_vlayout = QtWidgets.QVBoxLayout(self)
- # Header Layout
- self.bbp_header_layout = QtWidgets.QHBoxLayout()
- self.bbp_header_layout.setObjectName("bbp_header_layout")
- self.bbp_header_title = QtWidgets.QLabel(parent=self)
- sizePolicy.setHeightForWidth(
- self.bbp_header_title.sizePolicy().hasHeightForWidth()
- )
- self.bbp_header_title.setSizePolicy(sizePolicy)
- self.bbp_header_title.setMinimumSize(QtCore.QSize(200, 60))
- self.bbp_header_title.setMaximumSize(QtCore.QSize(16777215, 60))
- font = QtGui.QFont()
- font.setPointSize(22)
- self.bbp_header_title.setFont(font)
+ header = QtWidgets.QHBoxLayout()
+
+ title_font = QtGui.QFont()
+ title_font.setPointSize(22)
palette = QtGui.QPalette()
palette.setColor(
palette.ColorGroup.All,
@@ -136,187 +163,65 @@ def setupUI(self):
palette.ColorRole.WindowText,
QtGui.QColor("#FFFFFF"),
)
+
+ self.bbp_header_title = QtWidgets.QLabel("Babystep", parent=self)
+ self.bbp_header_title.setMinimumSize(QtCore.QSize(200, 60))
+ self.bbp_header_title.setMaximumSize(QtCore.QSize(16777215, 60))
+ self.bbp_header_title.setFont(title_font)
self.bbp_header_title.setAutoFillBackground(True)
self.bbp_header_title.setBackgroundRole(palette.ColorRole.Window)
self.bbp_header_title.setPalette(palette)
- self.bbp_header_title.setText("Babystep")
self.bbp_header_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
- self.bbp_header_title.setObjectName("bbp_header_title")
-
- spacerItem = QtWidgets.QSpacerItem(
- 60,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.bbp_header_layout.addItem(spacerItem)
+ header.addWidget(self.bbp_header_title, 1)
- self.bbp_header_layout.addWidget(
- self.bbp_header_title,
- 0,
- QtCore.Qt.AlignmentFlag.AlignCenter,
- )
self.babystep_back_btn = IconButton(parent=self)
- sizePolicy.setHeightForWidth(
- self.babystep_back_btn.sizePolicy().hasHeightForWidth()
- )
- self.babystep_back_btn.setSizePolicy(sizePolicy)
+ self.babystep_back_btn.setSizePolicy(size_policy)
self.babystep_back_btn.setMinimumSize(QtCore.QSize(60, 60))
self.babystep_back_btn.setMaximumSize(QtCore.QSize(60, 60))
- self.babystep_back_btn.setText("")
self.babystep_back_btn.setFlat(True)
self.babystep_back_btn.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back.svg"))
- self.babystep_back_btn.setObjectName("babystep_back_btn")
-
- self.bbp_header_layout.addWidget(
- self.babystep_back_btn,
- 0,
- QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
- self.bbp_header_layout.setStretch(0, 1)
- self.verticalLayout.addLayout(self.bbp_header_layout)
-
- self.main_content_horizontal_layout = QtWidgets.QHBoxLayout()
- self.main_content_horizontal_layout.setObjectName(
- "main_content_horizontal_layout"
- )
-
- # Offset Steps Buttons Group Box (LEFT side of main_content_horizontal_layout)
- self.bbp_offset_steps_buttons_group_box = QtWidgets.QGroupBox(self)
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_offset_steps_buttons_group_box.setFont(font)
- self.bbp_offset_steps_buttons_group_box.setFlat(True)
- # Add stylesheet to explicitly remove any border from the QGroupBox
- self.bbp_offset_steps_buttons_group_box.setStyleSheet(
- "QGroupBox { border: none; }"
- )
- self.bbp_offset_steps_buttons_group_box.setObjectName(
- "bbp_offset_steps_buttons_group_box"
- )
-
- self.bbp_offset_steps_buttons = QtWidgets.QVBoxLayout(
- self.bbp_offset_steps_buttons_group_box
- )
- self.bbp_offset_steps_buttons.setContentsMargins(9, 9, 9, 9)
- self.bbp_offset_steps_buttons.setObjectName("bbp_offset_steps_buttons")
-
- # 0.1mm button
- self.bbp_nozzle_offset_1 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.bbp_nozzle_offset_1.setMinimumSize(QtCore.QSize(100, 70))
- self.bbp_nozzle_offset_1.setMaximumSize(QtCore.QSize(100, 70))
- self.bbp_nozzle_offset_1.setText("0.1 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_nozzle_offset_1.setFont(font)
- self.bbp_nozzle_offset_1.setCheckable(True)
- self.bbp_nozzle_offset_1.setChecked(True) # Set as initially checked
- self.bbp_nozzle_offset_1.setFlat(True)
- self.bbp_nozzle_offset_1.setProperty("button_type", "")
- self.bbp_nozzle_offset_1.setObjectName("bbp_nozzle_offset_1")
- self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_1)
- self.bbp_offset_steps_buttons.addWidget(
- self.bbp_nozzle_offset_1,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # 0.05mm button
- self.bbp_nozzle_offset_05 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.bbp_nozzle_offset_05.setMinimumSize(QtCore.QSize(100, 70))
- self.bbp_nozzle_offset_05.setMaximumSize(
- QtCore.QSize(100, 70)
- ) # Increased max width by 5 pixels
- self.bbp_nozzle_offset_05.setText("0.05 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_nozzle_offset_05.setFont(font)
- self.bbp_nozzle_offset_05.setCheckable(True)
- self.bbp_nozzle_offset_05.setFlat(True)
- self.bbp_nozzle_offset_05.setProperty("button_type", "")
- self.bbp_nozzle_offset_05.setObjectName("bbp_nozzle_offset_05")
- self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_05)
- self.bbp_offset_steps_buttons.addWidget(
- self.bbp_nozzle_offset_05,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # Line separator for 0.1mm - set size policy to expanding horizontally
-
- # 0.01mm button
- self.bbp_nozzle_offset_01 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.bbp_nozzle_offset_01.setMinimumSize(QtCore.QSize(100, 70))
- self.bbp_nozzle_offset_01.setMaximumSize(
- QtCore.QSize(100, 70)
- ) # Increased max width by 5 pixels
- self.bbp_nozzle_offset_01.setText("0.01 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_nozzle_offset_01.setFont(font)
- self.bbp_nozzle_offset_01.setCheckable(True)
- self.bbp_nozzle_offset_01.setFlat(True)
- self.bbp_nozzle_offset_01.setProperty("button_type", "")
- self.bbp_nozzle_offset_01.setObjectName("bbp_nozzle_offset_01")
- self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_01)
- self.bbp_offset_steps_buttons.addWidget(
- self.bbp_nozzle_offset_01,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # 0.025mm button
- self.bbp_nozzle_offset_025 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.bbp_nozzle_offset_025.setMinimumSize(QtCore.QSize(100, 70))
- self.bbp_nozzle_offset_025.setMaximumSize(
- QtCore.QSize(100, 70)
- ) # Increased max width by 5 pixels
- self.bbp_nozzle_offset_025.setText("0.025 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_nozzle_offset_025.setFont(font)
- self.bbp_nozzle_offset_025.setCheckable(True)
- self.bbp_nozzle_offset_025.setFlat(True)
- self.bbp_nozzle_offset_025.setProperty("button_type", "")
- self.bbp_nozzle_offset_025.setObjectName("bbp_nozzle_offset_025")
- self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_025)
- self.bbp_offset_steps_buttons.addWidget(
- self.bbp_nozzle_offset_025,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # Line separator for 0.025mm - set size policy to expanding horizontally
-
- # Set the layout for the group box
- self.bbp_offset_steps_buttons_group_box.setLayout(self.bbp_offset_steps_buttons)
- # Add the group box to the main content horizontal layout FIRST for left placement
- self.main_content_horizontal_layout.addWidget(
- self.bbp_offset_steps_buttons_group_box
- )
-
- # Graphic and Current Value Frame (This will now be in the MIDDLE)
- self.frame_2 = QtWidgets.QFrame(parent=self)
- sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth())
- self.frame_2.setSizePolicy(sizePolicy)
- self.frame_2.setMinimumSize(QtCore.QSize(350, 160))
- self.frame_2.setMaximumSize(QtCore.QSize(350, 160))
- self.frame_2.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
- self.frame_2.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
- self.frame_2.setObjectName("frame_2")
- self.bbp_babystep_graphic = QtWidgets.QLabel(parent=self.frame_2)
+ header.addWidget(self.babystep_back_btn, 0)
+ main_vlayout.addLayout(header)
+
+ # --- Main content (3 columns) ---
+ content = QtWidgets.QHBoxLayout()
+
+ # Column 1: offset step buttons (highest → lowest)
+ group_box = QtWidgets.QGroupBox(self)
+ btn_font = QtGui.QFont()
+ btn_font.setPointSize(14)
+ group_box.setFont(btn_font)
+ group_box.setFlat(True)
+ group_box.setStyleSheet("QGroupBox { border: none; }")
+
+ steps_layout = QtWidgets.QVBoxLayout(group_box)
+ steps_layout.setContentsMargins(9, 9, 9, 9)
+
+ center = (
+ QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter
+ )
+ for label, value, obj_name, checked in _OFFSET_STEPS:
+ btn = self._create_offset_button(
+ group_box, label, obj_name, checked, btn_font
+ )
+ btn.toggled.connect(
+ lambda checked_state, v=value: checked_state and self._set_z_offset(v)
+ )
+ setattr(self, obj_name, btn)
+ btn_group.addButton(btn)
+ steps_layout.addWidget(btn, 0, center)
+
+ content.addWidget(group_box)
+
+ # Column 2: graphic + Z offset labels
+ frame = QtWidgets.QFrame(parent=self)
+ frame.setSizePolicy(size_policy)
+ frame.setMinimumSize(QtCore.QSize(350, 160))
+ frame.setMaximumSize(QtCore.QSize(350, 160))
+ frame.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
+ frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
+
+ self.bbp_babystep_graphic = QtWidgets.QLabel(parent=frame)
self.bbp_babystep_graphic.setGeometry(QtCore.QRect(0, 30, 371, 121))
self.bbp_babystep_graphic.setLayoutDirection(
QtCore.Qt.LayoutDirection.RightToLeft
@@ -326,129 +231,92 @@ def setupUI(self):
)
self.bbp_babystep_graphic.setScaledContents(False)
self.bbp_babystep_graphic.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
- self.bbp_babystep_graphic.setObjectName("bbp_babystep_graphic")
- # === NEW LABEL ADDED HERE ===
- # This is the title label that appears above the red value box.
+ grey_font = QtGui.QFont()
+ grey_font.setPointSize(12)
self.bbp_z_offset_title_label = QtWidgets.QLabel(parent=self)
- # Position it just above the red box. Red box is at y=70, so y=40 is appropriate.
- self.bbp_z_offset_title_label.setGeometry(QtCore.QRect(100, 40, 200, 30))
- font = QtGui.QFont()
- font.setPointSize(12)
-
- self.bbp_z_offset_title_label.setFont(font)
- # Set color to white to be visible on the dark background
+ self.bbp_z_offset_title_label.setFont(grey_font)
self.bbp_z_offset_title_label.setStyleSheet(
"color: gray; background: transparent;"
)
- self.bbp_z_offset_title_label.setObjectName("bbp_z_offset_title_label")
- self.bbp_z_offset_title_label.setText("Z: 0.000mm")
+ self.bbp_z_offset_title_label.setText(
+ f"Z: {round(self._z_offset_text, 3) or 0.0:.3f} mm"
+ )
self.bbp_z_offset_title_label.setGeometry(420, 270, 200, 30)
- # === END OF NEW LABEL ===
-
- self.bbp_z_offset_current_value = BlocksLabel(parent=self.frame_2)
+ white_font = QtGui.QFont()
+ white_font.setPointSize(14)
+ self.bbp_z_offset_current_value = BlocksLabel(parent=frame)
self.bbp_z_offset_current_value.setGeometry(QtCore.QRect(100, 70, 200, 60))
- sizePolicy.setHeightForWidth(
- self.bbp_z_offset_current_value.sizePolicy().hasHeightForWidth()
- )
- self.bbp_z_offset_current_value.setSizePolicy(sizePolicy)
+ self.bbp_z_offset_current_value.setSizePolicy(size_policy)
self.bbp_z_offset_current_value.setMinimumSize(QtCore.QSize(150, 60))
self.bbp_z_offset_current_value.setMaximumSize(QtCore.QSize(200, 60))
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_z_offset_current_value.setFont(font)
+ self.bbp_z_offset_current_value.setFont(white_font)
self.bbp_z_offset_current_value.setStyleSheet(
"background: transparent; color: white;"
)
- self.bbp_z_offset_current_value.setText(f"Z: {self._z_offset:.2f}mm")
+ self.bbp_z_offset_current_value.setText(
+ f"Z: {round(self._pending_z_offset, 3) or 0.0:.3f} mm"
+ )
self.bbp_z_offset_current_value.setPixmap(
QtGui.QPixmap(":/graphics/media/btn_icons/z_offset_adjust.svg")
)
self.bbp_z_offset_current_value.setAlignment(
QtCore.Qt.AlignmentFlag.AlignCenter
)
- self.bbp_z_offset_current_value.setObjectName("bbp_z_offset_current_value")
- # Add graphic frame AFTER the offset buttons group box
- self.main_content_horizontal_layout.addWidget(
- self.frame_2,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
+
+ content.addWidget(frame, 0, center)
+
+ # Spacer before move buttons
+ content.addItem(
+ QtWidgets.QSpacerItem(
+ 40,
+ 20,
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ )
)
- # Move Buttons Layout (This will now be on the RIGHT)
- self.bbp_buttons_layout = QtWidgets.QVBoxLayout()
- self.bbp_buttons_layout.setContentsMargins(5, 5, 5, 5)
- self.bbp_buttons_layout.setObjectName("bbp_buttons_layout")
+ # Column 3: move up/down buttons
+ move_layout = QtWidgets.QVBoxLayout()
+ move_layout.setContentsMargins(5, 5, 5, 5)
+
self.bbp_mvup = IconButton(parent=self)
- sizePolicy.setHeightForWidth(self.bbp_mvup.sizePolicy().hasHeightForWidth())
- self.bbp_mvup.setSizePolicy(sizePolicy)
+ self.bbp_mvup.setSizePolicy(size_policy)
self.bbp_mvup.setMinimumSize(QtCore.QSize(80, 80))
self.bbp_mvup.setMaximumSize(QtCore.QSize(80, 80))
- self.bbp_mvup.setText("")
self.bbp_mvup.setFlat(True)
self.bbp_mvup.setPixmap(
QtGui.QPixmap(":/baby_step/media/btn_icons/move_nozzle_close.svg")
)
- self.bbp_mvup.setObjectName("bbp_away_from_bed")
- self.bbp_option_button_group = QtWidgets.QButtonGroup(self)
- self.bbp_option_button_group.setObjectName("bbp_option_button_group")
- self.bbp_option_button_group.addButton(self.bbp_mvup)
- self.bbp_buttons_layout.addWidget(
- self.bbp_mvup, 0, QtCore.Qt.AlignmentFlag.AlignRight
- )
+ move_layout.addWidget(self.bbp_mvup, 0, QtCore.Qt.AlignmentFlag.AlignRight)
+
self.bbp_mvdown = IconButton(parent=self)
- sizePolicy.setHeightForWidth(self.bbp_mvdown.sizePolicy().hasHeightForWidth())
- self.bbp_mvdown.setSizePolicy(sizePolicy)
+ self.bbp_mvdown.setSizePolicy(size_policy)
self.bbp_mvdown.setMinimumSize(QtCore.QSize(80, 80))
self.bbp_mvdown.setMaximumSize(QtCore.QSize(80, 80))
- self.bbp_mvdown.setText("")
self.bbp_mvdown.setFlat(True)
self.bbp_mvdown.setPixmap(
QtGui.QPixmap(":/baby_step/media/btn_icons/move_nozzle_away.svg")
)
- self.bbp_mvdown.setObjectName("bbp_close_to_bed")
- self.bbp_option_button_group.addButton(self.bbp_mvdown)
- self.bbp_buttons_layout.addWidget(
- self.bbp_mvdown, 0, QtCore.Qt.AlignmentFlag.AlignRight
- )
- spacerItem = QtWidgets.QSpacerItem(
- 40,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.main_content_horizontal_layout.addItem(spacerItem)
+ move_layout.addWidget(self.bbp_mvdown, 0, QtCore.Qt.AlignmentFlag.AlignRight)
- # Add move buttons layout LAST for right placement
- self.main_content_horizontal_layout.addLayout(self.bbp_buttons_layout)
+ content.addLayout(move_layout)
- spacerItem = QtWidgets.QSpacerItem(
- 40,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
+ # Trailing spacer
+ content.addItem(
+ QtWidgets.QSpacerItem(
+ 40,
+ 20,
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ )
)
- self.main_content_horizontal_layout.addItem(spacerItem)
-
- # Set stretch factors for main content horizontal layout
- # This will distribute space: offset buttons, graphic frame, move buttons
- self.main_content_horizontal_layout.setStretch(
- 0, 1
- ) # offset_steps_buttons_group_box
- self.main_content_horizontal_layout.setStretch(
- 1, 2
- ) # frame_2 (graphic and current value)
- self.main_content_horizontal_layout.setStretch(
- 2, 0
- ) # bbp_buttons_layout (move buttons)
-
- # Add the main content horizontal layout to the vertical layout
- self.verticalLayout.addLayout(self.main_content_horizontal_layout)
-
- # Set stretch factors for vertical layout (adjust as needed for overall sizing)
- self.verticalLayout.setStretch(
- 1, 1
- ) # This stretch applies to main_content_horizontal_layout
-
- self.setLayout(self.verticalLayout)
+
+ content.setStretch(0, 1) # offset buttons
+ content.setStretch(1, 2) # graphic frame
+ content.setStretch(2, 0) # move buttons
+
+ main_vlayout.addLayout(content)
+ main_vlayout.setStretch(1, 1)
+ self.setLayout(main_vlayout)
diff --git a/BlocksScreen/lib/panels/widgets/probeHelperPage.py b/BlocksScreen/lib/panels/widgets/probeHelperPage.py
index 219adb6a..8170b8ba 100644
--- a/BlocksScreen/lib/panels/widgets/probeHelperPage.py
+++ b/BlocksScreen/lib/panels/widgets/probeHelperPage.py
@@ -1,3 +1,5 @@
+import enum
+import logging
import typing
from lib.panels.widgets.optionCardWidget import OptionCard
@@ -7,6 +9,36 @@
from lib.utils.icon_button import IconButton
from PyQt6 import QtCore, QtGui, QtWidgets
+logger = logging.getLogger(__name__)
+
+_PROBE_MOVE_STEPS: list[tuple[str, float, str, bool]] = [
+ ("0.010 mm", 0.010, "move_option_1", True),
+ ("0.025 mm", 0.025, "move_option_2", False),
+ ("0.100 mm", 0.100, "move_option_3", False),
+ ("0.500 mm", 0.500, "move_option_4", False),
+ ("1.000 mm", 1.000, "move_option_5", False),
+]
+
+_TRACKED_GCODES: frozenset[str] = frozenset(
+ {
+ "PROBE_CALIBRATE",
+ "PROBE_EDDY_CURRENT_CALIBRATE",
+ "LDC_CALIBRATE_DRIVE_CURRENT",
+ "Z_ENDSTOP_CALIBRATE",
+ "MANUAL_PROBE",
+ "CLEAN_NOZZLE",
+ }
+)
+
+
+class _CalibPhase(enum.Enum):
+ IDLE = "idle"
+ PROBE_ACTIVE = "probe_active" # non-eddy: CLEAN_NOZZLE/homing before probe session
+ EDDY_PHASE1 = "eddy_phase1" # LDC drive-current calibration → first SAVE_CONFIG
+ EDDY_PHASE1_RESTART = "eddy_phase1_restart" # post-Phase1 restart, awaiting standby
+ EDDY_PHASE2 = "eddy_phase2" # Z offset calibration → second SAVE_CONFIG
+ SAVE_RESTART = "save_restart" # non-eddy SAVE_CONFIG restart
+
class ProbeHelper(QtWidgets.QWidget):
request_back: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
@@ -33,26 +65,36 @@ class ProbeHelper(QtWidgets.QWidget):
request_page_view: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
name="request_page_view"
)
- call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel")
-
- toggle_conn_page = QtCore.pyqtSignal(bool, name="toggles-conn-panel")
+ call_load_panel: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ bool, str, name="call-load-panel"
+ )
+ toggle_conn_page: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ bool, name="toggles-conn-panel"
+ )
disable_popups: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
bool, name="disable-popups"
)
-
- distances = ["0.01", ".025", "0.1", "0.5", "1"]
- _calibration_commands: list = []
- helper_start: bool = False
- helper_initialize: bool = False
- _zhop_height: float = float(distances[0])
- card_options: dict = {}
- z_offset_method_type: str = ""
- z_offset_config_method: tuple = ()
- z_offset_calibration_speed: int = 100
+ lock_ui: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ bool, name="lock-ui"
+ )
+ show_notifications: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ str, str, int, bool, name="show-notifications"
+ )
def __init__(self, parent: QtWidgets.QWidget) -> None:
super().__init__(parent)
+ self.helper_start: bool = False
+ self.helper_initialize: bool = False
+ self._zhop_height: float = _PROBE_MOVE_STEPS[0][1]
+ self.z_offset_method_type: str = ""
+ self.z_offset_config_method: tuple = ()
+ self.z_offset_calibration_speed: int = 100
+ self.z_offsets: tuple = ()
+ self._calibration_commands: set = set()
+ self.card_options: dict = {}
+ self.z_offset_config_type: str = ""
+ self._eddy_command: str = ""
self.setObjectName("probe_offset_page")
self._setupUi()
@@ -65,97 +107,108 @@ def __init__(self, parent: QtWidgets.QWidget) -> None:
)
self.eddy_icon = QtGui.QPixmap(":/z_levelling/media/btn_icons/eddy_mech.svg")
self._toggle_tool_buttons(False)
- self._setup_move_option_buttons()
- self.move_option_1.toggled.connect(
- lambda: self.handle_zhopHeight_change(new_value=float(self.distances[0]))
- )
- self.move_option_2.toggled.connect(
- lambda: self.handle_zhopHeight_change(new_value=float(self.distances[1]))
- )
- self.move_option_3.toggled.connect(
- lambda: self.handle_zhopHeight_change(new_value=float(self.distances[2]))
- )
- self.move_option_4.toggled.connect(
- lambda: self.handle_zhopHeight_change(new_value=float(self.distances[3]))
- )
- self.move_option_5.toggled.connect(
- lambda: self.handle_zhopHeight_change(new_value=float(self.distances[4]))
- )
self.mb_raise_nozzle.clicked.connect(lambda: self.handle_nozzle_move("raise"))
self.mb_lower_nozzle.clicked.connect(lambda: self.handle_nozzle_move("lower"))
self.po_back_button.clicked.connect(self.request_back)
self.accept_button.clicked.connect(self.handle_accept)
self.abort_button.clicked.connect(self.handle_abort)
- self.update()
self.block_z = False
self.block_list = False
self.target_temp = 0
self.current_temp = 0
- self._eddy_calibration_state = False
+ self._calib_phase = _CalibPhase.IDLE
+ self._active_calibration_tool: str = ""
@QtCore.pyqtSlot(str, dict, name="on_print_stats_update")
@QtCore.pyqtSlot(str, float, name="on_print_stats_update")
@QtCore.pyqtSlot(str, str, name="on_print_stats_update")
def on_print_stats_update(self, field: str, value: dict | float | str) -> None:
"""Handle print stats object update"""
- if isinstance(value, str):
- if "state" in field:
- if value in ("standby"):
- if self._eddy_calibration_state:
- self.run_gcode_signal.emit("G28\nM400")
- self._move_to_pos(
- self.z_offset_safe_xy[0], self.z_offset_safe_xy[1], 100
- )
- self.call_load_panel.emit(True, "Almost done...\nPlease wait")
- self.run_gcode_signal.emit(self._eddy_command)
-
- self.request_page_view.emit()
-
- self.disable_popups.emit(False)
- self.toggle_conn_page.emit(True)
-
- self._eddy_calibration_state = False
-
- def on_klippy_status(self, state: str):
+ if isinstance(value, str) and "state" in field and value == "standby":
+ if self._calib_phase in (
+ _CalibPhase.EDDY_PHASE1,
+ _CalibPhase.EDDY_PHASE1_RESTART,
+ ):
+ self.call_load_panel.emit(
+ True, "Running Z offset calibration\nMoving to position..."
+ )
+ self.run_gcode_signal.emit(self._eddy_command)
+ self.request_page_view.emit()
+ self.disable_popups.emit(False)
+ self.toggle_conn_page.emit(True)
+ self._calib_phase = _CalibPhase.IDLE
+ elif self._calib_phase in (
+ _CalibPhase.EDDY_PHASE2,
+ _CalibPhase.SAVE_RESTART,
+ ):
+ self._calib_phase = _CalibPhase.IDLE
+ self.run_gcode_signal.emit("G28")
+ self.request_page_view.emit()
+ self._restore_ui()
+
+ def on_klippy_status(self, state: str) -> None:
"""Handle Klippy status event change"""
- if state.lower() == "standby":
+ _state = state.lower()
+ if _state == "disconnected":
+ if self._calib_phase in (
+ _CalibPhase.EDDY_PHASE1,
+ _CalibPhase.EDDY_PHASE1_RESTART,
+ _CalibPhase.EDDY_PHASE2,
+ _CalibPhase.SAVE_RESTART,
+ ):
+ self.helper_start = False
+ self.helper_initialize = False
+ match self._calib_phase:
+ case _CalibPhase.EDDY_PHASE2:
+ msg = "Saving calibration data\nRestarting Klipper..."
+ case _CalibPhase.SAVE_RESTART:
+ msg = "Saving configuration\nRestarting Klipper..."
+ case _:
+ msg = "Restarting Klipper..."
+ self.call_load_panel.emit(True, msg)
+ else:
+ self._cancel_calibration()
+ elif _state == "ready":
+ match self._calib_phase:
+ case _CalibPhase.EDDY_PHASE1:
+ self._calib_phase = _CalibPhase.EDDY_PHASE1_RESTART
+ self.call_load_panel.emit(
+ True,
+ "Wait for the toolhead to park.\nPlace a sheet of paper under the nozzle."
+ "\nAdjust until it drags slightly.",
+ )
+ case _CalibPhase.EDDY_PHASE2:
+ self.call_load_panel.emit(
+ True, "Calibration saved\nHoming printer..."
+ )
+ case _CalibPhase.SAVE_RESTART:
+ self.call_load_panel.emit(
+ True, "Configuration saved\nHoming printer..."
+ )
+ elif _state == "shutdown":
+ if self._calib_phase != _CalibPhase.IDLE:
+ self._cancel_calibration()
+ elif _state == "standby":
self.block_z = False
self.block_list = False
- # Safely remove all items (widgets, spacers, sub-layouts) from the layout.
- layout = self.main_content_horizontal_layout
- if layout is not None:
- while layout.count():
- item = layout.takeAt(0)
- if item is None:
- continue
- widget = item.widget()
- if widget is not None:
- # Remove widget from layout and schedule for deletion
- widget.setParent(None)
- widget.deleteLater()
- continue
- child_layout = item.layout()
- if child_layout is not None:
- # Clear child layouts recursively
- while child_layout.count():
- child_item = child_layout.takeAt(0)
- if child_item is None:
- continue
- child_widget = child_item.widget()
- if child_widget is not None:
- child_widget.setParent(None)
- child_widget.deleteLater()
-
- def handle_nozzle_move(self, direction: str):
+ for card in list(self.card_options.values()):
+ self.main_content_horizontal_layout.removeWidget(card)
+ card.setParent(None)
+ card.deleteLater()
+ self.card_options.clear()
+
+ def handle_nozzle_move(self, direction: str) -> None:
"""Handle move z buttons click"""
if direction == "raise":
- self._pending_gcode = f"TESTZ Z={self._zhop_height}"
+ _gcode = f"TESTZ Z={self._zhop_height}"
elif direction == "lower":
- self._pending_gcode = f"TESTZ Z=-{self._zhop_height}"
+ _gcode = f"TESTZ Z=-{self._zhop_height}"
+ else:
+ return
self.accept_button.show()
self.abort_button.show()
- self.run_gcode_signal.emit(self._pending_gcode)
+ self.run_gcode_signal.emit(_gcode)
self.update()
def _configure_option_cards(self, probes_list: list[str]) -> None:
@@ -181,27 +234,28 @@ def _configure_option_cards(self, probes_list: list[str]) -> None:
_card_text = "Endstop Calibration"
_icon = self.endstop_icon
- _card = OptionCard(self, _card_text, str(probe), _icon) # type: ignore
- _card.setObjectName(str(probe))
- self.card_options.update({str(probe): _card})
+ _card = OptionCard(self, _card_text, probe, _icon) # type: ignore
+ if not hasattr(_card, "continue_clicked"):
+ _card.deleteLater()
+ continue
+ _card.setObjectName(probe)
+ self.card_options[probe] = _card
self.main_content_horizontal_layout.addWidget(
_card, alignment=QtCore.Qt.AlignmentFlag.AlignHCenter
)
- if not hasattr(self.card_options.get(probe), "continue_clicked"):
- del _card
- self.card_options.pop(probe)
- return
-
- self.card_options.get(probe).continue_clicked.connect( # type: ignore
- self.handle_start_tool
- )
- self.update()
+ _card.continue_clicked.connect(self.handle_start_tool) # type: ignore
+ self.update()
def _hide_option_cards(self) -> None:
- list(map(lambda x: x[1].hide(), self.card_options.items()))
+ """Hide all probe option cards."""
+ for card in self.card_options.values():
+ card.hide()
def _show_option_cards(self) -> None:
- list(map(lambda x: x[1].show(), self.card_options.items()))
+ """Show and re-enable all probe option cards."""
+ for card in self.card_options.values():
+ card.setEnabled(True)
+ card.show()
def _init_probe_config(self) -> None:
"""Initialize internal probe tracking"""
@@ -209,12 +263,10 @@ def _init_probe_config(self) -> None:
return
if self.z_offset_config_type != "endstop":
self.z_offsets = tuple(
- map(
- lambda axis: self.z_offset_config_method[1].get(f"{axis}_offset"),
- ["x", "y", "z"],
- )
+ self.z_offset_config_method[0].get(f"{axis}_offset")
+ for axis in ("x", "y", "z")
)
- self.z_offset_calibration_speed = self.z_offset_config_method[1].get(
+ self.z_offset_calibration_speed = self.z_offset_config_method[0].get(
"speed"
)
@@ -230,22 +282,16 @@ def on_object_config(self, config: dict | list) -> None:
if not config:
return
- # BUG: If i don't add if not self.probe_config i'll just receive the configuration a bunch of times
if isinstance(config, list):
if self.block_list:
return
- else:
- self.block_list = True
-
- _keys = []
- if not isinstance(config, list):
- return
+ self.block_list = True
- list(map(lambda item: _keys.extend(item.keys()), config))
+ _keys = [k for item in config for k in item]
probe, *_ = config[0].items()
self.z_offset_method_type = probe[0] # The one found first
- self.z_offset_method_config = (
+ self.z_offset_config_method = (
probe[1],
"PROBE_CALIBRATE",
"Z_OFFSET_APPLY_PROBE",
@@ -256,16 +302,12 @@ def on_object_config(self, config: dict | list) -> None:
self._configure_option_cards(_keys)
elif isinstance(config, dict):
- if config.get("stepper_z"):
+ if _config := config.get("stepper_z"):
if self.block_z:
return
- else:
- self.block_z = True
+ self.block_z = True
_virtual_endstop = "probe:z_virtual_endstop"
- _config = config.get("stepper_z")
- if not _config:
- return
if _config.get("endstop_pin") == _virtual_endstop: # home with probe
return
self.z_offset_config_type = "endstop"
@@ -276,39 +318,8 @@ def on_object_config(self, config: dict | list) -> None:
)
self._configure_option_cards(["endstop"])
- if config.get("safe_z_home"):
- _config = config.get("safe_z_home")
- if not _config:
- return
- if _config.get("home_xy_position"):
- if not _config.get("home_xy_position"):
- return
- self.z_offset_safe_xy = tuple(
- map(
- lambda value: float(value),
- _config.get("home_xy_position").split(","),
- )
- )
- return
- if config.get("bed_mesh"):
- # TODO: This configuration needs to be prioritized over the safe_z_home
- # If available always use the zero reference xy
- # position for the probe calibration
- _config = config.get("bed_mesh")
- if not _config:
- return
- if not _config.get("zero_reference_position"):
- return
- self.z_offset_safe_xy = tuple(
- map(
- lambda value: float(value),
- _config.get("zero_reference_position").split(","),
- )
- )
- return
-
@QtCore.pyqtSlot(dict, name="on_printer_config")
- def on_printer_config(self, config: dict) -> None:
+ def on_printer_config(self, _config: dict) -> None:
"""Handle received printer config"""
_probe_types = [
"probe",
@@ -321,29 +332,13 @@ def on_printer_config(self, config: dict) -> None:
_probe_types, self.on_object_config
)
self.subscribe_config[str, "PyQt_PyObject"].emit(
- str("stepper_z"), self.on_object_config
- )
- self.subscribe_config[str, "PyQt_PyObject"].emit(
- str("safe_z_home"), self.on_object_config
- )
- self.subscribe_config[str, "PyQt_PyObject"].emit(
- str("bed_mesh"), self.on_object_config
+ "stepper_z", self.on_object_config
)
@QtCore.pyqtSlot(dict, name="on_available_gcode_cmds")
def on_available_gcode_cmds(self, gcode_cmds: dict) -> None:
"""Setup available probe calibration commands"""
- _available_commands = gcode_cmds.keys()
- if "PROBE_CALIBRATE" in _available_commands:
- self._calibration_commands.append("PROBE_CALIBRATE")
- if "PROBE_EDDY_CURRENT_CALIBRATE" in _available_commands:
- self._calibration_commands.append("PROBE_EDDY_CURRENT_CALIBRATE")
- if "LDC_CALIBRATE_DRIVE_CURRENT" in _available_commands:
- self._calibration_commands.append("LDC_CALIBRATE_DRIVE_CURRENT")
- if "Z_ENDSTOP_CALIBRATE" in _available_commands:
- self._calibration_commands.append("Z_ENDSTOP_CALIBRATE")
- if "MANUAL_PROBE" in _available_commands:
- self._calibration_commands.append("MANUAL_PROBE")
+ self._calibration_commands = gcode_cmds.keys() & _TRACKED_GCODES
def _verify_gcode(self, gcode: str) -> bool:
"""Check if the specified gcode exists
@@ -360,29 +355,23 @@ def _verify_gcode(self, gcode: str) -> bool:
return gcode in self._calibration_commands
def _build_calibration_command(self, tool: str) -> str:
+ """Return the calibration gcode command for the given tool name, or empty string if unavailable."""
if not tool:
return ""
if tool == "endstop":
if self._verify_gcode("Z_ENDSTOP_CALIBRATE"):
return "Z_ENDSTOP_CALIBRATE"
elif "eddy" in tool:
- if self._verify_gcode("PROBE_EDDY_CURRENT_CALIBRATE"):
- _name = tool.split(" ")[1]
- # if not _name:
- # return ""
- # return (
- # f"PROBE_EDDY_CURRENT_CALIBRATE CHIP={tool.split(' ')[1]}"
- # )
- return (
- f"PROBE_EDDY_CURRENT_CALIBRATE CHIP={tool.split(' ')[1]}"
- * bool(_name)
- ) + ("" * ~bool(_name))
-
- elif "probe" in tool or "bltouch" in tool:
+ parts = tool.split(" ", 1)
+ if len(parts) < 2 or not parts[1]:
+ return ""
+ if self._verify_gcode("LDC_CALIBRATE_DRIVE_CURRENT"):
+ return f"LDC_CALIBRATE_DRIVE_CURRENT CHIP={parts[1]}"
+ elif "probe" in tool or "bltouch" in tool or "smart_effector" in tool:
if self._verify_gcode("PROBE_CALIBRATE"):
- return "PROBE_CALIBRATE" + (
- str(" ") + f"SPEED={self.z_offset_calibration_speed}"
- ) * bool(self.z_offset_calibration_speed)
+ if self.z_offset_calibration_speed:
+ return f"PROBE_CALIBRATE SPEED={self.z_offset_calibration_speed}"
+ return "PROBE_CALIBRATE"
return ""
@QtCore.pyqtSlot(float, name="handle_zhopHeight_change")
@@ -403,7 +392,7 @@ def handle_zhopHeight_change(self, new_value: float) -> None:
self._zhop_height = new_value
@QtCore.pyqtSlot("PyQt_PyObject", name="handle_start_tool")
- def handle_start_tool(self, sender: typing.Type[OptionCard]) -> None:
+ def handle_start_tool(self, sender: OptionCard) -> None:
"""Handle probe tool helper start by sending
the correct gcode command according to the
clicked option card. This is achieved by
@@ -414,73 +403,83 @@ def handle_start_tool(self, sender: typing.Type[OptionCard]) -> None:
sender.
Args:
- sender (typing.Type[OptionCard]): The clicked OptionCard object
+ sender (OptionCard): The clicked OptionCard instance
"""
if not sender:
return
+ _name: str = sender.name # type: ignore
+ _cmd = self._build_calibration_command(_name)
+ if not _cmd:
+ return
+
+ self._active_calibration_tool = _name
for i in self.card_options.values():
i.setDisabled(True)
-
self.helper_initialize = True
- _timer = QtCore.QTimer()
- _timer.setSingleShot(True)
- _timer.timeout.connect(
- lambda: self.query_printer_object.emit({"manual_probe": None})
+ QtCore.QTimer.singleShot(
+ 300, lambda: self.query_printer_object.emit({"manual_probe": None})
)
- _timer.start(int(300))
- # self.query_printer_object.emit({"manual_probe": None})
- _cmd = self._build_calibration_command(sender.name) # type:ignore
- if not _cmd:
- return
-
self.disable_popups.emit(True)
- self.run_gcode_signal.emit("G28\nM400")
- if "eddy" in sender.name: # type:ignore
- self.call_load_panel.emit(True, "Preparing Eddy Current Calibration...")
+ self.lock_ui.emit(True)
+ _clean_nozzle = self._verify_gcode("CLEAN_NOZZLE")
+ if "eddy" in _name:
+ _name_parts = _name.split(" ", 1)
+ if len(_name_parts) < 2:
+ return
+ if _clean_nozzle:
+ self.call_load_panel.emit(True, "Cleaning nozzle...\nPlease wait")
+ self.run_gcode_signal.emit("CLEAN_NOZZLE")
+ else:
+ self.call_load_panel.emit(
+ True, "Calibrating drive current\nHoming axes..."
+ )
self.toggle_conn_page.emit(False)
- self._move_to_pos(self.z_offset_safe_xy[0], self.z_offset_safe_xy[1], 100)
- self.run_gcode_signal.emit(
- f"LDC_CALIBRATE_DRIVE_CURRENT CHIP={sender.name.split(' ')[1]}" # type:ignore
- )
- self.run_gcode_signal.emit("M400\nSAVE_CONFIG")
-
- self._eddy_command = _cmd
- self._eddy_calibration_state = True
+ self.run_gcode_signal.emit(_cmd)
+ self._eddy_command = f"PROBE_EDDY_CURRENT_CALIBRATE CHIP={_name_parts[1]}"
+ self._calib_phase = _CalibPhase.EDDY_PHASE1
return
+ if _clean_nozzle and _cmd != "Z_ENDSTOP_CALIBRATE":
+ self.call_load_panel.emit(True, "Cleaning nozzle...\nPlease wait")
+ self.run_gcode_signal.emit("CLEAN_NOZZLE")
else:
- if self.z_offset_safe_xy:
- self.call_load_panel.emit(True, "Homing Axes...")
- self._move_to_pos(
- self.z_offset_safe_xy[0], self.z_offset_safe_xy[1], 100
- )
+ self.call_load_panel.emit(True, "Starting calibration\nHoming axes...")
+ self._calib_phase = _CalibPhase.PROBE_ACTIVE
self.run_gcode_signal.emit(_cmd)
@QtCore.pyqtSlot(str, str, float, name="on_extruder_update")
def on_extruder_update(
- self, extruder_name: str, field: str, new_value: float
+ self, _extruder_name: str, field: str, new_value: float
) -> None:
"""Handle extruder update"""
- if not self.helper_initialize:
+ if self._calib_phase == _CalibPhase.IDLE:
return
- if self._eddy_calibration_state:
+ if field == "target":
+ prev_temp = self.target_temp
+ self.target_temp = round(new_value, 0)
+ if self.isVisible():
+ if self.target_temp > 0:
+ self.call_load_panel.emit(
+ True, "Heating nozzle\nCleaning before calibration..."
+ )
+ elif prev_temp > 0:
+ # Heater turned off — brushing is starting
+ self.call_load_panel.emit(True, "Cleaning nozzle...\nPlease wait")
return
if self.target_temp != 0:
if self.current_temp == self.target_temp:
- if self.isVisible:
- self.call_load_panel.emit(True, "Extruder heated up \n Please wait")
+ if self.isVisible():
+ self.call_load_panel.emit(
+ True, "Nozzle at temperature\nCleaning nozzle..."
+ )
return
if field == "temperature":
self.current_temp = round(new_value, 0)
- if self.isVisible:
+ if self.isVisible():
self.call_load_panel.emit(
True,
- f"Heating up ({new_value}/{self.target_temp}) \n Please wait",
+ f"Heating nozzle ({new_value}/{self.target_temp}°C)\nPlease wait...",
)
- if field == "target":
- self.target_temp = round(new_value, 0)
- if self.isVisible:
- self.call_load_panel.emit(True, "Cleaning the nozzle \n Please wait")
@QtCore.pyqtSlot(name="handle_accept")
def handle_accept(self) -> None:
@@ -489,37 +488,51 @@ def handle_accept(self) -> None:
return
self.helper_start = False
self._toggle_tool_buttons(False)
- self._show_option_cards()
- self.run_gcode_signal.emit(self.z_offset_config_method[2])
- self.run_gcode_signal.emit("M400")
- self.run_gcode_signal.emit(
- "SAVE_CONFIG"
- ) # Immediately save the new value and restart the host
+ if "eddy" in self.z_offset_method_type.lower():
+ self._calib_phase = _CalibPhase.EDDY_PHASE2
+ self.call_load_panel.emit(
+ True,
+ "Finalising Eddy calibration...\nThis may take a few minutes",
+ )
+ else:
+ self._show_option_cards()
+ self._calib_phase = _CalibPhase.SAVE_RESTART
+ self.call_load_panel.emit(
+ True, "Saving configuration...\nMachine will restart"
+ )
+ self.toggle_conn_page.emit(False)
+ self.run_gcode_signal.emit("ACCEPT")
+ self.run_gcode_signal.emit("SAVE_CONFIG")
@QtCore.pyqtSlot(name="handle_abort")
def handle_abort(self) -> None:
"""Aborts the calibration procedure"""
if not self.helper_start:
return
- self.helper_start = False
- self._toggle_tool_buttons(False)
- self._show_option_cards()
+ self._cancel_calibration()
self.run_gcode_signal.emit("ABORT")
@QtCore.pyqtSlot(str, list, name="on_gcode_move_update")
- def on_gcode_move_update(self, name: str, value: list) -> None:
- """Handle gcode move update"""
- if not value:
+ def on_gcode_move_update(self, _name: str, _value: list) -> None:
+ """Update loading message once homing completes after nozzle cleaning."""
+ if (
+ self._calib_phase
+ not in (
+ _CalibPhase.EDDY_PHASE1,
+ _CalibPhase.PROBE_ACTIVE,
+ )
+ or self.target_temp != 0
+ ):
return
-
- _fields = [
- "absolute_coordinates",
- "absolute_extrude",
- "homing_origin",
- "position",
- "gcode_position",
- ]
- ...
+ if _name == "homing_origin" and self.isVisible():
+ if self._calib_phase == _CalibPhase.EDDY_PHASE1:
+ self.call_load_panel.emit(
+ True, "Calibrating drive current\nPlease wait..."
+ )
+ else:
+ self.call_load_panel.emit(
+ True, "Moving to calibration position\nPlease wait..."
+ )
@QtCore.pyqtSlot(dict, name="on_manual_probe_update")
def on_manual_probe_update(self, update: dict) -> None:
@@ -527,27 +540,31 @@ def on_manual_probe_update(self, update: dict) -> None:
if not update:
return
- # if update.get("z_position_lower"):
- # f"{update.get('z_position_lower'):.4f} mm"
-
is_active = update.get("is_active", None)
- if update.get("z_position_upper"):
- self.old_offset_info.setText(f"{update.get('z_position_upper'):.4f} mm")
- if update.get("z_position"):
- self.current_offset_info.setText(f"{update.get('z_position'):.4f} mm")
+ if (z_upper := update.get("z_position_upper")) is not None:
+ self.old_offset_info.setText(f"{round(z_upper, 3) or 0.0:.3f} mm")
+ if (z_pos := update.get("z_position")) is not None:
+ self.current_offset_info.setText(f"{round(z_pos, 3) or 0.0:.3f} mm")
- if not is_active:
+ if is_active is None:
return
if not self.isVisible():
self.request_page_view.emit()
# Shared state updates
self.helper_initialize = False
+ _was_active = self.helper_start
self.helper_start = is_active
+ if is_active and self._calib_phase == _CalibPhase.PROBE_ACTIVE:
+ # Probe session started — CLEAN_NOZZLE/homing phase is over.
+ self._calib_phase = _CalibPhase.IDLE
+ elif not is_active and _was_active:
+ # A manual probe session ended (external abort or normal completion).
+ self._calib_phase = _CalibPhase.IDLE
# UI updates
self._toggle_tool_buttons(is_active)
if is_active:
self._hide_option_cards()
- else:
+ elif self._calib_phase == _CalibPhase.IDLE:
self._show_option_cards()
@QtCore.pyqtSlot(list, name="handle_gcode_response")
@@ -558,6 +575,8 @@ def handle_gcode_response(self, data: list) -> None:
data (list): A list containing the gcode that originated
the response and the response
"""
+ if not data:
+ return
if self.isVisible():
if data[0].startswith("!!"): # An error occurred
if "already in a manual z probe" in data[0].strip("!! ").lower():
@@ -568,35 +587,60 @@ def handle_gcode_response(self, data: list) -> None:
self._show_option_cards()
self.helper_start = False
self._toggle_tool_buttons(False)
-
- # elif data[0].startswith("// "): ...
+ error_msg = data[0].removeprefix("!! ")
+ self.show_notifications.emit("probe_helper", error_msg, 3, True)
@QtCore.pyqtSlot(list, name="handle_error_response")
def handle_error_response(self, data: list) -> None:
"""Handle received error response"""
- ...
- # _data, _metadata, *extra = data + [None] * max(0, 2 - len(data))
-
- def _move_to_pos(self, x, y, speed) -> None:
- self.run_gcode_signal.emit(f"G91\nG1 Z5 F{10 * 60}\nM400")
- self.run_gcode_signal.emit(f"G90\nG1 X{x} Y{y} F{speed * 60}\nM400")
- return
+ if not data:
+ return
+ raw = data[0]
+ if isinstance(raw, dict):
+ error_msg = raw.get("message", "Unknown error")
+ else:
+ error_msg = str(raw)
+ # Eddy phase 1 and phase 2 both end with SAVE_CONFIG which restarts Klipper.
+ if (
+ not self.helper_start
+ and self._calib_phase != _CalibPhase.IDLE
+ and (self._calib_phase != _CalibPhase.EDDY_PHASE1 or self._eddy_command)
+ ):
+ logger.debug(
+ "Suppressing error during eddy phase-1 SAVE_CONFIG restart: %s",
+ error_msg,
+ )
+ return
+ logger.error("Error Response: %s", error_msg)
+ self._cancel_calibration()
+ self.show_notifications.emit("probe_helper", error_msg, 3, True)
- def _setup_move_option_buttons(self) -> None:
- """Change move_option_x buttons text for configured
- zhop values in stored in the class variable `distances`
+ def _reset_calibration_state(self) -> None:
+ """Reset all calibration state and temp tracking to idle."""
+ self.helper_start = False
+ self.helper_initialize = False
+ self._calib_phase = _CalibPhase.IDLE
+ self._eddy_command = ""
+ self._active_calibration_tool = ""
+ self.target_temp = 0
+ self.current_temp = 0
- `distances` Has the values from lowest to maximum zhop
- """
- if self.distances:
- return
- self.move_option_1.setText(str(self.distances[0]))
- self.move_option_2.setText(str(self.distances[1]))
- self.move_option_3.setText(str(self.distances[2]))
- self.move_option_4.setText(str(self.distances[3]))
- self.move_option_5.setText(str(self.distances[4]))
+ def _restore_ui(self) -> None:
+ """Dismiss loading overlay and re-enable navigation."""
+ self._show_option_cards()
+ self.call_load_panel.emit(False, "")
+ self.disable_popups.emit(False)
+ self.lock_ui.emit(False)
+ self.toggle_conn_page.emit(True)
+
+ def _cancel_calibration(self) -> None:
+ """Full reset: clear state, hide tool buttons, restore UI."""
+ self._reset_calibration_state()
+ self._toggle_tool_buttons(False)
+ self._restore_ui()
def _toggle_tool_buttons(self, state: bool) -> None:
+ """Show/hide and enable/disable calibration tool buttons based on active state."""
self.mb_lower_nozzle.setEnabled(state)
self.mb_raise_nozzle.setEnabled(state)
self.accept_button.setEnabled(state)
@@ -606,6 +650,7 @@ def _toggle_tool_buttons(self, state: bool) -> None:
if state:
for i in self.card_options.values():
i.setDisabled(False)
+ self.lock_ui.emit(True)
self.call_load_panel.emit(False, "")
self.po_back_button.setEnabled(False)
self.po_back_button.hide()
@@ -615,6 +660,7 @@ def _toggle_tool_buttons(self, state: bool) -> None:
self.old_offset_info.show()
self.bbp_offset_steps_buttons_group_box.show()
self.current_offset_info.show()
+ self.abort_button.show()
self.tool_image.show()
self.mb_raise_nozzle.show()
self.mb_lower_nozzle.show()
@@ -633,7 +679,9 @@ def _toggle_tool_buttons(self, state: bool) -> None:
self.po_header_title.show()
self.separator_line.show()
self.bbp_offset_steps_buttons_group_box.hide()
+ self.old_offset_info.setText("0.000 mm")
self.old_offset_info.hide()
+ self.current_offset_info.setText("0.000 mm")
self.current_offset_info.hide()
self.tool_image.hide()
self.mb_raise_nozzle.hide()
@@ -647,9 +695,30 @@ def _toggle_tool_buttons(self, state: bool) -> None:
)
self.update()
- return
+
+ def _create_move_button(
+ self,
+ parent: QtWidgets.QWidget,
+ label: str,
+ obj_name: str,
+ checked: bool,
+ font: QtGui.QFont,
+ ) -> BlocksCustomCheckButton:
+ """Create a single move-step check button."""
+ btn = BlocksCustomCheckButton(parent=parent)
+ btn.setMinimumSize(QtCore.QSize(100, 60))
+ btn.setMaximumSize(QtCore.QSize(100, 60))
+ btn.setText(label)
+ btn.setFont(font)
+ btn.setCheckable(True)
+ btn.setChecked(checked)
+ btn.setFlat(True)
+ btn.setProperty("button_type", "")
+ btn.setObjectName(obj_name)
+ return btn
def _setupUi(self) -> None:
+ """Build and lay out all UI elements for the probe helper page."""
self.bbp_offset_value_selector_group = QtWidgets.QButtonGroup(self)
self.bbp_offset_value_selector_group.setExclusive(True)
sizePolicy = QtWidgets.QSizePolicy(
@@ -714,7 +783,7 @@ def _setupUi(self) -> None:
self.abort_button = BlocksCustomButton(self)
self.abort_button.setGeometry(QtCore.QRect(300, 340, 170, 60))
self.abort_button.setText("Abort")
- self.abort_button.setObjectName("accept_button")
+ self.abort_button.setObjectName("abort_button")
self.abort_button.setPixmap(QtGui.QPixmap(":/dialog/media/btn_icons/no.svg"))
self.abort_button.setVisible(False)
font = QtGui.QFont()
@@ -786,126 +855,27 @@ def _setupUi(self) -> None:
self.bbp_offset_steps_buttons.setContentsMargins(9, 9, 9, 9)
self.bbp_offset_steps_buttons.setObjectName("bbp_offset_steps_buttons")
- # 0.1mm button
- self.move_option_1 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.move_option_1.setMinimumSize(QtCore.QSize(100, 60))
- self.move_option_1.setMaximumSize(QtCore.QSize(100, 60))
- self.move_option_1.setText("0.01 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.move_option_1.setFont(font)
- self.move_option_1.setCheckable(True)
- self.move_option_1.setChecked(True) # Set as initially checked
- self.move_option_1.setFlat(True)
- self.move_option_1.setProperty("button_type", "")
- self.move_option_1.setObjectName("move_option_1")
- self.bbp_offset_value_selector_group.addButton(self.move_option_1)
- self.bbp_offset_steps_buttons.addWidget(
- self.move_option_1,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # 0.01mm button
- self.move_option_2 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.move_option_2.setMinimumSize(QtCore.QSize(100, 60))
- self.move_option_2.setMaximumSize(
- QtCore.QSize(100, 60)
- ) # Increased max width by 5 pixels
- self.move_option_2.setText("0.25 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.move_option_2.setFont(font)
- self.move_option_2.setCheckable(True)
- self.move_option_2.setFlat(True)
- self.move_option_2.setProperty("button_type", "")
- self.move_option_2.setObjectName("move_option_2")
- self.bbp_offset_value_selector_group.addButton(self.move_option_2)
- self.bbp_offset_steps_buttons.addWidget(
- self.move_option_2,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # 0.05mm button
- self.move_option_3 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.move_option_3.setMinimumSize(QtCore.QSize(100, 60))
- self.move_option_3.setMaximumSize(
- QtCore.QSize(100, 60)
- ) # Increased max width by 5 pixels
- self.move_option_3.setText("0.1 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.move_option_3.setFont(font)
- self.move_option_3.setCheckable(True)
- self.move_option_3.setFlat(True)
- self.move_option_3.setProperty("button_type", "")
- self.move_option_3.setObjectName("move_option_3")
- self.bbp_offset_value_selector_group.addButton(self.move_option_3)
- self.bbp_offset_steps_buttons.addWidget(
- self.move_option_3,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # 0.025mm button
- self.move_option_4 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.move_option_4.setMinimumSize(QtCore.QSize(100, 60))
- self.move_option_4.setMaximumSize(
- QtCore.QSize(100, 60)
- ) # Increased max width by 5 pixels
- self.move_option_4.setText("0.5 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.move_option_4.setFont(font)
- self.move_option_4.setCheckable(True)
- self.move_option_4.setFlat(True)
- self.move_option_4.setProperty("button_type", "")
- self.move_option_4.setObjectName("move_option_4")
- self.bbp_offset_value_selector_group.addButton(self.move_option_4)
- self.bbp_offset_steps_buttons.addWidget(
- self.move_option_4,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # 0.01mm button
- self.move_option_5 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.move_option_5.setMinimumSize(QtCore.QSize(100, 60))
- self.move_option_5.setMaximumSize(
- QtCore.QSize(100, 60)
- ) # Increased max width by 5 pixels
- self.move_option_5.setText("1 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.move_option_5.setFont(font)
- self.move_option_5.setCheckable(True)
- self.move_option_5.setFlat(True)
- self.move_option_5.setProperty("button_type", "")
- self.move_option_5.setObjectName("move_option_4")
- self.bbp_offset_value_selector_group.addButton(self.move_option_5)
- self.bbp_offset_steps_buttons.addWidget(
- self.move_option_5,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # Line separator for 0.025mm - set size policy to expanding horizontally
+ move_font = QtGui.QFont()
+ move_font.setPointSize(14)
+ center = (
+ QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter
+ )
+ for label, value, obj_name, checked in _PROBE_MOVE_STEPS:
+ btn = self._create_move_button(
+ self.bbp_offset_steps_buttons_group_box,
+ label,
+ obj_name,
+ checked,
+ move_font,
+ )
+ btn.toggled.connect(
+ lambda checked_state, v=value: (
+ checked_state and self.handle_zhopHeight_change(new_value=v)
+ )
+ )
+ setattr(self, obj_name, btn)
+ self.bbp_offset_value_selector_group.addButton(btn)
+ self.bbp_offset_steps_buttons.addWidget(btn, 0, center)
# Set the layout for the group box
self.bbp_offset_steps_buttons_group_box.setLayout(self.bbp_offset_steps_buttons)
@@ -944,7 +914,6 @@ def _setupUi(self) -> None:
self.old_offset_info.setFont(font)
# Set color to white to be visible on the dark background
self.old_offset_info.setStyleSheet("color: gray; background: transparent;")
- self.old_offset_info.setText("Z-Offset")
self.old_offset_info.setObjectName("old_offset_info")
self.old_offset_info.setText("0 mm")
@@ -1021,8 +990,6 @@ def _setupUi(self) -> None:
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
- self.main_content_horizontal_layout.addItem(self.spacerItem)
-
# Add move buttons layout LAST for right placement
self.main_content_horizontal_layout.addLayout(self.bbp_buttons_layout)
diff --git a/BlocksScreen/lib/panels/widgets/tunePage.py b/BlocksScreen/lib/panels/widgets/tunePage.py
index 301c5cd1..1b68c2f7 100644
--- a/BlocksScreen/lib/panels/widgets/tunePage.py
+++ b/BlocksScreen/lib/panels/widgets/tunePage.py
@@ -1,5 +1,6 @@
import re
import typing
+import logging
from helper_methods import normalize
from lib.utils.blocks_button import BlocksCustomButton
@@ -8,6 +9,9 @@
from PyQt6 import QtCore, QtGui, QtWidgets
+_logger = logging.getLogger(__name__)
+
+
class TuneWidget(QtWidgets.QWidget):
request_back: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
name="request_back_page"
@@ -45,31 +49,51 @@ def __init__(self, parent) -> None:
self.sensors_menu_btn.clicked.connect(self.request_sensorsPage.emit)
self.tune_babystep_menu_btn.clicked.connect(self.request_bbpPage.emit)
self.tune_back_btn.clicked.connect(self.request_back)
- self.bed_display.clicked.connect(
- lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit(
- "Bed",
- int(round(self.bed_target)),
- self.on_numpad_change,
- 0,
- 120, # TODO: Get this value from printer objects
+ self.speed_display.clicked.connect(
+ lambda: self.request_sliderPage[str, int, "PyQt_PyObject", int, int].emit(
+ "Speed",
+ int(self.speed_factor_override * 100),
+ self.on_slider_change,
+ 10,
+ 300,
)
)
+
+ @QtCore.pyqtSlot(dict, name="printer_config")
+ def on_printer_config(self, config: dict) -> None:
+ """Slot that receives the full printer configuration,
+
+ Additionally, this method configures the signal connections
+ between controllable heaters and numpad calls
+ """
+ try:
+ self.extruder_display.clicked.disconnect()
+ self.bed_display.clicked.disconnect()
+ except Exception:
+ _logger.debug("Signals were not connected")
+ extruder = config.get("extruder", None) or {}
+ bed = config.get("heater_bed", None) or {}
+ e_min_temp = extruder.get("min_temp", 0)
+ e_max_temp = extruder.get("max_temp", 300)
+ b_max_temp = bed.get("max_temp", 100)
+ b_min_temp = bed.get("min_temp", 0)
+ # Configure numpads
self.extruder_display.clicked.connect(
lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit(
"Extruder",
int(round(self.extruder_target)),
self.on_numpad_change,
- 0,
- 300, # TODO: Get this value from printer objects
+ int(e_min_temp),
+ int(e_max_temp),
)
)
- self.speed_display.clicked.connect(
- lambda: self.request_sliderPage[str, int, "PyQt_PyObject", int, int].emit(
- "Speed",
- int(self.speed_factor_override * 100),
- self.on_slider_change,
- 10,
- 300,
+ self.bed_display.clicked.connect(
+ lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit(
+ "Bed",
+ int(round(self.bed_target)),
+ self.on_numpad_change,
+ int(b_min_temp),
+ int(b_max_temp),
)
)
diff --git a/BlocksScreen/lib/printer.py b/BlocksScreen/lib/printer.py
index c6c76fbc..67aecb55 100644
--- a/BlocksScreen/lib/printer.py
+++ b/BlocksScreen/lib/printer.py
@@ -30,6 +30,8 @@ class Printer(QtCore.QObject):
[str, float], [str, str], name="idle_timeout_update"
)
+ save_variables_update = QtCore.pyqtSignal(dict, name="save_variables_update")
+
gcode_move_update = QtCore.pyqtSignal(
[str, list], [str, float], [str, bool], name="gcode_move_update"
)
@@ -62,8 +64,8 @@ class Printer(QtCore.QObject):
gcode_macro_update = QtCore.pyqtSignal(str, dict, name="gcode_macro_update")
webhooks_update = QtCore.pyqtSignal(str, str, name="webhooks_update")
- load_filament_update = QtCore.pyqtSignal(bool, name="load_filament_update")
- unload_filament_update = QtCore.pyqtSignal(bool, name="unload_filament_update")
+ load_filament_update = QtCore.pyqtSignal(dict, name="load_filament_update")
+ unload_filament_update = QtCore.pyqtSignal(dict, name="unload_filament_update")
query_printer_object = QtCore.pyqtSignal(dict, name="query_printer_object")
save_config_pending: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
@@ -328,6 +330,11 @@ def _webhooks_object_updated(self, value: dict, name: str = "webhooks") -> None:
e,
)
+ def _save_variables_object_updated(
+ self, value: dict, name: str = "saved_variables"
+ ) -> None:
+ self.save_variables_update.emit(value)
+
def _gcode_move_object_updated(self, value: dict, name: str = "gcode_move") -> None:
if "speed_factor" in value.keys():
self.gcode_move_update[str, float].emit(
@@ -734,9 +741,7 @@ def _temperature_probe_object_updated(self, values: dict, name: str) -> None:
# TODO: testing needed here idk if does work
def _unload_filament_object_updated(self, values: dict, name: str) -> None:
- if "state" in values.keys():
- self.unload_filament_update[bool].emit(values["state"])
+ self.unload_filament_update[dict].emit(values)
def _load_filament_object_updated(self, values: dict, name: str) -> None:
- if "state" in values.keys():
- self.load_filament_update[bool].emit(values["state"])
+ self.load_filament_update[dict].emit(values)
diff --git a/BlocksScreen/lib/ui/filamentStackedWidget.ui b/BlocksScreen/lib/ui/filamentStackedWidget.ui
index e9d359a6..c418ca80 100644
--- a/BlocksScreen/lib/ui/filamentStackedWidget.ui
+++ b/BlocksScreen/lib/ui/filamentStackedWidget.ui
@@ -75,10 +75,26 @@
-
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 60
+ 0
+
+
+
+
-
-
+
0
0
@@ -118,6 +134,69 @@
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 60
+ 60
+
+
+
+
+ 60
+ 60
+
+
+
+
+ Momcake
+ 20
+ false
+ PreferAntialias
+
+
+
+ false
+
+
+ true
+
+
+ Qt::NoContextMenu
+
+
+ Qt::LeftToRight
+
+
+
+
+
+ Back
+
+
+ false
+
+
+ true
+
+
+ menu_btn
+
+
+ icon
+
+
+ :/ui/media/btn_icons/back.svg
+
+
+
-
@@ -137,125 +216,6 @@
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 600
- 80
-
-
-
-
- 600
- 80
-
-
-
- QFrame::StyledPanel
-
-
- QFrame::Raised
-
-
-
-
- 20
- 20
- 201
- 41
-
-
-
-
- 0
- 0
-
-
-
-
- 280
- 60
-
-
-
-
- 15
-
-
-
- background: transparent; color:white ;
-
-
- Filament Name
-
-
- Qt::AlignCenter
-
-
- Qt::NoTextInteraction
-
-
-
-
-
- 260
- 10
- 321
- 60
-
-
-
-
- 0
- 60
-
-
-
-
- 16777215
- 60
-
-
-
-
- 13
-
-
-
- color:white
-
-
- ...
-
-
- Qt::AlignCenter
-
-
-
-
-
- 240
- 10
- 3
- 61
-
-
-
- color:white
-
-
- Qt::Vertical
-
-
-
-
-
@@ -1624,6 +1584,8 @@ Filament
+
+
diff --git a/BlocksScreen/lib/ui/filamentStackedWidget_ui.py b/BlocksScreen/lib/ui/filamentStackedWidget_ui.py
index bd545139..8ec597b2 100644
--- a/BlocksScreen/lib/ui/filamentStackedWidget_ui.py
+++ b/BlocksScreen/lib/ui/filamentStackedWidget_ui.py
@@ -1,6 +1,6 @@
-# Form implementation generated from reading ui file '/home/levi/main/Blocks_Screen/BlocksScreen/lib/ui/filamentStackedWidget.ui'
+# Form implementation generated from reading ui file 'filamentStackedWidget.ui'
#
-# Created by: PyQt6 UI code generator 6.7.1
+# Created by: PyQt6 UI code generator 6.10.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
@@ -36,8 +36,10 @@ def setupUi(self, filamentStackedWidget):
self.verticalLayout.addItem(spacerItem)
self.filament_page_header_layout = QtWidgets.QHBoxLayout()
self.filament_page_header_layout.setObjectName("filament_page_header_layout")
+ self.spacerItem1 = QtWidgets.QSpacerItem(60, 0, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
+ self.filament_page_header_layout.addItem(self.spacerItem1)
self.filament_page_header_title = QtWidgets.QLabel(parent=self.filament_control_page)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.filament_page_header_title.sizePolicy().hasHeightForWidth())
@@ -53,54 +55,39 @@ def setupUi(self, filamentStackedWidget):
self.filament_page_header_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.filament_page_header_title.setObjectName("filament_page_header_title")
self.filament_page_header_layout.addWidget(self.filament_page_header_title, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignVCenter)
+ self.main_back_button = IconButton(parent=self.filament_control_page)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.main_back_button.sizePolicy().hasHeightForWidth())
+ self.main_back_button.setSizePolicy(sizePolicy)
+ self.main_back_button.setMinimumSize(QtCore.QSize(60, 60))
+ self.main_back_button.setMaximumSize(QtCore.QSize(60, 60))
+ font = QtGui.QFont()
+ font.setFamily("Momcake")
+ font.setPointSize(20)
+ font.setItalic(False)
+ font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias)
+ self.main_back_button.setFont(font)
+ self.main_back_button.setMouseTracking(False)
+ self.main_back_button.setTabletTracking(True)
+ self.main_back_button.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu)
+ self.main_back_button.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
+ self.main_back_button.setStyleSheet("")
+ self.main_back_button.setAutoDefault(False)
+ self.main_back_button.setFlat(True)
+ self.main_back_button.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/back.svg"))
+ self.main_back_button.setObjectName("main_back_button")
+ self.filament_page_header_layout.addWidget(self.main_back_button)
self.verticalLayout.addLayout(self.filament_page_header_layout)
- spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout.addItem(spacerItem1)
+ spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
+ self.verticalLayout.addItem(spacerItem2)
self.verticalLayout_4 = QtWidgets.QVBoxLayout()
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3")
- self.frame_8 = BlocksCustomFrame(parent=self.filament_control_page)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.frame_8.sizePolicy().hasHeightForWidth())
- self.frame_8.setSizePolicy(sizePolicy)
- self.frame_8.setMinimumSize(QtCore.QSize(600, 80))
- self.frame_8.setMaximumSize(QtCore.QSize(600, 80))
- self.frame_8.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
- self.frame_8.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
- self.frame_8.setObjectName("frame_8")
- self.filament_page_info_content_6 = QtWidgets.QLabel(parent=self.frame_8)
- self.filament_page_info_content_6.setGeometry(QtCore.QRect(20, 20, 201, 41))
- self.filament_page_info_content_6.setMinimumSize(QtCore.QSize(0, 0))
- self.filament_page_info_content_6.setMaximumSize(QtCore.QSize(280, 60))
- font = QtGui.QFont()
- font.setPointSize(15)
- self.filament_page_info_content_6.setFont(font)
- self.filament_page_info_content_6.setStyleSheet("background: transparent; color:white ;")
- self.filament_page_info_content_6.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
- self.filament_page_info_content_6.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.NoTextInteraction)
- self.filament_page_info_content_6.setObjectName("filament_page_info_content_6")
- self.label = QtWidgets.QLabel(parent=self.frame_8)
- self.label.setGeometry(QtCore.QRect(260, 10, 321, 60))
- self.label.setMinimumSize(QtCore.QSize(0, 60))
- self.label.setMaximumSize(QtCore.QSize(16777215, 60))
- font = QtGui.QFont()
- font.setPointSize(13)
- self.label.setFont(font)
- self.label.setStyleSheet("color:white")
- self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
- self.label.setObjectName("label")
- self.line = QtWidgets.QFrame(parent=self.frame_8)
- self.line.setGeometry(QtCore.QRect(240, 10, 3, 61))
- self.line.setStyleSheet("color:white")
- self.line.setFrameShape(QtWidgets.QFrame.Shape.VLine)
- self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
- self.line.setObjectName("line")
- self.verticalLayout_3.addWidget(self.frame_8, 0, QtCore.Qt.AlignmentFlag.AlignHCenter)
- spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout_3.addItem(spacerItem2)
+ spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
+ self.verticalLayout_3.addItem(spacerItem3)
self.frame_7 = BlocksCustomFrame(parent=self.filament_control_page)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
@@ -138,8 +125,8 @@ def setupUi(self, filamentStackedWidget):
self.line_2.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
self.line_2.setObjectName("line_2")
self.verticalLayout_3.addWidget(self.frame_7, 0, QtCore.Qt.AlignmentFlag.AlignHCenter)
- spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout_3.addItem(spacerItem3)
+ spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
+ self.verticalLayout_3.addItem(spacerItem4)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.filament_page_load_btn = BlocksCustomButton(parent=self.filament_control_page)
@@ -193,10 +180,10 @@ def setupUi(self, filamentStackedWidget):
self.verticalLayout_3.addLayout(self.horizontalLayout)
self.verticalLayout_4.addLayout(self.verticalLayout_3)
self.verticalLayout.addLayout(self.verticalLayout_4)
- spacerItem4 = QtWidgets.QSpacerItem(20, 26, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
- self.verticalLayout.addItem(spacerItem4)
- spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
+ spacerItem5 = QtWidgets.QSpacerItem(20, 26, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
self.verticalLayout.addItem(spacerItem5)
+ spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
+ self.verticalLayout.addItem(spacerItem6)
filamentStackedWidget.addWidget(self.filament_control_page)
self.load_page = QtWidgets.QWidget()
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding)
@@ -210,12 +197,12 @@ def setupUi(self, filamentStackedWidget):
self.load_page.setObjectName("load_page")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.load_page)
self.verticalLayout_2.setObjectName("verticalLayout_2")
- spacerItem6 = QtWidgets.QSpacerItem(20, 24, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
- self.verticalLayout_2.addItem(spacerItem6)
+ spacerItem7 = QtWidgets.QSpacerItem(20, 24, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
+ self.verticalLayout_2.addItem(spacerItem7)
self.load_page_header_layout = QtWidgets.QHBoxLayout()
self.load_page_header_layout.setObjectName("load_page_header_layout")
- spacerItem7 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Minimum)
- self.load_page_header_layout.addItem(spacerItem7)
+ spacerItem8 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Minimum)
+ self.load_page_header_layout.addItem(spacerItem8)
self.load_header_page_title = QtWidgets.QLabel(parent=self.load_page)
self.load_header_page_title.setMinimumSize(QtCore.QSize(0, 60))
font = QtGui.QFont()
@@ -251,8 +238,8 @@ def setupUi(self, filamentStackedWidget):
self.load_header_back_button.setObjectName("load_header_back_button")
self.load_page_header_layout.addWidget(self.load_header_back_button)
self.verticalLayout_2.addLayout(self.load_page_header_layout)
- spacerItem8 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout_2.addItem(spacerItem8)
+ spacerItem9 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
+ self.verticalLayout_2.addItem(spacerItem9)
self.load_page_content_layout = QtWidgets.QGridLayout()
self.load_page_content_layout.setContentsMargins(5, 5, 5, 5)
self.load_page_content_layout.setHorizontalSpacing(6)
@@ -428,8 +415,8 @@ def setupUi(self, filamentStackedWidget):
self.load_custom_btn.setObjectName("load_custom_btn")
self.load_page_footer_layout.addWidget(self.load_custom_btn)
self.verticalLayout_2.addLayout(self.load_page_footer_layout)
- spacerItem9 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout_2.addItem(spacerItem9)
+ spacerItem10 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
+ self.verticalLayout_2.addItem(spacerItem10)
filamentStackedWidget.addWidget(self.load_page)
self.custom_filament_page = QtWidgets.QWidget()
self.custom_filament_page.setMinimumSize(QtCore.QSize(710, 400))
@@ -587,8 +574,9 @@ def retranslateUi(self, filamentStackedWidget):
filamentStackedWidget.setWindowTitle(_translate("filamentStackedWidget", "StackedWidget"))
self.filament_page_header_title.setText(_translate("filamentStackedWidget", "Filament Control"))
self.filament_page_header_title.setProperty("class", _translate("filamentStackedWidget", "title_text"))
- self.filament_page_info_content_6.setText(_translate("filamentStackedWidget", "Filament Name"))
- self.label.setText(_translate("filamentStackedWidget", "..."))
+ self.main_back_button.setText(_translate("filamentStackedWidget", "Back"))
+ self.main_back_button.setProperty("class", _translate("filamentStackedWidget", "menu_btn"))
+ self.main_back_button.setProperty("button_type", _translate("filamentStackedWidget", "icon"))
self.filament_page_info_title_6.setText(_translate("filamentStackedWidget", "Loaded Filament Type"))
self.label_2.setText(_translate("filamentStackedWidget", "..."))
self.filament_page_load_btn.setText(_translate("filamentStackedWidget", "Load"))
@@ -638,3 +626,13 @@ def retranslateUi(self, filamentStackedWidget):
from lib.utils.blocks_button import BlocksCustomButton
from lib.utils.blocks_frame import BlocksCustomFrame
from lib.utils.icon_button import IconButton
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ filamentStackedWidget = QtWidgets.QStackedWidget()
+ ui = Ui_filamentStackedWidget()
+ ui.setupUi(filamentStackedWidget)
+ filamentStackedWidget.show()
+ sys.exit(app.exec())