From 6fe08e6b8206bcaaa292b7ff0e6b0d32b883f24f Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Thu, 25 Jan 2024 16:21:57 +0100
Subject: [PATCH] feat: motor_control.py MotorControl widgets - Absolute +
Relative movement, MotorSelection, ErrorMessage popups
---
bec_widgets/utils/bec_dispatcher.py | 4 -
bec_widgets/widgets/__init__.py | 1 +
bec_widgets/widgets/motor_control/__init__.py | 1 +
.../widgets/motor_control/motor_control.py | 699 ++++++++++++++++++
.../motor_control/motor_control_absolute.ui | 149 ++++
.../motor_control/motor_control_relative.ui | 298 ++++++++
.../motor_control/motor_control_selection.ui | 69 ++
7 files changed, 1217 insertions(+), 4 deletions(-)
create mode 100644 bec_widgets/widgets/motor_control/__init__.py
create mode 100644 bec_widgets/widgets/motor_control/motor_control.py
create mode 100644 bec_widgets/widgets/motor_control/motor_control_absolute.ui
create mode 100644 bec_widgets/widgets/motor_control/motor_control_relative.ui
create mode 100644 bec_widgets/widgets/motor_control/motor_control_selection.ui
diff --git a/bec_widgets/utils/bec_dispatcher.py b/bec_widgets/utils/bec_dispatcher.py
index b0195f71..6da32b2e 100644
--- a/bec_widgets/utils/bec_dispatcher.py
+++ b/bec_widgets/utils/bec_dispatcher.py
@@ -1,7 +1,3 @@
-# TODO last backup
-
-# todo super last refactor
-
from __future__ import annotations
import argparse
diff --git a/bec_widgets/widgets/__init__.py b/bec_widgets/widgets/__init__.py
index d5eb7e43..5cebaa62 100644
--- a/bec_widgets/widgets/__init__.py
+++ b/bec_widgets/widgets/__init__.py
@@ -4,3 +4,4 @@ from .scan_control import ScanControl
from .toolbar import ModularToolBar
from .editor import BECEditor
from .monitor_scatter_2D import BECMonitor2DScatter
+from .motor_control import MotorControlRelative, MotorControlAbsolute, MotorControlSelection
diff --git a/bec_widgets/widgets/motor_control/__init__.py b/bec_widgets/widgets/motor_control/__init__.py
new file mode 100644
index 00000000..e9af9b26
--- /dev/null
+++ b/bec_widgets/widgets/motor_control/__init__.py
@@ -0,0 +1 @@
+from .motor_control import MotorControlRelative, MotorControlAbsolute, MotorControlSelection
diff --git a/bec_widgets/widgets/motor_control/motor_control.py b/bec_widgets/widgets/motor_control/motor_control.py
new file mode 100644
index 00000000..b613b8b9
--- /dev/null
+++ b/bec_widgets/widgets/motor_control/motor_control.py
@@ -0,0 +1,699 @@
+# pylint: disable = no-name-in-module,missing-module-docstring
+import os
+from enum import Enum
+
+import qdarktheme
+
+from pyqtgraph.Qt import uic
+from qtpy import uic
+from qtpy.QtCore import QThread, Slot as pyqtSlot
+from qtpy.QtCore import Signal as pyqtSignal, Qt
+from qtpy.QtGui import QKeySequence
+from qtpy.QtWidgets import (
+ QApplication,
+ QWidget,
+ QSpinBox,
+ QDoubleSpinBox,
+ QShortcut,
+)
+
+from qtpy.QtWidgets import QApplication, QMessageBox
+from bec_lib.alarm_handler import AlarmBase
+from bec_lib.device import Positioner
+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 MotorControlConnectAbsoltue(QWidget):
+
+
+# class MotorControlPanel(QWidget):
+# def __init__(self,parent=None):
+# super().__init__()
+# self.init_ui()
+# def init_ui(self):
+# """Initialize the UI."""
+class MotorControlSelection(QWidget):
+ update_signal = pyqtSignal()
+ selected_motors_signal = pyqtSignal(str, str)
+
+ def __init__(
+ self,
+ parent=None,
+ client=None,
+ motor_thread=None,
+ config: dict = None,
+ ):
+ super().__init__(parent=parent)
+
+ bec_dispatcher = BECDispatcher()
+ self.client = bec_dispatcher.client if client is None else client
+ self.dev = self.client.device_manager.devices
+ self.config = config
+
+ # Loading UI
+ current_path = os.path.dirname(__file__)
+ uic.loadUi(os.path.join(current_path, "motor_control_selection.ui"), self)
+
+ # Motor Control Thread
+ self.motor_thread = (
+ MotorThread(client=self.client) if motor_thread is None else motor_thread
+ )
+
+ 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 _init_ui(self):
+ """Initialize the UI."""
+ self.pushButton_connecMotors.clicked.connect(self.select_motor)
+ self.get_available_motors()
+
+ @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()
+
+ def get_available_motors(self):
+ """
+ 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 is not None:
+ index_x = self.comboBox_motor_x.findText(self.motor_x)
+ index_y = self.comboBox_motor_y.findText(self.motor_y)
+
+ if index_x != -1:
+ self.comboBox_motor_x.setCurrentIndex(index_x)
+ else:
+ print(
+ f"Warning: Motor '{self.motor_x}' specified in the config file is not available."
+ )
+ self.comboBox_motor_x.setCurrentIndex(0)
+
+ if index_y != -1:
+ self.comboBox_motor_y.setCurrentIndex(index_y)
+ else:
+ print(
+ f"Warning: Motor '{self.motor_y}' specified in the config file is not available."
+ )
+ self.comboBox_motor_y.setCurrentIndex(0)
+ if index_x != -1 and index_y != -1:
+ self.selected_motors_signal.emit(self.motor_x, self.motor_y)
+ # setup default index 0, if there is no config
+ else:
+ self.comboBox_motor_x.setCurrentIndex(0)
+ self.comboBox_motor_y.setCurrentIndex(0)
+
+ def select_motor(self):
+ """Emit the selected motors"""
+ motor_x = self.comboBox_motor_x.currentText()
+ motor_y = self.comboBox_motor_y.currentText()
+
+ self.selected_motors_signal.emit(motor_x, motor_y)
+ print(f"emitted motors {motor_x} and {motor_y}")
+
+
+class MotorControlAbsolute(QWidget):
+ update_signal = pyqtSignal()
+ coordinates_signal = pyqtSignal(tuple)
+
+ def __init__(
+ self,
+ parent=None,
+ client=None,
+ motor_thread=None,
+ config: dict = None,
+ ):
+ super().__init__(parent=parent)
+
+ bec_dispatcher = BECDispatcher()
+ self.client = bec_dispatcher.client if client is None else client
+ self.dev = self.client.device_manager.devices
+ self.config = config
+
+ # Loading UI
+ current_path = os.path.dirname(__file__)
+ uic.loadUi(os.path.join(current_path, "motor_control_absolute.ui"), self)
+
+ # Motor Control Thread
+ self.motor_thread = (
+ MotorThread(client=self.client) if motor_thread is None else motor_thread
+ )
+
+ 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)
+
+ @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()
+
+ 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
+ ): # TODO change logic of checking -> jsut check names
+ self.motorControl_absolute.setEnabled(False)
+ return
+
+ # Move to absolute coordinates
+ self.pushButton_go_absolute.clicked.connect(
+ lambda: self.move_motor_absolute(
+ self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()
+ )
+ )
+
+ self.pushButton_set.clicked.connect(self.save_absolute_coordinates)
+ self.pushButton_save.clicked.connect(self.save_current_coordinates)
+ self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
+
+ # Enable/Disable GUI
+ self.motor_thread.move_finished.connect(lambda: self._enable_motor_controls(True))
+
+ # Error messages
+ self.motor_thread.motor_error.connect(
+ lambda error: MotorControlErrors.display_error_message(error)
+ )
+
+ def _enable_motor_controls(self, disable: bool) -> None:
+ # Disable or enable all controls within the motorControl_absolute group box
+ for widget in self.motorControl_absolute.findChildren(QWidget):
+ widget.setEnabled(disable)
+
+ # Enable the pushButton_stop if the motor is moving
+ self.pushButton_stop.setEnabled(True)
+
+ def move_motor_absolute(self, x: float, y: float) -> None:
+ self._enable_motor_controls(False)
+ target_coordinates = (x, y)
+ self.motor_thread.move_absolute(self.motor_x, self.motor_y, target_coordinates)
+ if self.checkBox_save_with_go.isChecked():
+ self.save_absolute_coordinates()
+
+ def _init_keyboard_shortcuts(self):
+ # Go absolute button
+ self.pushButton_go_absolute.setShortcut("Ctrl+G")
+ self.pushButton_go_absolute.setToolTip("Ctrl+G")
+
+ # Set absolute coordinates
+ self.pushButton_set.setShortcut("Ctrl+D")
+ self.pushButton_set.setToolTip("Ctrl+D")
+
+ # Save Current coordinates
+ self.pushButton_save.setShortcut("Ctrl+S")
+ self.pushButton_save.setToolTip("Ctrl+S")
+
+ # Stop Button
+ self.pushButton_stop.setShortcut("Ctrl+X")
+ self.pushButton_stop.setToolTip("Ctrl+X")
+
+ def save_absolute_coordinates(self):
+ """Emit the setup coordinates from the spinboxes"""
+
+ x, y = round(self.spinBox_absolute_x.value(), self.precision), round(
+ self.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.retrieve_coordinates()
+ self.coordinates_signal.emit((x, y))
+
+
+class MotorControlRelative(QWidget):
+ update_signal = pyqtSignal()
+
+ def __init__(
+ self,
+ parent=None,
+ client=None,
+ motor_thread=None,
+ config: dict = None,
+ ):
+ super().__init__(parent=parent)
+
+ # BECclient
+ bec_dispatcher = BECDispatcher()
+ self.client = bec_dispatcher.client if client is None else client
+ self.dev = self.client.device_manager.devices
+ self.config = config
+
+ # Loading UI
+ current_path = os.path.dirname(__file__)
+ uic.loadUi(os.path.join(current_path, "motor_control_relative.ui"), self)
+
+ # Motor Control Thread
+ self.motor_thread = (
+ MotorThread(client=self.client) if motor_thread is None else motor_thread
+ )
+
+ self._init_ui()
+ if self.config is None:
+ print(f"No initial config found for {self.__class__.__name__}")
+ else:
+ self.on_config_update(self.config)
+
+ 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"""
+ 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.spinBox_precision.setValue(self.precision)
+
+ # Update step sizes
+ self.spinBox_step_x.setValue(self.config["motor_control"]["step_size_x"])
+ self.spinBox_step_y.setValue(self.config["motor_control"]["step_size_y"])
+
+ # Checkboxes for keyboard shortcuts and x/y step size link
+ self.checkBox_same_xy.setChecked(self.config["motor_control"]["step_x_y_same"])
+ self.checkBox_enableArrows.setChecked(self.config["motor_control"]["move_with_arrows"])
+
+ def _init_ui_motor_control(self) -> None:
+ """Initialize the motor control elements"""
+
+ # Connect checkbox and spinBoxes
+ self.checkBox_same_xy.stateChanged.connect(self._sync_step_sizes)
+ self.spinBox_step_x.valueChanged.connect(self._update_step_size_x)
+ self.spinBox_step_y.valueChanged.connect(self._update_step_size_y)
+
+ self.toolButton_right.clicked.connect(
+ lambda: self.move_motor_relative(self.motor_x, "x", 1)
+ )
+ self.toolButton_left.clicked.connect(
+ lambda: self.move_motor_relative(self.motor_x, "x", -1)
+ )
+ self.toolButton_up.clicked.connect(lambda: self.move_motor_relative(self.motor_y, "y", 1))
+ self.toolButton_down.clicked.connect(
+ lambda: self.move_motor_relative(self.motor_y, "y", -1)
+ )
+
+ # Switch between key shortcuts active
+ self.checkBox_enableArrows.stateChanged.connect(self._update_arrow_key_shortcuts)
+ self._update_arrow_key_shortcuts()
+
+ # Enable/Disable GUI
+ self.motor_thread.move_finished.connect(lambda: self.enable_motor_controls(True))
+
+ # Precision update
+ self.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)
+ )
+
+ 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.spinBox_step_x, 2)
+ )
+ decrease_x_shortcut.activated.connect(
+ lambda: self._change_step_size(self.spinBox_step_x, 0.5)
+ )
+
+ # 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.spinBox_step_y, 2)
+ )
+ decrease_y_shortcut.activated.connect(
+ lambda: self._change_step_size(self.spinBox_step_y, 0.5)
+ )
+
+ # Stop Button
+ self.pushButton_stop.setShortcut("Ctrl+X")
+ self.pushButton_stop.setToolTip("Ctrl+X")
+
+ def _update_arrow_key_shortcuts(self):
+ """Update the arrow key shortcuts based on the checkbox state."""
+ if self.checkBox_enableArrows.isChecked():
+ # Set the arrow key shortcuts for motor movement
+ self.toolButton_right.setShortcut(Qt.Key_Right)
+ self.toolButton_left.setShortcut(Qt.Key_Left)
+ self.toolButton_up.setShortcut(Qt.Key_Up)
+ self.toolButton_down.setShortcut(Qt.Key_Down)
+ else:
+ # Clear the shortcuts
+ self.toolButton_right.setShortcut("")
+ self.toolButton_left.setShortcut("")
+ self.toolButton_up.setShortcut("")
+ self.toolButton_down.setShortcut("")
+
+ def _update_precision(self, precision: int):
+ """Update the precision of the spinboxes."""
+ self.spinBox_step_x.setDecimals(precision)
+ self.spinBox_step_y.setDecimals(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.checkBox_same_xy.isChecked():
+ value = self.spinBox_step_x.value()
+ self.spinBox_step_y.setValue(value)
+
+ def _update_step_size_x(self):
+ """Update step size for x if checkbox is checked."""
+ if self.checkBox_same_xy.isChecked():
+ value = self.spinBox_step_x.value()
+ self.spinBox_step_y.setValue(value)
+
+ def _update_step_size_y(self):
+ """Update step size for y if checkbox is checked."""
+ if self.checkBox_same_xy.isChecked():
+ value = self.spinBox_step_y.value()
+ self.spinBox_step_x.setValue(value)
+
+ @pyqtSlot()
+ def enable_motor_control(self):
+ """Enable the motor control buttons."""
+ self.motorControl.setEnabled(True)
+
+ @pyqtSlot(str, str)
+ def change_motors(self, motor_x: str, motor_y: str):
+ """
+ Change the active motors and update config.
+ 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
+
+ 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.motorControl.findChildren(QWidget):
+ widget.setEnabled(disable)
+
+ # Enable the pushButton_stop if the motor is moving
+ self.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.
+ """
+ self.enable_motor_controls(False)
+ if axis == "x":
+ step = direction * self.spinBox_step_x.value()
+ elif axis == "y":
+ step = direction * self.spinBox_step_y.value()
+ self.motor_thread.move_relative(motor, step)
+
+
+class MotorControlErrors:
+ @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.
+
+ Attributes:
+ coordinates_updated (pyqtSignal): Signal to emit current coordinates.
+ limits_retrieved (pyqtSignal): Signal to emit current limits.
+ move_finished (pyqtSignal): Signal to emit when the move is finished.
+ motors_loaded (pyqtSignal): Signal to emit when the motors are loaded.
+ motors_selected (pyqtSignal): Signal to emit when the motors are selected.
+ """
+
+ coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates
+ limits_retrieved = pyqtSignal(list, list) # Signal to emit current limits #TODO remove?
+ move_finished = pyqtSignal() # Signal to emit when the move is finished
+ motors_loaded = pyqtSignal(
+ list, list
+ ) # Signal to emit when the motors are loaded #todo remove?
+ motors_selected = pyqtSignal(
+ object, object
+ ) # Signal to emit when the motors are selected #TODO remove?
+ motor_error = pyqtSignal(str) # Signal to emit when there is an error with the motors
+
+ 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.
+ """
+ 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.move_finished.emit()
+
+ 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.
+ """
+ try:
+ status = self.scans.mv(self.dev[motor], value, relative=True)
+ status.wait()
+ except AlarmBase as e:
+ print(e)
+ self.motor_error.emit(str(e))
+ finally:
+ self.move_finished.emit()
+
+ def stop_movement(self):
+ self.queue.request_scan_abortion()
+ self.queue.request_queue_reset()
+
+
+if __name__ == "__main__":
+ bec_dispatcher = BECDispatcher()
+ # BECclient global variables
+ client = bec_dispatcher.client
+ client.start()
+
+ app = QApplication([])
+ qdarktheme.setup_theme("auto")
+ # motor_control = MotorControlRelative(client=client, config=CONFIG_DEFAULT)
+
+ motor_control = MotorControlSelection(client=client, config=CONFIG_DEFAULT)
+ window = motor_control
+ window.show()
+ app.exec()
diff --git a/bec_widgets/widgets/motor_control/motor_control_absolute.ui b/bec_widgets/widgets/motor_control/motor_control_absolute.ui
new file mode 100644
index 00000000..ea4ec863
--- /dev/null
+++ b/bec_widgets/widgets/motor_control/motor_control_absolute.ui
@@ -0,0 +1,149 @@
+
+
+ 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/motor_control_relative.ui b/bec_widgets/widgets/motor_control/motor_control_relative.ui
new file mode 100644
index 00000000..78fed89b
--- /dev/null
+++ b/bec_widgets/widgets/motor_control/motor_control_relative.ui
@@ -0,0 +1,298 @@
+
+
+ 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/motor_control_selection.ui b/bec_widgets/widgets/motor_control/motor_control_selection.ui
new file mode 100644
index 00000000..6953c2e2
--- /dev/null
+++ b/bec_widgets/widgets/motor_control/motor_control_selection.ui
@@ -0,0 +1,69 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 285
+ 156
+
+
+
+
+ 285
+ 156
+
+
+
+ Form
+
+
+ -
+
+
+
+ 261
+ 145
+
+
+
+ Motor Selection
+
+
+
-
+
+
+ Motor X
+
+
+
+ -
+
+
+ -
+
+
+ Motor Y
+
+
+
+ -
+
+
+ Connect Motors
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+