import os import linuxcnc from enum import Enum from qtpy import uic from qtpy.QtCore import Qt from qtpy.QtWidgets import QWidget, QPushButton from qtpyvcp.actions import machine_actions from qtpyvcp.plugins import getPlugin from qtpyvcp.utilities import logger from widgets.conversational.float_line_edit import FloatLineEdit LOG = logger.getLogger(__name__) STATUS = getPlugin('status') TOOL_TABLE = getPlugin('tooltable') INI_FILE = linuxcnc.ini(os.getenv('INI_FILE_NAME')) CMD = linuxcnc.command() class MoveMode(Enum): """Defines the available movement modes (absolute, work coordinate system, relative).""" ABS = 1 WCS = 2 REL = 3 class UserTab(QWidget): """A sidebar widget for controlling machine axis movements.""" def __init__(self, parent=None): super(UserTab, self).__init__(parent) ui_file = os.path.splitext(os.path.basename(__file__))[0] + ".ui" uic.loadUi(os.path.join(os.path.dirname(__file__), ui_file), self) self.position_inputs: list[FloatLineEdit] = self.findChildren(FloatLineEdit) self.move_buttons: list[QPushButton] = self.findChildren(QPushButton) self._connect_signals() self.update_wcs_label() def _connect_signals(self): """Connects widget signals to corresponding slots.""" STATUS.on.notify(self._update_control_states) STATUS.all_axes_homed.notify(self._update_control_states) STATUS.g5x_index.notify(self.update_wcs_label) for line_edit in self.position_inputs: line_edit.setEnabled(False) line_edit.editingFinished.connect(self._format_sender_float_value) for button in self.move_buttons: button.setEnabled(False) button.clicked.connect(self._on_move_button_clicked) def _format_sender_float_value(self): """Formats the text of the sending FloatLineEdit to its specified format.""" sender_widget = self.sender() if not isinstance(sender_widget, FloatLineEdit): return LOG.debug(f"Formatting float for {sender_widget.objectName()}") value = sender_widget.value() formatted_value = sender_widget.format_string.format(value) sender_widget.setText(formatted_value) def update_wcs_label(self, *args): """Updates the WCS status label with the current coordinate system.""" wcs_index = STATUS.g5x_index self.wcs_status_label.setText(f"WCS {wcs_index}") def _on_move_button_clicked(self): """Handles clicks on any of the move buttons.""" sender = self.sender() if not isinstance(sender, QPushButton): return button_name = sender.objectName() LOG.debug(f"Move button clicked: {button_name}") try: _, mode_str, axes_str = button_name.replace('_button', '').split('_') except ValueError: LOG.error(f"Could not parse button name: {button_name}") return move_configs = { 'abs': (MoveMode.ABS, self.abs_x_input, self.abs_y_input, self.abs_z_input), 'wcs': (MoveMode.WCS, self.wcs_x_input, self.wcs_y_input, self.wcs_z_input), 'rel': (MoveMode.REL, self.rel_x_input, self.rel_y_input, self.rel_z_input) } if mode_str not in move_configs: LOG.error(f"Unknown move mode '{mode_str}' in button name: {button_name}") return mode, x_input, y_input, z_input = move_configs[mode_str] x, y, z = None, None, None if 'x' in axes_str or 'all' in axes_str: x = x_input.value() if 'y' in axes_str or 'all' in axes_str: y = y_input.value() if 'z' in axes_str or 'all' in axes_str: z = z_input.value() self._execute_move_command(mode, x, y, z) LOG.debug(f"Executed move: {mode.name}, X={x}, Y={y}, Z={z}") def _execute_move_command(self, mode: MoveMode, x=None, y=None, z=None): """Builds and sends an MDI command for the specified movement.""" mdi_command = "" match mode: case MoveMode.ABS: mdi_command = "G53 G0" case MoveMode.WCS: mdi_command = "G0" case MoveMode.REL: mdi_command = "G91 G0" if x is not None: mdi_command += f" X{x}" if y is not None: mdi_command += f" Y{y}" if z is not None: mdi_command += f" Z{z}" if mode == MoveMode.REL: mdi_command += " ;G90" LOG.debug(f"Sending MDI command: {mdi_command}") machine_actions.issue_mdi(mdi_command) def _is_machine_ready(self) -> bool: """Checks if the machine is on and all axes are homed.""" is_ready = STATUS.on() and STATUS.all_axes_homed() LOG.debug(f"Machine ready check: on={STATUS.on()}, homed={STATUS.all_axes_homed()} -> {is_ready}") return is_ready def _update_control_states(self, *args): """Enables or disables UI controls based on the machine's ready state.""" is_ready = self._is_machine_ready() LOG.debug(f"Updating control states. Machine ready: {is_ready}") for button in self.move_buttons: button.setEnabled(is_ready) for line_edit in self.position_inputs: line_edit.setEnabled(is_ready)