From 99114f14f62202e1fd8bf145616fa8c69937ada4 Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Sun, 7 Jul 2024 11:41:07 +0200 Subject: [PATCH] fix(motor_control): temporary remove of motor control widgets --- bec_widgets/examples/__init__.py | 9 - .../examples/motor_movement/__init__.py | 9 - .../motor_control_compilations.py | 250 ----- .../motor_movement/motor_controller.ui | 926 ------------------ bec_widgets/widgets/motor_control/__init__.py | 0 .../widgets/motor_control/motor_control.py | 252 ----- .../motor_control/motor_table/__init__.py | 0 .../motor_control/motor_table/motor_table.py | 484 --------- .../motor_control/motor_table/motor_table.ui | 113 --- .../movement_absolute/__init__.py | 0 .../movement_absolute/movement_absolute.py | 159 --- .../movement_absolute/movement_absolute.ui | 149 --- .../movement_relative/__init__.py | 0 .../movement_relative/movement_relative.py | 230 ----- .../movement_relative/movement_relative.ui | 298 ------ .../motor_control/selection/__init__.py | 0 .../motor_control/selection/selection.py | 110 --- .../motor_control/selection/selection.ui | 69 -- tests/unit_tests/test_motor_control.py | 588 ----------- 19 files changed, 3646 deletions(-) delete mode 100644 bec_widgets/examples/motor_movement/__init__.py delete mode 100644 bec_widgets/examples/motor_movement/motor_control_compilations.py delete mode 100644 bec_widgets/examples/motor_movement/motor_controller.ui delete mode 100644 bec_widgets/widgets/motor_control/__init__.py delete mode 100644 bec_widgets/widgets/motor_control/motor_control.py delete mode 100644 bec_widgets/widgets/motor_control/motor_table/__init__.py delete mode 100644 bec_widgets/widgets/motor_control/motor_table/motor_table.py delete mode 100644 bec_widgets/widgets/motor_control/motor_table/motor_table.ui delete mode 100644 bec_widgets/widgets/motor_control/movement_absolute/__init__.py delete mode 100644 bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py delete mode 100644 bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.ui delete mode 100644 bec_widgets/widgets/motor_control/movement_relative/__init__.py delete mode 100644 bec_widgets/widgets/motor_control/movement_relative/movement_relative.py delete mode 100644 bec_widgets/widgets/motor_control/movement_relative/movement_relative.ui delete mode 100644 bec_widgets/widgets/motor_control/selection/__init__.py delete mode 100644 bec_widgets/widgets/motor_control/selection/selection.py delete mode 100644 bec_widgets/widgets/motor_control/selection/selection.ui delete mode 100644 tests/unit_tests/test_motor_control.py diff --git a/bec_widgets/examples/__init__.py b/bec_widgets/examples/__init__.py index cdec4903..e69de29b 100644 --- a/bec_widgets/examples/__init__.py +++ b/bec_widgets/examples/__init__.py @@ -1,9 +0,0 @@ -from .motor_movement import ( - MotorControlApp, - MotorControlMap, - MotorControlPanel, - MotorControlPanelAbsolute, - MotorControlPanelRelative, - MotorCoordinateTable, - MotorThread, -) diff --git a/bec_widgets/examples/motor_movement/__init__.py b/bec_widgets/examples/motor_movement/__init__.py deleted file mode 100644 index 6e1ea76e..00000000 --- a/bec_widgets/examples/motor_movement/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from .motor_control_compilations import ( - MotorControlApp, - MotorControlMap, - MotorControlPanel, - MotorControlPanelAbsolute, - MotorControlPanelRelative, - MotorCoordinateTable, - MotorThread, -) diff --git a/bec_widgets/examples/motor_movement/motor_control_compilations.py b/bec_widgets/examples/motor_movement/motor_control_compilations.py deleted file mode 100644 index 21cb8a94..00000000 --- a/bec_widgets/examples/motor_movement/motor_control_compilations.py +++ /dev/null @@ -1,250 +0,0 @@ -# pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring - -import qdarktheme -from qtpy.QtCore import Qt -from qtpy.QtWidgets import QApplication, QSplitter, QVBoxLayout, QWidget - -from bec_widgets.utils.bec_dispatcher import BECDispatcher -from bec_widgets.widgets.motor_control.motor_control import MotorThread -from bec_widgets.widgets.motor_control.motor_table.motor_table import MotorCoordinateTable -from bec_widgets.widgets.motor_control.movement_absolute.movement_absolute import ( - MotorControlAbsolute, -) -from bec_widgets.widgets.motor_control.movement_relative.movement_relative import ( - MotorControlRelative, -) -from bec_widgets.widgets.motor_control.selection.selection import MotorControlSelection - -CONFIG_DEFAULT = { - "motor_control": { - "motor_x": "samx", - "motor_y": "samy", - "step_size_x": 3, - "step_size_y": 3, - "precision": 4, - "step_x_y_same": False, - "move_with_arrows": False, - }, - "plot_settings": { - "colormap": "Greys", - "scatter_size": 5, - "max_points": 1000, - "num_dim_points": 100, - "precision": 2, - "num_columns": 1, - "background_value": 25, - }, - "motors": [ - { - "plot_name": "Motor Map", - "x_label": "Motor X", - "y_label": "Motor Y", - "signals": { - "x": [{"name": "samx", "entry": "samx"}], - "y": [{"name": "samy", "entry": "samy"}], - }, - } - ], -} - - -class MotorControlApp(QWidget): - def __init__(self, parent=None, client=None, config=None): - super().__init__(parent) - - bec_dispatcher = BECDispatcher() - self.client = bec_dispatcher.client if client is None else client - self.config = config - - # Widgets - self.motor_control_panel = MotorControlPanel(client=self.client, config=self.config) - # Create MotorMap - # self.motion_map = MotorMap(client=self.client, config=self.config) - # Create MotorCoordinateTable - self.motor_table = MotorCoordinateTable(client=self.client, config=self.config) - - # Create the splitter and add MotorMap and MotorControlPanel - splitter = QSplitter(Qt.Horizontal) - # splitter.addWidget(self.motion_map) - splitter.addWidget(self.motor_control_panel) - splitter.addWidget(self.motor_table) - - # Set the main layout - layout = QVBoxLayout(self) - layout.addWidget(splitter) - self.setLayout(layout) - - # Connecting signals and slots - # self.motor_control_panel.selection_widget.selected_motors_signal.connect( - # lambda x, y: self.motion_map.change_motors(x, y, 0) - # ) - self.motor_control_panel.absolute_widget.coordinates_signal.connect( - self.motor_table.add_coordinate - ) - self.motor_control_panel.relative_widget.precision_signal.connect( - self.motor_table.set_precision - ) - self.motor_control_panel.relative_widget.precision_signal.connect( - self.motor_control_panel.absolute_widget.set_precision - ) - - # self.motor_table.plot_coordinates_signal.connect(self.motion_map.plot_saved_coordinates) - - -class MotorControlMap(QWidget): - def __init__(self, parent=None, client=None, config=None): - super().__init__(parent) - - bec_dispatcher = BECDispatcher() - self.client = bec_dispatcher.client if client is None else client - self.config = config - - # Widgets - self.motor_control_panel = MotorControlPanel(client=self.client, config=self.config) - # Create MotorMap - # self.motion_map = MotorMap(client=self.client, config=self.config) - - # Create the splitter and add MotorMap and MotorControlPanel - splitter = QSplitter(Qt.Horizontal) - # splitter.addWidget(self.motion_map) - splitter.addWidget(self.motor_control_panel) - - # Set the main layout - layout = QVBoxLayout(self) - layout.addWidget(splitter) - self.setLayout(layout) - - # Connecting signals and slots - # self.motor_control_panel.selection_widget.selected_motors_signal.connect( - # lambda x, y: self.motion_map.change_motors(x, y, 0) - # ) - - -class MotorControlPanel(QWidget): - def __init__(self, parent=None, client=None, config=None): - super().__init__(parent) - - bec_dispatcher = BECDispatcher() - self.client = bec_dispatcher.client if client is None else client - self.config = config - - self.motor_thread = MotorThread(client=self.client) - - self.selection_widget = MotorControlSelection( - client=self.client, config=self.config, motor_thread=self.motor_thread - ) - self.relative_widget = MotorControlRelative( - client=self.client, config=self.config, motor_thread=self.motor_thread - ) - self.absolute_widget = MotorControlAbsolute( - client=self.client, config=self.config, motor_thread=self.motor_thread - ) - - layout = QVBoxLayout(self) - - layout.addWidget(self.selection_widget) - layout.addWidget(self.relative_widget) - layout.addWidget(self.absolute_widget) - - # Connecting signals and slots - self.selection_widget.selected_motors_signal.connect(self.relative_widget.change_motors) - self.selection_widget.selected_motors_signal.connect(self.absolute_widget.change_motors) - - # Set the window to a fixed size based on its contents - # self.layout().setSizeConstraint(layout.SetFixedSize) - - -class MotorControlPanelAbsolute(QWidget): - def __init__(self, parent=None, client=None, config=None): - super().__init__(parent) - - bec_dispatcher = BECDispatcher() - self.client = bec_dispatcher.client if client is None else client - self.config = config - - self.motor_thread = MotorThread(client=self.client) - - self.selection_widget = MotorControlSelection( - client=client, config=config, motor_thread=self.motor_thread - ) - self.absolute_widget = MotorControlAbsolute( - client=client, config=config, motor_thread=self.motor_thread - ) - - layout = QVBoxLayout(self) - layout.addWidget(self.selection_widget) - layout.addWidget(self.absolute_widget) - - # Connecting signals and slots - self.selection_widget.selected_motors_signal.connect(self.absolute_widget.change_motors) - - -class MotorControlPanelRelative(QWidget): - def __init__(self, parent=None, client=None, config=None): - super().__init__(parent) - - bec_dispatcher = BECDispatcher() - self.client = bec_dispatcher.client if client is None else client - self.config = config - - self.motor_thread = MotorThread(client=self.client) - - self.selection_widget = MotorControlSelection( - client=client, config=config, motor_thread=self.motor_thread - ) - self.relative_widget = MotorControlRelative( - client=client, config=config, motor_thread=self.motor_thread - ) - - layout = QVBoxLayout(self) - layout.addWidget(self.selection_widget) - layout.addWidget(self.relative_widget) - - # Connecting signals and slots - self.selection_widget.selected_motors_signal.connect(self.relative_widget.change_motors) - - -if __name__ == "__main__": # pragma: no cover - import argparse - import sys - - parser = argparse.ArgumentParser(description="Run various Motor Control Widgets compositions.") - parser.add_argument( - "-v", - "--variant", - type=str, - choices=["app", "map", "panel", "panel_abs", "panel_rel"], - help="Select the variant of the motor control to run. " - "'app' for the full application, " - "'map' for MotorMap, " - "'panel' for the MotorControlPanel, " - "'panel_abs' for MotorControlPanel with absolute control, " - "'panel_rel' for MotorControlPanel with relative control.", - ) - - args = parser.parse_args() - - bec_dispatcher = BECDispatcher() - client = bec_dispatcher.client - client.start() - - app = QApplication([]) - qdarktheme.setup_theme("auto") - - if args.variant == "app": - window = MotorControlApp(client=client) # , config=CONFIG_DEFAULT) - elif args.variant == "map": - window = MotorControlMap(client=client) # , config=CONFIG_DEFAULT) - elif args.variant == "panel": - window = MotorControlPanel(client=client) # , config=CONFIG_DEFAULT) - elif args.variant == "panel_abs": - window = MotorControlPanelAbsolute(client=client) # , config=CONFIG_DEFAULT) - elif args.variant == "panel_rel": - window = MotorControlPanelRelative(client=client) # , config=CONFIG_DEFAULT) - else: - print("Please specify a valid variant to run. Use -h for help.") - print("Running the full application by default.") - window = MotorControlApp(client=client) # , config=CONFIG_DEFAULT) - - window.show() - sys.exit(app.exec()) diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui deleted file mode 100644 index 6134093f..00000000 --- a/bec_widgets/examples/motor_movement/motor_controller.ui +++ /dev/null @@ -1,926 +0,0 @@ - - - Form - - - - 0 - 0 - 1561 - 748 - - - - - 1409 - 748 - - - - Motor Controller - - - - - - true - - - - - - - - 221 - 471 - - - - - 1 - - - QLayout::SetMinimumSize - - - - - - 261 - 145 - - - - Motor Selection - - - - - - Motor Y - - - - - - - - - - - - - Motor X - - - - - - - Connect Motors - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Minimum - - - - 20 - 18 - - - - - - - - - 261 - 339 - - - - Motor Relative - - - - - - Move with arrow keys - - - - - - - Step [X] = Step [Y] - - - - - - - - - - 111 - 19 - - - - Step [Y] - - - - - - - - 111 - 19 - - - - Decimal - - - - - - - - 110 - 19 - - - - Qt::AlignCenter - - - 0.000000000000000 - - - 99.000000000000000 - - - 0.100000000000000 - - - 1.000000000000000 - - - - - - - - 111 - 19 - - - - Step [X] - - - - - - - - 110 - 19 - - - - Qt::AlignCenter - - - 0.000000000000000 - - - 99.000000000000000 - - - 0.100000000000000 - - - 1.000000000000000 - - - - - - - - 110 - 19 - - - - Qt::AlignCenter - - - 8 - - - 2 - - - - - - - - - QLayout::SetDefaultConstraint - - - - - - 26 - 26 - - - - ... - - - Qt::UpArrow - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 26 - 26 - - - - ... - - - Qt::DownArrow - - - - - - - - 26 - 26 - - - - ... - - - Qt::LeftArrow - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 26 - 26 - - - - ... - - - Qt::RightArrow - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Minimum - - - - 20 - 18 - - - - - - - - - 261 - 195 - - - - Move Absolute - - - - - - Save position with Go - - - - - - - - - Qt::AlignCenter - - - -500.000000000000000 - - - 500.000000000000000 - - - 0.100000000000000 - - - - - - - Qt::AlignCenter - - - -500.000000000000000 - - - 500.000000000000000 - - - 0.100000000000000 - - - - - - - Y - - - Qt::AlignCenter - - - - - - - X - - - Qt::AlignCenter - - - - - - - - - - - Save - - - - - - - Set - - - - - - - Go - - - - - - - - - Stop Movement - - - - - - - - - - - - - true - - - 0 - - - - Coordinates - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Entries Mode: - - - - - - - - Individual - - - - - Start/Stop - - - - - - - - - - QAbstractItemView::MultiSelection - - - QAbstractItemView::SelectRows - - - - Show - - - - - Move - - - - - Tag - - - - - X - - - - - Y - - - - - - - - - - Resize Table - - - - - - - Resize Auto - - - true - - - - - - - Import CSV - - - - - - - Export CSV - - - - - - - Help - - - - - - - Duplicate Last Entry - - - - - - - - - - Settings - - - - - - false - - - Motor Limits - - - - - - Update - - - - - - - + Y - - - Qt::AlignCenter - - - - - - - - Y - - - Qt::AlignCenter - - - - - - - - X - - - Qt::AlignCenter - - - - - - - + X - - - Qt::AlignCenter - - - - - - - Qt::AlignCenter - - - -1000.000000000000000 - - - 1000.000000000000000 - - - - - - - Qt::AlignCenter - - - -1000.000000000000000 - - - 1000.000000000000000 - - - - - - - Qt::AlignCenter - - - -1000.000000000000000 - - - 1000.000000000000000 - - - - - - - Qt::AlignCenter - - - -1000.000000000000000 - - - 1000.000000000000000 - - - - - - - - - - Plotting Options - - - - - - Qt::AlignCenter - - - 100 - - - 10000 - - - 100 - - - 5000 - - - - - - - Max Points - - - - - - - Qt::AlignCenter - - - 1 - - - 15 - - - 5 - - - - - - - Scatter Size - - - - - - - Update Settings - - - - - - - Qt::AlignCenter - - - 10 - - - 1000 - - - 10 - - - 100 - - - - - - - N dim - - - - - - - Enable Control GUI - - - - - - - - - - - Queue - - - - - - Work in progress - - - Qt::AlignCenter - - - - - - - false - - - Reset Queue - - - - - - - false - - - - queueID - - - - - scan_id - - - - - is_scan - - - - - type - - - - - scan_number - - - - - IQ status - - - - - - - - - - - - - GraphicsLayoutWidget - QGraphicsView -
pyqtgraph.h
-
-
- - -
diff --git a/bec_widgets/widgets/motor_control/__init__.py b/bec_widgets/widgets/motor_control/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bec_widgets/widgets/motor_control/motor_control.py b/bec_widgets/widgets/motor_control/motor_control.py deleted file mode 100644 index 45aa334a..00000000 --- a/bec_widgets/widgets/motor_control/motor_control.py +++ /dev/null @@ -1,252 +0,0 @@ -# pylint: disable = no-name-in-module,missing-module-docstring -from enum import Enum - -from bec_lib.alarm_handler import AlarmBase -from bec_lib.device import Positioner -from qtpy.QtCore import QThread -from qtpy.QtCore import Signal as pyqtSignal -from qtpy.QtCore import Slot as pyqtSlot -from qtpy.QtWidgets import QMessageBox, QWidget - -from bec_widgets.utils.bec_dispatcher import BECDispatcher - -CONFIG_DEFAULT = { - "motor_control": { - "motor_x": "samx", - "motor_y": "samy", - "step_size_x": 3, - "step_size_y": 50, - "precision": 4, - "step_x_y_same": False, - "move_with_arrows": False, - } -} - - -class MotorControlWidget(QWidget): - """Base class for motor control widgets.""" - - def __init__(self, parent=None, client=None, motor_thread=None, config=None): - super().__init__(parent) - self.client = client - self.motor_thread = motor_thread - self.config = config - - self.motor_x = None - self.motor_y = None - - if not self.client: - bec_dispatcher = BECDispatcher() - self.client = bec_dispatcher.client - - if not self.motor_thread: - self.motor_thread = MotorThread(client=self.client) - - self._load_ui() - - if self.config is None: - print(f"No initial config found for {self.__class__.__name__}") - self._init_ui() - else: - self.on_config_update(self.config) - - def _load_ui(self): - """Load the UI from the .ui file.""" - - def _init_ui(self): - """Initialize the UI components specific to the widget.""" - - @pyqtSlot(dict) - def on_config_update(self, config): - """Handle configuration updates.""" - self.config = config - self._init_ui() - - -class MotorControlErrors: - """Class for displaying formatted error messages.""" - - @staticmethod - def display_error_message(error_message: str) -> None: - """ - Display a critical error message. - Args: - error_message(str): Error message to display. - """ - # Create a QMessageBox - msg = QMessageBox() - msg.setIcon(QMessageBox.Critical) - msg.setWindowTitle("Critical Error") - - # Format the message - formatted_message = MotorControlErrors._format_error_message(error_message) - msg.setText(formatted_message) - - # Display the message box - msg.exec_() - - @staticmethod - def _format_error_message(error_message: str) -> str: - """ - Format the error message. - Args: - error_message(str): Error message to format. - - Returns: - str: Formatted error message. - """ - # Split the message into lines - lines = error_message.split("\n") - formatted_lines = [ - f"{line.strip()}" if i == 0 else line.strip() - for i, line in enumerate(lines) - if line.strip() - ] - - # Join the lines with double breaks for empty lines in between - formatted_message = "

".join(formatted_lines) - - return formatted_message - - -class MotorActions(Enum): - """Enum for motor actions.""" - - MOVE_ABSOLUTE = "move_absolute" - MOVE_RELATIVE = "move_relative" - - -class MotorThread(QThread): - """ - QThread subclass for controlling motor actions asynchronously. - - Signals: - coordinates_updated (pyqtSignal): Signal to emit current coordinates. - motor_error (pyqtSignal): Signal to emit when there is an error with the motors. - lock_gui (pyqtSignal): Signal to lock/unlock the GUI. - """ - - coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates - motor_error = pyqtSignal(str) # Signal to emit when there is an error with the motors - lock_gui = pyqtSignal(bool) # Signal to lock/unlock the GUI - - def __init__(self, parent=None, client=None): - super().__init__(parent) - - bec_dispatcher = BECDispatcher() - self.client = bec_dispatcher.client if client is None else client - self.dev = self.client.device_manager.devices - self.scans = self.client.scans - self.queue = self.client.queue - self.action = None - - self.motor = None - self.motor_x = None - self.motor_y = None - self.target_coordinates = None - self.value = None - - def get_all_motors_names(self) -> list: - """ - Get all the motors names. - Returns: - list: List of all the motors names. - """ - all_devices = self.client.device_manager.devices.enabled_devices - all_motors_names = [motor.name for motor in all_devices if isinstance(motor, Positioner)] - return all_motors_names - - def get_coordinates(self, motor_x: str, motor_y: str) -> tuple: - """ - Get the current coordinates of the motors. - Args: - motor_x(str): Motor X to get positions from. - motor_y(str): Motor Y to get positions from. - - Returns: - tuple: Current coordinates of the motors. - """ - x = self.dev[motor_x].readback.get() - y = self.dev[motor_y].readback.get() - return x, y - - def move_absolute(self, motor_x: str, motor_y: str, target_coordinates: tuple) -> None: - """ - Wrapper for moving the motor to the target coordinates. - Args: - motor_x(str): Motor X to move. - motor_y(str): Motor Y to move. - target_coordinates(tuple): Target coordinates. - """ - self.action = MotorActions.MOVE_ABSOLUTE - self.motor_x = motor_x - self.motor_y = motor_y - self.target_coordinates = target_coordinates - self.start() - - def move_relative(self, motor: str, value: float) -> None: - """ - Wrapper for moving the motor relative to the current position. - Args: - motor(str): Motor to move. - value(float): Value to move. - """ - self.action = MotorActions.MOVE_RELATIVE - self.motor = motor - self.value = value - self.start() - - def run(self): - """ - Run the thread. - Possible actions: - - Move to coordinates - - Move relative - """ - if self.action == MotorActions.MOVE_ABSOLUTE: - self._move_motor_absolute(self.motor_x, self.motor_y, self.target_coordinates) - elif self.action == MotorActions.MOVE_RELATIVE: - self._move_motor_relative(self.motor, self.value) - - def _move_motor_absolute(self, motor_x: str, motor_y: str, target_coordinates: tuple) -> None: - """ - Move the motor to the target coordinates. - Args: - motor_x(str): Motor X to move. - motor_y(str): Motor Y to move. - target_coordinates(tuple): Target coordinates. - """ - self.lock_gui.emit(False) - try: - status = self.scans.mv( - self.dev[motor_x], - target_coordinates[0], - self.dev[motor_y], - target_coordinates[1], - relative=False, - ) - status.wait() - except AlarmBase as e: - self.motor_error.emit(str(e)) - finally: - self.lock_gui.emit(True) - - def _move_motor_relative(self, motor, value: float) -> None: - """ - Move the motor relative to the current position. - Args: - motor(str): Motor to move. - value(float): Value to move. - """ - self.lock_gui.emit(False) - try: - status = self.scans.mv(self.dev[motor], value, relative=True) - status.wait() - except AlarmBase as e: - self.motor_error.emit(str(e)) - finally: - self.lock_gui.emit(True) - - def stop_movement(self): - self.queue.request_scan_abortion() - self.queue.request_queue_reset() diff --git a/bec_widgets/widgets/motor_control/motor_table/__init__.py b/bec_widgets/widgets/motor_control/motor_table/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bec_widgets/widgets/motor_control/motor_table/motor_table.py b/bec_widgets/widgets/motor_control/motor_table/motor_table.py deleted file mode 100644 index 5fbc439f..00000000 --- a/bec_widgets/widgets/motor_control/motor_table/motor_table.py +++ /dev/null @@ -1,484 +0,0 @@ -# pylint: disable = no-name-in-module,missing-module-docstring -import os - -from qtpy import uic -from qtpy.QtCore import Qt -from qtpy.QtCore import Signal as pyqtSignal -from qtpy.QtCore import Slot as pyqtSlot -from qtpy.QtGui import QDoubleValidator, QKeySequence -from qtpy.QtWidgets import ( - QCheckBox, - QLineEdit, - QMessageBox, - QPushButton, - QShortcut, - QTableWidget, - QTableWidgetItem, -) - -from bec_widgets.utils import UILoader -from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget - - -class MotorCoordinateTable(MotorControlWidget): - """ - Widget to save coordinates from motor, display them in the table and move back to them. - There are two modes of operation: - - Individual: Each row is a single coordinate. - - Start/Stop: Each pair of rows is a start and end coordinate. - Signals: - plot_coordinates_signal (pyqtSignal(list, str, str)): Signal to plot the coordinates in the MotorMap. - Slots: - add_coordinate (pyqtSlot(tuple)): Slot to add a coordinate to the table. - mode_switch (pyqtSlot): Slot to switch between individual and start/stop mode. - """ - - plot_coordinates_signal = pyqtSignal(list, str, str) - - def _load_ui(self): - """Load the UI for the coordinate table.""" - current_path = os.path.dirname(__file__) - self.ui = UILoader().load_ui(os.path.join(current_path, "motor_table.ui"), self) - - def _init_ui(self): - """Initialize the UI""" - # Setup table behaviour - self._setup_table() - self.ui.table.setSelectionBehavior(QTableWidget.SelectRows) - - # for tag columns default tag - self.tag_counter = 1 - - # Connect signals and slots - self.ui.checkBox_resize_auto.stateChanged.connect(self.resize_table_auto) - self.ui.comboBox_mode.currentIndexChanged.connect(self.mode_switch) - - # Keyboard shortcuts for deleting a row - self.delete_shortcut = QShortcut(QKeySequence(Qt.Key_Delete), self.ui.table) - self.delete_shortcut.activated.connect(self.delete_selected_row) - self.backspace_shortcut = QShortcut(QKeySequence(Qt.Key_Backspace), self.ui.table) - self.backspace_shortcut.activated.connect(self.delete_selected_row) - - # Warning message for mode switch enable/disable - self.warning_message = True - - @pyqtSlot(dict) - def on_config_update(self, config: dict) -> None: - """ - Update config dict - Args: - config(dict): New config dict - """ - self.config = config - - # Get motor names - self.motor_x, self.motor_y = ( - self.config["motor_control"]["motor_x"], - self.config["motor_control"]["motor_y"], - ) - - # Decimal precision of the table coordinates - self.precision = self.config["motor_control"].get("precision", 3) - - # Mode switch default option - self.mode = self.config["motor_control"].get("mode", "Individual") - - # Set combobox to default mode - self.ui.comboBox_mode.setCurrentText(self.mode) - - self._init_ui() - - def _setup_table(self): - """Setup the table with appropriate headers and configurations.""" - mode = self.ui.comboBox_mode.currentText() - - if mode == "Individual": - self._setup_individual_mode() - elif mode == "Start/Stop": - self._setup_start_stop_mode() - self.start_stop_counter = 0 # TODO: remove this?? - - self.wipe_motor_map_coordinates() - - def _setup_individual_mode(self): - """Setup the table for individual mode.""" - self.ui.table.setColumnCount(5) - self.ui.table.setHorizontalHeaderLabels(["Show", "Move", "Tag", "X", "Y"]) - self.ui.table.verticalHeader().setVisible(False) - - def _setup_start_stop_mode(self): - """Setup the table for start/stop mode.""" - self.ui.table.setColumnCount(8) - self.ui.table.setHorizontalHeaderLabels( - [ - "Show", - "Move [start]", - "Move [end]", - "Tag", - "X [start]", - "Y [start]", - "X [end]", - "Y [end]", - ] - ) - self.ui.table.verticalHeader().setVisible(False) - # Set flag to track if the coordinate is stat or the end of the entry - self.is_next_entry_end = False - - def mode_switch(self): - """Switch between individual and start/stop mode.""" - last_selected_index = self.ui.comboBox_mode.currentIndex() - - if self.ui.table.rowCount() > 0 and self.warning_message is True: - msgBox = QMessageBox() - msgBox.setIcon(QMessageBox.Critical) - msgBox.setText( - "Switching modes will delete all table entries. Do you want to continue?" - ) - msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) - returnValue = msgBox.exec() - - if returnValue is QMessageBox.Cancel: - self.ui.comboBox_mode.blockSignals(True) # Block signals - self.ui.comboBox_mode.setCurrentIndex(last_selected_index) - self.ui.comboBox_mode.blockSignals(False) # Unblock signals - return - - # Wipe table - self.wipe_motor_map_coordinates() - - # Initiate new table with new mode - self._setup_table() - - @pyqtSlot(tuple) - def add_coordinate(self, coordinates: tuple): - """ - Add a coordinate to the table. - Args: - coordinates(tuple): Coordinates (x,y) to add to the table. - """ - tag = f"Pos {self.tag_counter}" - self.tag_counter += 1 - x, y = coordinates - self._add_row(tag, x, y) - - def _add_row(self, tag: str, x: float, y: float) -> None: - """ - Add a row to the table. - Args: - tag(str): Tag of the coordinate. - x(float): X coordinate. - y(float): Y coordinate. - """ - - mode = self.ui.comboBox_mode.currentText() - if mode == "Individual": - checkbox_pos = 0 - button_pos = 1 - tag_pos = 2 - x_pos = 3 - y_pos = 4 - coordinate_reference = "Individual" - color = "green" - - # Add new row -> new entry - row_count = self.ui.table.rowCount() - self.ui.table.insertRow(row_count) - - # Add Widgets - self._add_widgets( - tag, - x, - y, - row_count, - checkbox_pos, - tag_pos, - button_pos, - x_pos, - y_pos, - coordinate_reference, - color, - ) - - if mode == "Start/Stop": - # These positions are always fixed - checkbox_pos = 0 - tag_pos = 3 - - if self.is_next_entry_end is False: # It is the start position of the entry - print("Start position") - button_pos = 1 - x_pos = 4 - y_pos = 5 - coordinate_reference = "Start" - color = "blue" - - # Add new row -> new entry - row_count = self.ui.table.rowCount() - self.ui.table.insertRow(row_count) - - # Add Widgets - self._add_widgets( - tag, - x, - y, - row_count, - checkbox_pos, - tag_pos, - button_pos, - x_pos, - y_pos, - coordinate_reference, - color, - ) - - # Next entry will be the end of the current entry - self.is_next_entry_end = True - - elif self.is_next_entry_end is True: # It is the end position of the entry - print("End position") - row_count = self.ui.table.rowCount() - 1 # Current row - button_pos = 2 - x_pos = 6 - y_pos = 7 - coordinate_reference = "Stop" - color = "red" - - # Add Widgets - self._add_widgets( - tag, - x, - y, - row_count, - checkbox_pos, - tag_pos, - button_pos, - x_pos, - y_pos, - coordinate_reference, - color, - ) - self.is_next_entry_end = False # Next entry will be the start of the new entry - - # Auto table resize - self.resize_table_auto() - - def _add_widgets( - self, - tag: str, - x: float, - y: float, - row: int, - checkBox_pos: int, - tag_pos: int, - button_pos: int, - x_pos: int, - y_pos: int, - coordinate_reference: str, - color: str, - ) -> None: - """ - Add widgets to the table. - Args: - tag(str): Tag of the coordinate. - x(float): X coordinate. - y(float): Y coordinate. - row(int): Row of the QTableWidget where to add the widgets. - checkBox_pos(int): Column where to put CheckBox. - tag_pos(int): Column where to put Tag. - button_pos(int): Column where to put Move button. - x_pos(int): Column where to link x coordinate. - y_pos(int): Column where to link y coordinate. - coordinate_reference(str): Reference to the coordinate for MotorMap. - color(str): Color of the coordinate for MotorMap. - """ - # Add widgets - self._add_checkbox(row, checkBox_pos, x_pos, y_pos) - self._add_move_button(row, button_pos, x_pos, y_pos) - self.ui.table.setItem(row, tag_pos, QTableWidgetItem(tag)) - self._add_line_edit(x, row, x_pos, x_pos, y_pos, coordinate_reference, color) - self._add_line_edit(y, row, y_pos, x_pos, y_pos, coordinate_reference, color) - - # # Emit the coordinates to be plotted - self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color) - - # Connect item edit to emit coordinates - self.ui.table.itemChanged.connect( - lambda: print(f"item changed from {coordinate_reference} slot \n {x}-{y}-{color}") - ) - self.ui.table.itemChanged.connect( - lambda: self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color) - ) - - def _add_checkbox(self, row: int, checkBox_pos: int, x_pos: int, y_pos: int): - """ - Add a checkbox to the table. - Args: - row(int): Row of QTableWidget where to add the checkbox. - checkBox_pos(int): Column where to put CheckBox. - x_pos(int): Column where to link x coordinate. - y_pos(int): Column where to link y coordinate. - """ - show_checkbox = QCheckBox() - show_checkbox.setChecked(True) - show_checkbox.stateChanged.connect(lambda: self.emit_plot_coordinates(x_pos, y_pos)) - self.ui.table.setCellWidget(row, checkBox_pos, show_checkbox) - - def _add_move_button(self, row: int, button_pos: int, x_pos: int, y_pos: int) -> None: - """ - Add a move button to the table. - Args: - row(int): Row of QTableWidget where to add the move button. - button_pos(int): Column where to put move button. - x_pos(int): Column where to link x coordinate. - y_pos(int): Column where to link y coordinate. - """ - move_button = QPushButton("Move") - move_button.clicked.connect(lambda: self.handle_move_button_click(x_pos, y_pos)) - self.ui.table.setCellWidget(row, button_pos, move_button) - - def _add_line_edit( - self, - value: float, - row: int, - line_pos: int, - x_pos: int, - y_pos: int, - coordinate_reference: str, - color: str, - ) -> None: - """ - Add a QLineEdit to the table. - Args: - value(float): Initial value of the QLineEdit. - row(int): Row of QTableWidget where to add the QLineEdit. - line_pos(int): Column where to put QLineEdit. - x_pos(int): Column where to link x coordinate. - y_pos(int): Column where to link y coordinate. - coordinate_reference(str): Reference to the coordinate for MotorMap. - color(str): Color of the coordinate for MotorMap. - """ - # Adding validator - validator = QDoubleValidator() - validator.setDecimals(self.precision) - - # Create line edit - edit = QLineEdit(str(f"{value:.{self.precision}f}")) - edit.setValidator(validator) - edit.setAlignment(Qt.AlignmentFlag.AlignCenter) - - # Add line edit to the table - self.ui.table.setCellWidget(row, line_pos, edit) - edit.textChanged.connect( - lambda: self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color) - ) - - def wipe_motor_map_coordinates(self): - """Wipe the motor map coordinates.""" - try: - self.ui.table.itemChanged.disconnect() # Disconnect all previous connections - except TypeError: - print("No previous connections to disconnect") - self.ui.table.setRowCount(0) - reference_tags = ["Individual", "Start", "Stop"] - for reference_tag in reference_tags: - self.plot_coordinates_signal.emit([], reference_tag, "green") - - def handle_move_button_click(self, x_pos: int, y_pos: int) -> None: - """ - Handle the move button click. - Args: - x_pos(int): X position of the coordinate. - y_pos(int): Y position of the coordinate. - """ - button = self.sender() - row = self.ui.table.indexAt(button.pos()).row() - - x = self.get_coordinate(row, x_pos) - y = self.get_coordinate(row, y_pos) - self.move_motor(x, y) - - def emit_plot_coordinates(self, x_pos: float, y_pos: float, reference_tag: str, color: str): - """ - Emit the coordinates to be plotted. - Args: - x_pos(float): X position of the coordinate. - y_pos(float): Y position of the coordinate. - reference_tag(str): Reference tag of the coordinate. - color(str): Color of the coordinate. - """ - print( - f"Emitting plot coordinates: x_pos={x_pos}, y_pos={y_pos}, reference_tag={reference_tag}, color={color}" - ) - coordinates = [] - for row in range(self.ui.table.rowCount()): - show = self.ui.table.cellWidget(row, 0).isChecked() - x = self.get_coordinate(row, x_pos) - y = self.get_coordinate(row, y_pos) - - coordinates.append((x, y, show)) # (x, y, show_flag) - self.plot_coordinates_signal.emit(coordinates, reference_tag, color) - - def get_coordinate(self, row: int, column: int) -> float: - """ - Helper function to get the coordinate from the table QLineEdit cells. - Args: - row(int): Row of the table. - column(int): Column of the table. - Returns: - float: Value of the coordinate. - """ - edit = self.ui.table.cellWidget(row, column) - value = float(edit.text()) if edit and edit.text() != "" else None - if value: - return value - - def delete_selected_row(self): - """Delete the selected row from the table.""" - selected_rows = self.ui.table.selectionModel().selectedRows() - for row in selected_rows: - self.ui.table.removeRow(row.row()) - if self.ui.comboBox_mode.currentText() == "Start/Stop": - self.emit_plot_coordinates(x_pos=4, y_pos=5, reference_tag="Start", color="blue") - self.emit_plot_coordinates(x_pos=6, y_pos=7, reference_tag="Stop", color="red") - self.is_next_entry_end = False - elif self.ui.comboBox_mode.currentText() == "Individual": - self.emit_plot_coordinates(x_pos=3, y_pos=4, reference_tag="Individual", color="green") - - def resize_table_auto(self): - """Resize the table to fit the contents.""" - if self.ui.checkBox_resize_auto.isChecked(): - self.ui.table.resizeColumnsToContents() - - def move_motor(self, x: float, y: float) -> None: - """ - Move the motor to the target coordinates. - Args: - x(float): Target x coordinate. - y(float): Target y coordinate. - """ - self.motor_thread.move_absolute(self.motor_x, self.motor_y, (x, y)) - - @pyqtSlot(str, str) - def change_motors(self, motor_x: str, motor_y: str) -> None: - """ - Change the active motors and update config. - Can be connected to the selected_motors_signal from MotorControlSelection. - Args: - motor_x(str): New motor X to be controlled. - motor_y(str): New motor Y to be controlled. - """ - self.motor_x = motor_x - self.motor_y = motor_y - self.config["motor_control"]["motor_x"] = motor_x - self.config["motor_control"]["motor_y"] = motor_y - - @pyqtSlot(int) - def set_precision(self, precision: int) -> None: - """ - Set the precision of the coordinates. - Args: - precision(int): Precision of the coordinates. - """ - self.precision = precision - self.config["motor_control"]["precision"] = precision diff --git a/bec_widgets/widgets/motor_control/motor_table/motor_table.ui b/bec_widgets/widgets/motor_control/motor_table/motor_table.ui deleted file mode 100644 index 52aa059a..00000000 --- a/bec_widgets/widgets/motor_control/motor_table/motor_table.ui +++ /dev/null @@ -1,113 +0,0 @@ - - - Form - - - - 0 - 0 - 676 - 667 - - - - Motor Coordinates Table - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Resize Auto - - - true - - - - - - - false - - - Edit Custom Column - - - - - - - Entries Mode: - - - - - - - true - - - - Individual - - - - - Start/Stop - - - - - - - - - - Qt::SolidLine - - - - - - - - - false - - - Import CSV - - - - - - - false - - - Export CSV - - - - - - - - - - diff --git a/bec_widgets/widgets/motor_control/movement_absolute/__init__.py b/bec_widgets/widgets/motor_control/movement_absolute/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py b/bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py deleted file mode 100644 index 72f6a7af..00000000 --- a/bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +++ /dev/null @@ -1,159 +0,0 @@ -import os - -from qtpy import uic -from qtpy.QtCore import Signal as pyqtSignal -from qtpy.QtCore import Slot as pyqtSlot -from qtpy.QtWidgets import QWidget - -from bec_widgets.utils import UILoader -from bec_widgets.widgets.motor_control.motor_control import MotorControlErrors, MotorControlWidget - - -class MotorControlAbsolute(MotorControlWidget): - """ - Widget for controlling the motors to absolute coordinates. - - Signals: - coordinates_signal (pyqtSignal(tuple)): Signal to emit the coordinates. - Slots: - change_motors (pyqtSlot): Slot to change the active motors. - enable_motor_controls (pyqtSlot(bool)): Slot to enable/disable the motor controls. - """ - - coordinates_signal = pyqtSignal(tuple) - - def _load_ui(self): - """Load the UI from the .ui file.""" - current_path = os.path.dirname(__file__) - self.ui = UILoader().load_ui(os.path.join(current_path, "movement_absolute.ui"), self) - - def _init_ui(self): - """Initialize the UI.""" - - # Check if there are any motors connected - if self.motor_x is None or self.motor_y is None: - self.ui.motorControl_absolute.setEnabled(False) - return - - # Move to absolute coordinates - self.ui.pushButton_go_absolute.clicked.connect( - lambda: self.move_motor_absolute( - self.ui.spinBox_absolute_x.value(), self.ui.spinBox_absolute_y.value() - ) - ) - - self.ui.pushButton_set.clicked.connect(self.save_absolute_coordinates) - self.ui.pushButton_save.clicked.connect(self.save_current_coordinates) - self.ui.pushButton_stop.clicked.connect(self.motor_thread.stop_movement) - - # Enable/Disable GUI - self.motor_thread.lock_gui.connect(self.enable_motor_controls) - - # Error messages - self.motor_thread.motor_error.connect( - lambda error: MotorControlErrors.display_error_message(error) - ) - - # Keyboard shortcuts - self._init_keyboard_shortcuts() - - @pyqtSlot(dict) - def on_config_update(self, config: dict) -> None: - """Update config dict""" - self.config = config - - # Get motor names - self.motor_x, self.motor_y = ( - self.config["motor_control"]["motor_x"], - self.config["motor_control"]["motor_y"], - ) - - # Update step precision - self.precision = self.config["motor_control"]["precision"] - - self._init_ui() - - @pyqtSlot(bool) - def enable_motor_controls(self, enable: bool) -> None: - """ - Enable or disable the motor controls. - Args: - enable(bool): True to enable, False to disable. - """ - - # Disable or enable all controls within the motorControl_absolute group box - for widget in self.ui.motorControl_absolute.findChildren(QWidget): - widget.setEnabled(enable) - - # Enable the pushButton_stop if the motor is moving - self.ui.pushButton_stop.setEnabled(True) - - @pyqtSlot(str, str) - def change_motors(self, motor_x: str, motor_y: str): - """ - Change the active motors and update config. - Can be connected to the selected_motors_signal from MotorControlSelection. - Args: - motor_x(str): New motor X to be controlled. - motor_y(str): New motor Y to be controlled. - """ - self.motor_x = motor_x - self.motor_y = motor_y - self.config["motor_control"]["motor_x"] = motor_x - self.config["motor_control"]["motor_y"] = motor_y - - @pyqtSlot(int) - def set_precision(self, precision: int) -> None: - """ - Set the precision of the coordinates. - Args: - precision(int): Precision of the coordinates. - """ - self.precision = precision - self.config["motor_control"]["precision"] = precision - self.ui.spinBox_absolute_x.setDecimals(precision) - self.ui.spinBox_absolute_y.setDecimals(precision) - - def move_motor_absolute(self, x: float, y: float) -> None: - """ - Move the motor to the target coordinates. - Args: - x(float): Target x coordinate. - y(float): Target y coordinate. - """ - # self._enable_motor_controls(False) - target_coordinates = (x, y) - self.motor_thread.move_absolute(self.motor_x, self.motor_y, target_coordinates) - if self.ui.checkBox_save_with_go.isChecked(): - self.save_absolute_coordinates() - - def _init_keyboard_shortcuts(self): - """Initialize the keyboard shortcuts.""" - # Go absolute button - self.ui.pushButton_go_absolute.setShortcut("Ctrl+G") - self.ui.pushButton_go_absolute.setToolTip("Ctrl+G") - - # Set absolute coordinates - self.ui.pushButton_set.setShortcut("Ctrl+D") - self.ui.pushButton_set.setToolTip("Ctrl+D") - - # Save Current coordinates - self.ui.pushButton_save.setShortcut("Ctrl+S") - self.ui.pushButton_save.setToolTip("Ctrl+S") - - # Stop Button - self.ui.pushButton_stop.setShortcut("Ctrl+X") - self.ui.pushButton_stop.setToolTip("Ctrl+X") - - def save_absolute_coordinates(self): - """Emit the setup coordinates from the spinboxes""" - - x, y = round(self.ui.spinBox_absolute_x.value(), self.precision), round( - self.ui.spinBox_absolute_y.value(), self.precision - ) - self.coordinates_signal.emit((x, y)) - - def save_current_coordinates(self): - """Emit the current coordinates from the motor thread""" - x, y = self.motor_thread.get_coordinates(self.motor_x, self.motor_y) - self.coordinates_signal.emit((round(x, self.precision), round(y, self.precision))) diff --git a/bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.ui b/bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.ui deleted file mode 100644 index ea4ec863..00000000 --- a/bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.ui +++ /dev/null @@ -1,149 +0,0 @@ - - - Form - - - - 0 - 0 - 285 - 220 - - - - - 285 - 220 - - - - - 285 - 220 - - - - Move Movement Absolute - - - - - - - 261 - 195 - - - - - 261 - 195 - - - - Move Movement Absolute - - - - - - Save position with Go - - - - - - - - - Qt::AlignCenter - - - -500.000000000000000 - - - 500.000000000000000 - - - 0.100000000000000 - - - - - - - Qt::AlignCenter - - - -500.000000000000000 - - - 500.000000000000000 - - - 0.100000000000000 - - - - - - - Y - - - Qt::AlignCenter - - - - - - - X - - - Qt::AlignCenter - - - - - - - - - - - Save - - - - - - - Set - - - - - - - Go - - - - - - - - - Stop Movement - - - - - - - - - - - diff --git a/bec_widgets/widgets/motor_control/movement_relative/__init__.py b/bec_widgets/widgets/motor_control/movement_relative/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bec_widgets/widgets/motor_control/movement_relative/movement_relative.py b/bec_widgets/widgets/motor_control/movement_relative/movement_relative.py deleted file mode 100644 index 80a60ae6..00000000 --- a/bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +++ /dev/null @@ -1,230 +0,0 @@ -import os - -from qtpy import uic -from qtpy.QtCore import Qt -from qtpy.QtCore import Signal as pyqtSignal -from qtpy.QtCore import Slot as pyqtSlot -from qtpy.QtGui import QKeySequence -from qtpy.QtWidgets import QDoubleSpinBox, QShortcut, QWidget - -from bec_widgets.utils import UILoader -from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget - - -class MotorControlRelative(MotorControlWidget): - """ - Widget for controlling the motors to relative coordinates. - - Signals: - precision_signal (pyqtSignal): Signal to emit the precision of the coordinates. - Slots: - change_motors (pyqtSlot(str,str)): Slot to change the active motors. - enable_motor_controls (pyqtSlot): Slot to enable/disable the motor controls. - """ - - precision_signal = pyqtSignal(int) - - def _load_ui(self): - """Load the UI from the .ui file.""" - # Loading UI - current_path = os.path.dirname(__file__) - self.ui = UILoader().load_ui(os.path.join(current_path, "movement_relative.ui"), self) - - def _init_ui(self): - """Initialize the UI.""" - self._init_ui_motor_control() - self._init_keyboard_shortcuts() - - @pyqtSlot(dict) - def on_config_update(self, config: dict) -> None: - """ - Update config dict - Args: - config(dict): New config dict - """ - self.config = config - - # Get motor names - self.motor_x, self.motor_y = ( - self.config["motor_control"]["motor_x"], - self.config["motor_control"]["motor_y"], - ) - - # Update step precision - self.precision = self.config["motor_control"]["precision"] - self.ui.spinBox_precision.setValue(self.precision) - - # Update step sizes - self.ui.spinBox_step_x.setValue(self.config["motor_control"]["step_size_x"]) - self.ui.spinBox_step_y.setValue(self.config["motor_control"]["step_size_y"]) - - # Checkboxes for keyboard shortcuts and x/y step size link - self.ui.checkBox_same_xy.setChecked(self.config["motor_control"]["step_x_y_same"]) - self.ui.checkBox_enableArrows.setChecked(self.config["motor_control"]["move_with_arrows"]) - - self._init_ui() - - def _init_ui_motor_control(self) -> None: - """Initialize the motor control elements""" - - # Connect checkbox and spinBoxes - self.ui.checkBox_same_xy.stateChanged.connect(self._sync_step_sizes) - self.ui.spinBox_step_x.valueChanged.connect(self._update_step_size_x) - self.ui.spinBox_step_y.valueChanged.connect(self._update_step_size_y) - - self.ui.toolButton_right.clicked.connect( - lambda: self.move_motor_relative(self.motor_x, "x", 1) - ) - self.ui.toolButton_left.clicked.connect( - lambda: self.move_motor_relative(self.motor_x, "x", -1) - ) - self.ui.toolButton_up.clicked.connect( - lambda: self.move_motor_relative(self.motor_y, "y", 1) - ) - self.ui.toolButton_down.clicked.connect( - lambda: self.move_motor_relative(self.motor_y, "y", -1) - ) - - # Switch between key shortcuts active - self.ui.checkBox_enableArrows.stateChanged.connect(self._update_arrow_key_shortcuts) - self._update_arrow_key_shortcuts() - - # Enable/Disable GUI - self.motor_thread.lock_gui.connect(self.enable_motor_controls) - - # Precision update - self.ui.spinBox_precision.valueChanged.connect(lambda x: self._update_precision(x)) - - # Error messages - self.motor_thread.motor_error.connect( - lambda error: MotorControlErrors.display_error_message(error) - ) - - # Stop Button - self.ui.pushButton_stop.clicked.connect(self.motor_thread.stop_movement) - - def _init_keyboard_shortcuts(self) -> None: - """Initialize the keyboard shortcuts""" - - # Increase/decrease step size for X motor - increase_x_shortcut = QShortcut(QKeySequence("Ctrl+A"), self) - decrease_x_shortcut = QShortcut(QKeySequence("Ctrl+Z"), self) - increase_x_shortcut.activated.connect( - lambda: self._change_step_size(self.ui.spinBox_step_x, 2) - ) - decrease_x_shortcut.activated.connect( - lambda: self._change_step_size(self.ui.spinBox_step_x, 0.5) - ) - self.ui.spinBox_step_x.setToolTip("Increase step size: Ctrl+A\nDecrease step size: Ctrl+Z") - - # Increase/decrease step size for Y motor - increase_y_shortcut = QShortcut(QKeySequence("Alt+A"), self) - decrease_y_shortcut = QShortcut(QKeySequence("Alt+Z"), self) - increase_y_shortcut.activated.connect( - lambda: self._change_step_size(self.ui.spinBox_step_y, 2) - ) - decrease_y_shortcut.activated.connect( - lambda: self._change_step_size(self.ui.spinBox_step_y, 0.5) - ) - self.ui.spinBox_step_y.setToolTip("Increase step size: Alt+A\nDecrease step size: Alt+Z") - - # Stop Button - self.ui.pushButton_stop.setShortcut("Ctrl+X") - self.ui.pushButton_stop.setToolTip("Ctrl+X") - - def _update_arrow_key_shortcuts(self) -> None: - """Update the arrow key shortcuts based on the checkbox state.""" - if self.ui.checkBox_enableArrows.isChecked(): - # Set the arrow key shortcuts for motor movement - self.ui.toolButton_right.setShortcut(Qt.Key_Right) - self.ui.toolButton_left.setShortcut(Qt.Key_Left) - self.ui.toolButton_up.setShortcut(Qt.Key_Up) - self.ui.toolButton_down.setShortcut(Qt.Key_Down) - else: - # Clear the shortcuts - self.ui.toolButton_right.setShortcut("") - self.ui.toolButton_left.setShortcut("") - self.ui.toolButton_up.setShortcut("") - self.ui.toolButton_down.setShortcut("") - - def _update_precision(self, precision: int) -> None: - """ - Update the precision of the coordinates. - Args: - precision(int): Precision of the coordinates. - """ - self.ui.spinBox_step_x.setDecimals(precision) - self.ui.spinBox_step_y.setDecimals(precision) - self.precision_signal.emit(precision) - - def _change_step_size(self, spinBox: QDoubleSpinBox, factor: float) -> None: - """ - Change the step size of the spinbox. - Args: - spinBox(QDoubleSpinBox): Spinbox to change the step size. - factor(float): Factor to change the step size. - """ - old_step = spinBox.value() - new_step = old_step * factor - spinBox.setValue(new_step) - - def _sync_step_sizes(self): - """Sync step sizes based on checkbox state.""" - if self.ui.checkBox_same_xy.isChecked(): - value = self.ui.spinBox_step_x.value() - self.ui.spinBox_step_y.setValue(value) - - def _update_step_size_x(self): - """Update step size for x if checkbox is checked.""" - if self.ui.checkBox_same_xy.isChecked(): - value = self.ui.spinBox_step_x.value() - self.ui.spinBox_step_y.setValue(value) - - def _update_step_size_y(self): - """Update step size for y if checkbox is checked.""" - if self.ui.checkBox_same_xy.isChecked(): - value = self.ui.spinBox_step_y.value() - self.ui.spinBox_step_x.setValue(value) - - @pyqtSlot(str, str) - def change_motors(self, motor_x: str, motor_y: str): - """ - Change the active motors and update config. - Can be connected to the selected_motors_signal from MotorControlSelection. - Args: - motor_x(str): New motor X to be controlled. - motor_y(str): New motor Y to be controlled. - """ - self.motor_x = motor_x - self.motor_y = motor_y - self.config["motor_control"]["motor_x"] = motor_x - self.config["motor_control"]["motor_y"] = motor_y - - @pyqtSlot(bool) - def enable_motor_controls(self, disable: bool) -> None: - """ - Enable or disable the motor controls. - Args: - disable(bool): True to disable, False to enable. - """ - - # Disable or enable all controls within the motorControl_absolute group box - for widget in self.ui.motorControl.findChildren(QWidget): - widget.setEnabled(disable) - - # Enable the pushButton_stop if the motor is moving - self.ui.pushButton_stop.setEnabled(True) - - def move_motor_relative(self, motor, axis: str, direction: int) -> None: - """ - Move the motor relative to the current position. - Args: - motor: Motor to move. - axis(str): Axis to move. - direction(int): Direction to move. 1 for positive, -1 for negative. - """ - if axis == "x": - step = direction * self.ui.spinBox_step_x.value() - elif axis == "y": - step = direction * self.ui.spinBox_step_y.value() - self.motor_thread.move_relative(motor, step) diff --git a/bec_widgets/widgets/motor_control/movement_relative/movement_relative.ui b/bec_widgets/widgets/motor_control/movement_relative/movement_relative.ui deleted file mode 100644 index 78fed89b..00000000 --- a/bec_widgets/widgets/motor_control/movement_relative/movement_relative.ui +++ /dev/null @@ -1,298 +0,0 @@ - - - Form - - - - 0 - 0 - 285 - 405 - - - - - 285 - 405 - - - - Motor Control Relative - - - - - - - 261 - 394 - - - - Motor Control Relative - - - - - - Move with arrow keys - - - - - - - Step [X] = Step [Y] - - - - - - - - - - 111 - 19 - - - - Step [Y] - - - - - - - - 111 - 19 - - - - Decimal - - - - - - - - 110 - 19 - - - - Qt::AlignCenter - - - 0.000000000000000 - - - 99.000000000000000 - - - 0.100000000000000 - - - 1.000000000000000 - - - - - - - - 111 - 19 - - - - Step [X] - - - - - - - - 110 - 19 - - - - Qt::AlignCenter - - - 0.000000000000000 - - - 99.000000000000000 - - - 0.100000000000000 - - - 1.000000000000000 - - - - - - - - 110 - 19 - - - - Qt::AlignCenter - - - 8 - - - 2 - - - - - - - - - QLayout::SetDefaultConstraint - - - - - - 26 - 26 - - - - ... - - - Qt::UpArrow - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 26 - 26 - - - - ... - - - Qt::DownArrow - - - - - - - - 26 - 26 - - - - ... - - - Qt::LeftArrow - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 26 - 26 - - - - ... - - - Qt::RightArrow - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Stop Movement - - - - - - - - - - - diff --git a/bec_widgets/widgets/motor_control/selection/__init__.py b/bec_widgets/widgets/motor_control/selection/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bec_widgets/widgets/motor_control/selection/selection.py b/bec_widgets/widgets/motor_control/selection/selection.py deleted file mode 100644 index 1f48c1a1..00000000 --- a/bec_widgets/widgets/motor_control/selection/selection.py +++ /dev/null @@ -1,110 +0,0 @@ -# pylint: disable = no-name-in-module,missing-module-docstring -import os - -from qtpy import uic -from qtpy.QtCore import Signal as pyqtSignal -from qtpy.QtCore import Slot as pyqtSlot -from qtpy.QtWidgets import QComboBox - -from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget - - -class MotorControlSelection(MotorControlWidget): - """ - Widget for selecting the motors to control. - - Signals: - selected_motors_signal (pyqtSignal(str,str)): Signal to emit the selected motors. - Slots: - get_available_motors (pyqtSlot): Slot to populate the available motors in the combo boxes and set the index based on the configuration. - enable_motor_controls (pyqtSlot(bool)): Slot to enable/disable the motor controls GUI. - on_config_update (pyqtSlot(dict)): Slot to update the config dict. - """ - - selected_motors_signal = pyqtSignal(str, str) - - def _load_ui(self): - """Load the UI from the .ui file.""" - current_path = os.path.dirname(__file__) - uic.loadUi(os.path.join(current_path, "selection.ui"), self) - - def _init_ui(self): - """Initialize the UI.""" - # Lock GUI while motors are moving - self.motor_thread.lock_gui.connect(self.enable_motor_controls) - - self.pushButton_connecMotors.clicked.connect(self.select_motor) - self.get_available_motors() - - # Connect change signals to change color - self.comboBox_motor_x.currentIndexChanged.connect( - lambda: self.set_combobox_style(self.comboBox_motor_x, "#ffa700") - ) - self.comboBox_motor_y.currentIndexChanged.connect( - lambda: self.set_combobox_style(self.comboBox_motor_y, "#ffa700") - ) - - @pyqtSlot(dict) - def on_config_update(self, config: dict) -> None: - """ - Update config dict - Args: - config(dict): New config dict - """ - self.config = config - - # Get motor names - self.motor_x, self.motor_y = ( - self.config["motor_control"]["motor_x"], - self.config["motor_control"]["motor_y"], - ) - - self._init_ui() - - @pyqtSlot(bool) - def enable_motor_controls(self, enable: bool) -> None: - """ - Enable or disable the motor controls. - Args: - enable(bool): True to enable, False to disable. - """ - self.motorSelection.setEnabled(enable) - - @pyqtSlot() - def get_available_motors(self) -> None: - """ - Slot to populate the available motors in the combo boxes and set the index based on the configuration. - """ - # Get all available motors - self.motor_list = self.motor_thread.get_all_motors_names() - - # Populate the combo boxes - self.comboBox_motor_x.addItems(self.motor_list) - self.comboBox_motor_y.addItems(self.motor_list) - - # Set the index based on the config if provided - if self.config: - index_x = self.comboBox_motor_x.findText(self.motor_x) - index_y = self.comboBox_motor_y.findText(self.motor_y) - self.comboBox_motor_x.setCurrentIndex(index_x if index_x != -1 else 0) - self.comboBox_motor_y.setCurrentIndex(index_y if index_y != -1 else 0) - - def set_combobox_style(self, combobox, color: str) -> None: - """ - Set the combobox style to a specific color. - Args: - combobox(QComboBox): Combobox to change the color. - color(str): Color to set the combobox to. - """ - combobox.setStyleSheet(f"QComboBox {{ background-color: {color}; }}") - - def select_motor(self): - """Emit the selected motors""" - motor_x = self.comboBox_motor_x.currentText() - motor_y = self.comboBox_motor_y.currentText() - - # Reset the combobox color to normal after selection - self.set_combobox_style(self.comboBox_motor_x, "") - self.set_combobox_style(self.comboBox_motor_y, "") - - self.selected_motors_signal.emit(motor_x, motor_y) diff --git a/bec_widgets/widgets/motor_control/selection/selection.ui b/bec_widgets/widgets/motor_control/selection/selection.ui deleted file mode 100644 index 406363df..00000000 --- a/bec_widgets/widgets/motor_control/selection/selection.ui +++ /dev/null @@ -1,69 +0,0 @@ - - - Form - - - - 0 - 0 - 285 - 156 - - - - - 285 - 156 - - - - Motor Control Selection - - - - - - - 261 - 145 - - - - Motor Selection - - - - - - Motor X - - - - - - - - - - Motor Y - - - - - - - Connect Motors - - - - - - - - - - - - - - diff --git a/tests/unit_tests/test_motor_control.py b/tests/unit_tests/test_motor_control.py deleted file mode 100644 index c0bcec3a..00000000 --- a/tests/unit_tests/test_motor_control.py +++ /dev/null @@ -1,588 +0,0 @@ -# pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring -from unittest.mock import MagicMock, patch - -import pytest -from bec_lib.devicemanager import DeviceContainer - -from bec_widgets.examples import ( - MotorControlApp, - MotorControlMap, - MotorControlPanel, - MotorControlPanelAbsolute, - MotorControlPanelRelative, -) -from bec_widgets.widgets.motor_control.motor_control import MotorActions, MotorThread -from bec_widgets.widgets.motor_control.motor_table.motor_table import MotorCoordinateTable -from bec_widgets.widgets.motor_control.movement_absolute.movement_absolute import ( - MotorControlAbsolute, -) -from bec_widgets.widgets.motor_control.movement_relative.movement_relative import ( - MotorControlRelative, -) -from bec_widgets.widgets.motor_control.selection.selection import MotorControlSelection - -from .client_mocks import mocked_client - -CONFIG_DEFAULT = { - "motor_control": { - "motor_x": "samx", - "motor_y": "samy", - "step_size_x": 3, - "step_size_y": 3, - "precision": 4, - "step_x_y_same": False, - "move_with_arrows": False, - }, - "plot_settings": { - "colormap": "Greys", - "scatter_size": 5, - "max_points": 1000, - "num_dim_points": 100, - "precision": 2, - "num_columns": 1, - "background_value": 25, - }, - "motors": [ - { - "plot_name": "Motor Map", - "x_label": "Motor X", - "y_label": "Motor Y", - "signals": { - "x": [{"name": "samx", "entry": "samx"}], - "y": [{"name": "samy", "entry": "samy"}], - }, - } - ], -} - - -####################################################### -# Motor Thread -####################################################### -@pytest.fixture -def motor_thread(mocked_client): - """Fixture for MotorThread with a mocked client.""" - return MotorThread(client=mocked_client) - - -def test_motor_thread_initialization(mocked_client): - motor_thread = MotorThread(client=mocked_client) - assert motor_thread.client == mocked_client - assert isinstance(motor_thread.dev, DeviceContainer) - - -def test_get_all_motors_names(mocked_client): - motor_thread = MotorThread(client=mocked_client) - motor_names = motor_thread.get_all_motors_names() - expected_names = ["samx", "samy", "samz", "aptrx", "aptry"] - assert sorted(motor_names) == sorted(expected_names) - assert all(name in motor_names for name in expected_names) - assert len(motor_names) == len(expected_names) # Ensure only these motors are returned - - -def test_get_coordinates(mocked_client): - motor_thread = MotorThread(client=mocked_client) - motor_x, motor_y = "samx", "samy" - x, y = motor_thread.get_coordinates(motor_x, motor_y) - - assert x == mocked_client.device_manager.devices[motor_x].readback.get() - assert y == mocked_client.device_manager.devices[motor_y].readback.get() - - -def test_move_motor_absolute_by_run(mocked_client): - motor_thread = MotorThread(client=mocked_client) - motor_thread.motor_x = "samx" - motor_thread.motor_y = "samy" - motor_thread.target_coordinates = (5.0, -3.0) - motor_thread.action = MotorActions.MOVE_ABSOLUTE - motor_thread.run() - - assert mocked_client.device_manager.devices["samx"].read_value == 5.0 - assert mocked_client.device_manager.devices["samy"].read_value == -3.0 - - -def test_move_motor_relative_by_run(mocked_client): - motor_thread = MotorThread(client=mocked_client) - - initial_value = motor_thread.dev["samx"].read()["samx"]["value"] - move_value = 2.0 - expected_value = initial_value + move_value - motor_thread.motor = "samx" - motor_thread.value = move_value - motor_thread.action = MotorActions.MOVE_RELATIVE - motor_thread.run() - - assert mocked_client.device_manager.devices["samx"].read_value == expected_value - - -def test_motor_thread_move_absolute(motor_thread): - motor_x = "samx" - motor_y = "samy" - target_x = 5.0 - target_y = -3.0 - - motor_thread.move_absolute(motor_x, motor_y, (target_x, target_y)) - motor_thread.wait() - - assert motor_thread.dev[motor_x].read()["samx"]["value"] == target_x - assert motor_thread.dev[motor_y].read()["samy"]["value"] == target_y - - -def test_motor_thread_move_relative(motor_thread): - motor_name = "samx" - move_value = 2.0 - - initial_value = motor_thread.dev[motor_name].read()["samx"]["value"] - motor_thread.move_relative(motor_name, move_value) - motor_thread.wait() - - expected_value = initial_value + move_value - assert motor_thread.dev[motor_name].read()["samx"]["value"] == expected_value - - -####################################################### -# Motor Control Widgets - MotorControlSelection -####################################################### -@pytest.fixture(scope="function") -def motor_selection_widget(qtbot, mocked_client, motor_thread): - """Fixture for creating a MotorControlSelection widget with a mocked client.""" - widget = MotorControlSelection( - client=mocked_client, config=CONFIG_DEFAULT, motor_thread=motor_thread - ) - qtbot.addWidget(widget) - qtbot.waitExposed(widget) - return widget - - -def test_initialization_and_population(motor_selection_widget): - assert motor_selection_widget.comboBox_motor_x.count() == 5 - assert motor_selection_widget.comboBox_motor_x.itemText(0) == "samx" - assert motor_selection_widget.comboBox_motor_y.itemText(1) == "samy" - assert motor_selection_widget.comboBox_motor_y.itemText(2) == "samz" - assert motor_selection_widget.comboBox_motor_x.itemText(3) == "aptrx" - assert motor_selection_widget.comboBox_motor_y.itemText(4) == "aptry" - - -def test_selection_and_signal_emission(motor_selection_widget): - # Connect signal to a custom slot to capture the emitted values - emitted_values = [] - - def capture_emitted_values(motor_x, motor_y): - emitted_values.append((motor_x, motor_y)) - - motor_selection_widget.selected_motors_signal.connect(capture_emitted_values) - - # Select motors - motor_selection_widget.comboBox_motor_x.setCurrentIndex(0) # Select 'samx' - motor_selection_widget.comboBox_motor_y.setCurrentIndex(1) # Select 'samy' - motor_selection_widget.pushButton_connecMotors.click() # Emit the signal - - # Verify the emitted signal - assert emitted_values == [ - ("samx", "samy") - ], "The emitted signal did not match the expected values" - - -def test_configuration_update(motor_selection_widget): - new_config = {"motor_control": {"motor_x": "samy", "motor_y": "samx"}} - motor_selection_widget.on_config_update(new_config) - assert motor_selection_widget.comboBox_motor_x.currentText() == "samy" - assert motor_selection_widget.comboBox_motor_y.currentText() == "samx" - - -def test_enable_motor_controls(motor_selection_widget): - motor_selection_widget.enable_motor_controls(False) - assert not motor_selection_widget.comboBox_motor_x.isEnabled() - assert not motor_selection_widget.comboBox_motor_y.isEnabled() - - motor_selection_widget.enable_motor_controls(True) - assert motor_selection_widget.comboBox_motor_x.isEnabled() - assert motor_selection_widget.comboBox_motor_y.isEnabled() - - -####################################################### -# Motor Control Widgets - MotorControlAbsolute -####################################################### - - -@pytest.fixture(scope="function") -def motor_absolute_widget(qtbot, mocked_client, motor_thread): - widget = MotorControlAbsolute( - client=mocked_client, config=CONFIG_DEFAULT, motor_thread=motor_thread - ) - qtbot.addWidget(widget) - qtbot.waitExposed(widget) - return widget - - -def test_absolute_initialization(motor_absolute_widget): - motor_absolute_widget.change_motors("samx", "samy") - motor_absolute_widget.on_config_update(CONFIG_DEFAULT) - assert motor_absolute_widget.motor_x == "samx", "Motor X not initialized correctly" - assert motor_absolute_widget.motor_y == "samy", "Motor Y not initialized correctly" - assert motor_absolute_widget.precision == CONFIG_DEFAULT["motor_control"]["precision"] - - -def test_absolute_save_current_coordinates(motor_absolute_widget): - motor_x_value = motor_absolute_widget.client.device_manager.devices["samx"].read()["samx"][ - "value" - ] - motor_y_value = motor_absolute_widget.client.device_manager.devices["samy"].read()["samy"][ - "value" - ] - motor_absolute_widget.change_motors("samx", "samy") - - emitted_coordinates = [] - - def capture_emit(x_y): - emitted_coordinates.append(x_y) - - motor_absolute_widget.coordinates_signal.connect(capture_emit) - - # Trigger saving current coordinates - motor_absolute_widget.ui.pushButton_save.click() - - assert emitted_coordinates == [(motor_x_value, motor_y_value)] - - -def test_absolute_set_absolute_coordinates(motor_absolute_widget): - motor_absolute_widget.ui.spinBox_absolute_x.setValue(5) - motor_absolute_widget.ui.spinBox_absolute_y.setValue(10) - - # Connect to the coordinates_signal to capture emitted values - emitted_values = [] - - def capture_coordinates(x_y): - emitted_values.append(x_y) - - motor_absolute_widget.coordinates_signal.connect(capture_coordinates) - - # Simulate button click for absolute movement - motor_absolute_widget.ui.pushButton_set.click() - - assert emitted_values == [(5, 10)] - - -def test_absolute_go_absolute_coordinates(motor_absolute_widget): - motor_absolute_widget.change_motors("samx", "samy") - - motor_absolute_widget.ui.spinBox_absolute_x.setValue(5) - motor_absolute_widget.ui.spinBox_absolute_y.setValue(10) - - with patch( - "bec_widgets.widgets.motor_control.motor_control.MotorThread.move_absolute", - new_callable=MagicMock, - ) as mock_move_absolute: - motor_absolute_widget.ui.pushButton_go_absolute.click() - mock_move_absolute.assert_called_once_with("samx", "samy", (5, 10)) - - -def test_change_motor_absolute(motor_absolute_widget): - motor_absolute_widget.change_motors("aptrx", "aptry") - - assert motor_absolute_widget.motor_x == "aptrx" - assert motor_absolute_widget.motor_y == "aptry" - - motor_absolute_widget.change_motors("samx", "samy") - - assert motor_absolute_widget.motor_x == "samx" - assert motor_absolute_widget.motor_y == "samy" - - -def test_set_precision(motor_absolute_widget): - motor_absolute_widget.on_config_update(CONFIG_DEFAULT) - motor_absolute_widget.set_precision(2) - - assert motor_absolute_widget.ui.spinBox_absolute_x.decimals() == 2 - assert motor_absolute_widget.ui.spinBox_absolute_y.decimals() == 2 - - -####################################################### -# Motor Control Widgets - MotorControlRelative -####################################################### -@pytest.fixture(scope="function") -def motor_relative_widget(qtbot, mocked_client, motor_thread): - widget = MotorControlRelative( - client=mocked_client, config=CONFIG_DEFAULT, motor_thread=motor_thread - ) - qtbot.addWidget(widget) - qtbot.waitExposed(widget) - return widget - - -def test_initialization_and_config_update(motor_relative_widget): - motor_relative_widget.on_config_update(CONFIG_DEFAULT) - - assert motor_relative_widget.motor_x == CONFIG_DEFAULT["motor_control"]["motor_x"] - assert motor_relative_widget.motor_y == CONFIG_DEFAULT["motor_control"]["motor_y"] - assert motor_relative_widget.precision == CONFIG_DEFAULT["motor_control"]["precision"] - - # Simulate a configuration update - new_config = { - "motor_control": { - "motor_x": "new_motor_x", - "motor_y": "new_motor_y", - "precision": 2, - "step_size_x": 5, - "step_size_y": 5, - "step_x_y_same": True, - "move_with_arrows": True, - } - } - motor_relative_widget.on_config_update(new_config) - - assert motor_relative_widget.motor_x == "new_motor_x" - assert motor_relative_widget.motor_y == "new_motor_y" - assert motor_relative_widget.precision == 2 - - -def test_move_motor_relative(motor_relative_widget): - motor_relative_widget.on_config_update(CONFIG_DEFAULT) - # Set step sizes - motor_relative_widget.ui.spinBox_step_x.setValue(1) - motor_relative_widget.ui.spinBox_step_y.setValue(1) - - # Mock the move_relative method - motor_relative_widget.motor_thread.move_relative = MagicMock() - - # Simulate button clicks - motor_relative_widget.ui.toolButton_right.click() - motor_relative_widget.motor_thread.move_relative.assert_called_with( - motor_relative_widget.motor_x, 1 - ) - - motor_relative_widget.ui.toolButton_left.click() - motor_relative_widget.motor_thread.move_relative.assert_called_with( - motor_relative_widget.motor_x, -1 - ) - - motor_relative_widget.ui.toolButton_up.click() - motor_relative_widget.motor_thread.move_relative.assert_called_with( - motor_relative_widget.motor_y, 1 - ) - - motor_relative_widget.ui.toolButton_down.click() - motor_relative_widget.motor_thread.move_relative.assert_called_with( - motor_relative_widget.motor_y, -1 - ) - - -def test_precision_update(motor_relative_widget): - # Capture emitted precision values - emitted_values = [] - - def capture_precision(precision): - emitted_values.append(precision) - - motor_relative_widget.precision_signal.connect(capture_precision) - - # Update precision - motor_relative_widget.ui.spinBox_precision.setValue(1) - - assert emitted_values == [1] - assert motor_relative_widget.ui.spinBox_step_x.decimals() == 1 - assert motor_relative_widget.ui.spinBox_step_y.decimals() == 1 - - -def test_sync_step_sizes(motor_relative_widget): - motor_relative_widget.on_config_update(CONFIG_DEFAULT) - motor_relative_widget.ui.checkBox_same_xy.setChecked(True) - - # Change step size for X - motor_relative_widget.ui.spinBox_step_x.setValue(2) - - assert motor_relative_widget.ui.spinBox_step_y.value() == 2 - - -def test_change_motor_relative(motor_relative_widget): - motor_relative_widget.on_config_update(CONFIG_DEFAULT) - motor_relative_widget.change_motors("aptrx", "aptry") - - assert motor_relative_widget.motor_x == "aptrx" - assert motor_relative_widget.motor_y == "aptry" - - -####################################################### -# Motor Control Widgets - MotorCoordinateTable -####################################################### -@pytest.fixture(scope="function") -def motor_coordinate_table(qtbot, mocked_client, motor_thread): - widget = MotorCoordinateTable( - client=mocked_client, config=CONFIG_DEFAULT, motor_thread=motor_thread - ) - qtbot.addWidget(widget) - qtbot.waitExposed(widget) - return widget - - -def test_delete_selected_row(motor_coordinate_table): - # Add a coordinate - motor_coordinate_table.add_coordinate((1.0, 2.0)) - motor_coordinate_table.add_coordinate((3.0, 4.0)) - - # Select the row - motor_coordinate_table.ui.table.selectRow(0) - - # Delete the selected row - motor_coordinate_table.delete_selected_row() - assert motor_coordinate_table.ui.table.rowCount() == 1 - - -def test_add_coordinate_and_table_update(motor_coordinate_table): - # Disable Warning message popups for test - motor_coordinate_table.warning_message = False - - # Add coordinate in Individual mode - motor_coordinate_table.add_coordinate((1.0, 2.0)) - assert motor_coordinate_table.ui.table.rowCount() == 1 - - # Check if the coordinates match - x_item_individual = motor_coordinate_table.ui.table.cellWidget( - 0, 3 - ) # Assuming X is in column 3 - y_item_individual = motor_coordinate_table.ui.table.cellWidget( - 0, 4 - ) # Assuming Y is in column 4 - assert float(x_item_individual.text()) == 1.0 - assert float(y_item_individual.text()) == 2.0 - - # Switch to Start/Stop and add coordinates - motor_coordinate_table.ui.comboBox_mode.setCurrentIndex(1) # Switch mode - - motor_coordinate_table.add_coordinate((3.0, 4.0)) - motor_coordinate_table.add_coordinate((5.0, 6.0)) - assert motor_coordinate_table.ui.table.rowCount() == 1 - - -def test_plot_coordinates_signal(motor_coordinate_table): - # Connect to the signal - def signal_emitted(coordinates, reference_tag, color): - nonlocal received - received = True - assert len(coordinates) == 1 # Assuming one coordinate was added - assert reference_tag in ["Individual", "Start", "Stop"] - assert color in ["green", "blue", "red"] - - received = False - motor_coordinate_table.plot_coordinates_signal.connect(signal_emitted) - - # Add a coordinate and check signal - motor_coordinate_table.add_coordinate((1.0, 2.0)) - assert received - - -# def test_move_motor_action(motor_coordinate_table,qtbot):#TODO enable again after table refactor -# # Add a coordinate -# motor_coordinate_table.add_coordinate((1.0, 2.0)) -# -# # Mock the motor thread move_absolute function -# motor_coordinate_table.motor_thread.move_absolute = MagicMock() -# -# # Trigger the move action -# move_button = motor_coordinate_table.table.cellWidget(0, 1) -# move_button.click() -# -# motor_coordinate_table.motor_thread.move_absolute.assert_called_with( -# motor_coordinate_table.motor_x, motor_coordinate_table.motor_y, (1.0, 2.0) -# ) - - -def test_plot_coordinates_signal_individual(motor_coordinate_table, qtbot): - motor_coordinate_table.warning_message = False - motor_coordinate_table.set_precision(3) - motor_coordinate_table.ui.comboBox_mode.setCurrentIndex(0) - - # This list will store the signals emitted during the test - emitted_signals = [] - - def signal_emitted(coordinates, reference_tag, color): - emitted_signals.append((coordinates, reference_tag, color)) - - motor_coordinate_table.plot_coordinates_signal.connect(signal_emitted) - - # Add new coordinates - motor_coordinate_table.add_coordinate((1.0, 2.0)) - qtbot.wait(100) - - # Verify the signals - assert len(emitted_signals) > 0, "No signals were emitted." - - for coordinates, reference_tag, color in emitted_signals: - assert len(coordinates) > 0, "Coordinates list is empty." - assert reference_tag == "Individual" - assert color == "green" - assert motor_coordinate_table.ui.table.cellWidget(0, 3).text() == "1.000" - assert motor_coordinate_table.ui.table.cellWidget(0, 4).text() == "2.000" - - -####################################################### -# MotorControl examples compilations -####################################################### -@pytest.fixture(scope="function") -def motor_app(qtbot, mocked_client): - widget = MotorControlApp(config=CONFIG_DEFAULT, client=mocked_client) - qtbot.addWidget(widget) - qtbot.waitExposed(widget) - yield widget - - -def test_motor_app_initialization(motor_app): - assert isinstance(motor_app, MotorControlApp) - assert motor_app.client is not None - assert motor_app.config == CONFIG_DEFAULT - - -@pytest.fixture(scope="function") -def motor_control_map(qtbot, mocked_client): - widget = MotorControlMap(config=CONFIG_DEFAULT, client=mocked_client) - qtbot.addWidget(widget) - qtbot.waitExposed(widget) - yield widget - - -def test_motor_control_map_initialization(motor_control_map): - assert isinstance(motor_control_map, MotorControlMap) - assert motor_control_map.client is not None - assert motor_control_map.config == CONFIG_DEFAULT - - -@pytest.fixture(scope="function") -def motor_control_panel(qtbot, mocked_client): - widget = MotorControlPanel(config=CONFIG_DEFAULT, client=mocked_client) - qtbot.addWidget(widget) - qtbot.waitExposed(widget) - yield widget - - -def test_motor_control_panel_initialization(motor_control_panel): - assert isinstance(motor_control_panel, MotorControlPanel) - assert motor_control_panel.client is not None - assert motor_control_panel.config == CONFIG_DEFAULT - - -@pytest.fixture(scope="function") -def motor_control_panel_absolute(qtbot, mocked_client): - widget = MotorControlPanelAbsolute(config=CONFIG_DEFAULT, client=mocked_client) - qtbot.addWidget(widget) - qtbot.waitExposed(widget) - yield widget - - -def test_motor_control_panel_absolute_initialization(motor_control_panel_absolute): - assert isinstance(motor_control_panel_absolute, MotorControlPanelAbsolute) - assert motor_control_panel_absolute.client is not None - assert motor_control_panel_absolute.config == CONFIG_DEFAULT - - -@pytest.fixture(scope="function") -def motor_control_panel_relative(qtbot, mocked_client): - widget = MotorControlPanelRelative(config=CONFIG_DEFAULT, client=mocked_client) - qtbot.addWidget(widget) - qtbot.waitExposed(widget) - yield widget - - -def test_motor_control_panel_relative_initialization(motor_control_panel_relative): - assert isinstance(motor_control_panel_relative, MotorControlPanelRelative) - assert motor_control_panel_relative.client is not None - assert motor_control_panel_relative.config == CONFIG_DEFAULT