From 8139e271de115819e05b7dd03ec6603d50f5482c Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Mon, 29 Jan 2024 14:23:34 +0100
Subject: [PATCH] refactor: base class for motor_control.py widgets
---
bec_widgets/widgets/__init__.py | 7 +-
bec_widgets/widgets/motor_control/__init__.py | 7 +-
.../widgets/motor_control/motor_control.py | 338 ++++++++----------
.../motor_control/motor_control_selection.ui | 2 +-
4 files changed, 171 insertions(+), 183 deletions(-)
diff --git a/bec_widgets/widgets/__init__.py b/bec_widgets/widgets/__init__.py
index 5cebaa62..54387b6f 100644
--- a/bec_widgets/widgets/__init__.py
+++ b/bec_widgets/widgets/__init__.py
@@ -4,4 +4,9 @@ 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
+from .motor_control import (
+ MotorControlRelative,
+ MotorControlAbsolute,
+ MotorControlSelection,
+ MotorThread,
+)
diff --git a/bec_widgets/widgets/motor_control/__init__.py b/bec_widgets/widgets/motor_control/__init__.py
index e9af9b26..4a259ca2 100644
--- a/bec_widgets/widgets/motor_control/__init__.py
+++ b/bec_widgets/widgets/motor_control/__init__.py
@@ -1 +1,6 @@
-from .motor_control import MotorControlRelative, MotorControlAbsolute, MotorControlSelection
+from .motor_control import (
+ MotorControlRelative,
+ MotorControlAbsolute,
+ MotorControlSelection,
+ MotorThread,
+)
diff --git a/bec_widgets/widgets/motor_control/motor_control.py b/bec_widgets/widgets/motor_control/motor_control.py
index b613b8b9..adc51bbb 100644
--- a/bec_widgets/widgets/motor_control/motor_control.py
+++ b/bec_widgets/widgets/motor_control/motor_control.py
@@ -4,7 +4,6 @@ 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
@@ -34,41 +33,24 @@ CONFIG_DEFAULT = {
}
}
-# class MotorControlConnectAbsoltue(QWidget):
+class MotorControlWidget(QWidget):
+ """Base class for motor control widgets."""
-# 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
+ 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
- # Loading UI
- current_path = os.path.dirname(__file__)
- uic.loadUi(os.path.join(current_path, "motor_control_selection.ui"), self)
+ if not self.client:
+ bec_dispatcher = BECDispatcher()
+ self.client = bec_dispatcher.client
- # Motor Control Thread
- self.motor_thread = (
- MotorThread(client=self.client) if motor_thread is None else motor_thread
- )
+ 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__}")
@@ -76,8 +58,44 @@ class MotorControlSelection(QWidget):
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 MotorControlSelection(MotorControlWidget):
+ """
+ Widget for selecting the motors to control.
+
+ Signals:
+ selected_motors_signal (pyqtSignal): 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.
+ select_motor (pyqtSlot): Slot to emit the selected motors.
+ enable_motor_controls (pyqtSlot): Slot to enable/disable the motor controls GUI.
+ """
+
+ 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, "motor_control_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.motor_thread.move_finished.connect(self.motorSelection.setEnabled(True))
+
self.pushButton_connecMotors.clicked.connect(self.select_motor)
self.get_available_motors()
@@ -98,6 +116,15 @@ class MotorControlSelection(QWidget):
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)
+
def get_available_motors(self):
"""
Slot to populate the available motors in the combo boxes and set the index based on the configuration.
@@ -110,31 +137,11 @@ class MotorControlSelection(QWidget):
self.comboBox_motor_y.addItems(self.motor_list)
# Set the index based on the config if provided
- if self.config is not None:
+ if self.config:
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)
+ 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 select_motor(self):
"""Emit the selected motors"""
@@ -142,41 +149,53 @@ class MotorControlSelection(QWidget):
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()
+class MotorControlAbsolute(MotorControlWidget):
+ """
+ Widget for controlling the motors to absolute coordinates.
+
+ Signals:
+ coordinates_signal (pyqtSignal): Signal to emit the coordinates.
+ Slots:
+ change_motors (pyqtSlot): Slot to change the active motors.
+ enable_motor_controls (pyqtSlot): Slot to enable/disable the motor controls.
+ """
+
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
+ def _load_ui(self):
+ """Load the UI from the .ui file."""
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
+ 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.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()
+ )
)
- 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)
+ 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.lock_gui.connect(self.enable_motor_controls)
+ # 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)
+ )
@pyqtSlot(dict)
def on_config_update(self, config: dict) -> None:
@@ -194,51 +213,50 @@ class MotorControlAbsolute(QWidget):
self._init_ui()
- def _init_ui(self):
- """Initialize the 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.
+ """
- # 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)
+ widget.setEnabled(enable)
# Enable the pushButton_stop if the motor is moving
self.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
+
def move_motor_absolute(self, x: float, y: float) -> None:
- self._enable_motor_controls(False)
+ """
+ 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.checkBox_save_with_go.isChecked():
self.save_absolute_coordinates()
def _init_keyboard_shortcuts(self):
+ """Initialize the keyboard shortcuts."""
# Go absolute button
self.pushButton_go_absolute.setShortcut("Ctrl+G")
self.pushButton_go_absolute.setToolTip("Ctrl+G")
@@ -265,43 +283,27 @@ class MotorControlAbsolute(QWidget):
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))
+ 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)))
-class MotorControlRelative(QWidget):
- update_signal = pyqtSignal()
+class MotorControlRelative(MotorControlWidget):
+ """
+ Widget for controlling the motors to relative coordinates.
- 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
+ Signals:
+ coordinates_signal (pyqtSignal): Signal to emit the coordinates.
+ Slots:
+ change_motors (pyqtSlot): Slot to change the active motors.
+ enable_motor_controls (pyqtSlot): Slot to enable/disable the motor controls.
+ """
+ def _load_ui(self):
+ """Load the UI from the .ui file."""
# 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()
@@ -330,6 +332,8 @@ class MotorControlRelative(QWidget):
self.checkBox_same_xy.setChecked(self.config["motor_control"]["step_x_y_same"])
self.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"""
@@ -354,7 +358,7 @@ class MotorControlRelative(QWidget):
self._update_arrow_key_shortcuts()
# Enable/Disable GUI
- self.motor_thread.move_finished.connect(lambda: self.enable_motor_controls(True))
+ self.motor_thread.lock_gui.connect(self.enable_motor_controls)
# Precision update
self.spinBox_precision.valueChanged.connect(lambda x: self._update_precision(x))
@@ -440,15 +444,11 @@ class MotorControlRelative(QWidget):
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.
+ 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.
@@ -458,11 +458,12 @@ class MotorControlRelative(QWidget):
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.
+ enable(bool): True to disable, False to enable.
"""
# Disable or enable all controls within the motorControl_absolute group box
@@ -480,7 +481,6 @@ class MotorControlRelative(QWidget):
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":
@@ -489,6 +489,8 @@ class MotorControlRelative(QWidget):
class MotorControlErrors:
+ """Class for displaying formatted error messages."""
+
@staticmethod
def display_error_message(error_message: str) -> None:
"""
@@ -543,24 +545,15 @@ class MotorThread(QThread):
"""
QThread subclass for controlling motor actions asynchronously.
- Attributes:
+ Signals:
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.
+ 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
- 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
+ lock_gui = pyqtSignal(bool) # Signal to lock/unlock the GUI
def __init__(self, parent=None, client=None):
super().__init__(parent)
@@ -648,6 +641,7 @@ class MotorThread(QThread):
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],
@@ -660,7 +654,7 @@ class MotorThread(QThread):
except AlarmBase as e:
self.motor_error.emit(str(e))
finally:
- self.move_finished.emit()
+ self.lock_gui.emit(True)
def _move_motor_relative(self, motor, value: float) -> None:
"""
@@ -669,31 +663,15 @@ class MotorThread(QThread):
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:
- print(e)
self.motor_error.emit(str(e))
finally:
- self.move_finished.emit()
+ self.lock_gui.emit(True)
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_selection.ui b/bec_widgets/widgets/motor_control/motor_control_selection.ui
index 6953c2e2..406363df 100644
--- a/bec_widgets/widgets/motor_control/motor_control_selection.ui
+++ b/bec_widgets/widgets/motor_control/motor_control_selection.ui
@@ -17,7 +17,7 @@
- Form
+ Motor Control Selection
-