diff --git a/BlocksScreen/lib/panels/printTab.py b/BlocksScreen/lib/panels/printTab.py index 65927aae..f5a53be9 100644 --- a/BlocksScreen/lib/panels/printTab.py +++ b/BlocksScreen/lib/panels/printTab.py @@ -65,9 +65,6 @@ class PrintTab(QtWidgets.QStackedWidget): 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 def __init__( self, @@ -77,6 +74,9 @@ def __init__( printer: Printer, ) -> None: super().__init__(parent) + self._active_z_offset: float = 0.0 + self._finish_print_handled: bool = False + self._z_apply_command: str = "Z_OFFSET_APPLY_ENDSTOP" self.setupMainPrintPage() self.ws: MoonWebSocket = ws @@ -267,7 +267,6 @@ 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) @QtCore.pyqtSlot(str, dict, name="on_print_stats_update") @QtCore.pyqtSlot(str, float, name="on_print_stats_update") @@ -321,8 +320,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,25 +333,35 @@ 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.babystepPage.bbp_z_offset_title_label.setText( + f"Z: {self._active_z_offset:.3f}mm" + ) 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._active_z_offset:.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): - """Runs the `SAVE_CONFIG` gcode""" - self.run_gcode_signal.emit("Z_OFFSET_APPLY_PROBE") + def update_configuration_file(self) -> None: + """Restore the captured offset, apply it to the probe config, then save.""" + 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._active_z_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.save_config_btn.setVisible(False) @QtCore.pyqtSlot(str, list, name="activate_save_button") def activate_save_button(self, name: str, value: list) -> None: @@ -357,15 +370,19 @@ def activate_save_button(self, name: str, value: list) -> None: return if name == "homing_origin": - self._active_z_offset = value[2] - self.save_config_btn.setVisible(value[2] != 0) + if len(value) > 2: + self._active_z_offset = value[2] + self.save_config_btn.setVisible(value[2] != 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 @@ -404,6 +421,17 @@ def klipper_ready_signal(self) -> None: """React to klipper ready signal""" self.babystepPage.baby_stepchange = False self._finish_print_handled = False + 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: diff --git a/BlocksScreen/lib/panels/widgets/babystepPage.py b/BlocksScreen/lib/panels/widgets/babystepPage.py index 273e8f9c..24e04b20 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.1 mm", 0.1, "bbp_nozzle_offset_1", True), + ("0.05 mm", 0.05, "bbp_nozzle_offset_05", False), + ("0.025 mm", 0.025, "bbp_nozzle_offset_025", False), + ("0.01 mm", 0.01, "bbp_nozzle_offset_01", 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" ) @@ -24,19 +37,18 @@ def __init__(self, parent) -> None: 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.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 @@ -47,84 +59,107 @@ def baby_stepchange(self, value: bool) -> None: @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: {self._pending_z_offset:.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: {self._pending_z_offset:.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 - - *** - - Possible values are: 0.01, 0.025, 0.05, 0.1 **mm** - """ + """Update step size from the clicked offset button text.""" _sender: QtCore.QObject | None = self.sender() - if self._z_offset == float(_sender.text()[:-3]): + if _sender is None: + return + if not isinstance(_sender, QtWidgets.QAbstractButton): + return + try: + _value = float(_sender.text()[:-3]) + except ValueError: + logger.warning( + "handle_z_offset_change: could not parse button text %r", + _sender.text(), + ) + return + if self._z_offset == _value: return - self._z_offset = float(_sender.text()[:-3]) + self._z_offset = _value 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: {confirmed:.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: {confirmed:.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) + btn.toggled.connect(self.handle_z_offset_change) + 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 = QtWidgets.QHBoxLayout() - # 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() + header.addItem( + QtWidgets.QSpacerItem( + 60, + 20, + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Minimum, + ) ) - 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) + + title_font = QtGui.QFont() + title_font.setPointSize(22) palette = QtGui.QPalette() palette.setColor( palette.ColorGroup.All, @@ -136,187 +171,68 @@ def setupUI(self): palette.ColorRole.WindowText, QtGui.QColor("#FFFFFF"), ) + + self.bbp_header_title = QtWidgets.QLabel("Babystep", parent=self) + self.bbp_header_title.setSizePolicy(size_policy) + 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") + header.addWidget(self.bbp_header_title, 0, QtCore.Qt.AlignmentFlag.AlignCenter) - spacerItem = QtWidgets.QSpacerItem( - 60, - 20, - QtWidgets.QSizePolicy.Policy.Expanding, - QtWidgets.QSizePolicy.Policy.Minimum, - ) - self.bbp_header_layout.addItem(spacerItem) - - 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( + header.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.setStretch(0, 1) + 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 + ) + 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,38 +242,25 @@ 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: {self._z_offset_text:.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;" ) @@ -368,87 +271,59 @@ def setupUI(self): 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)