0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 11:41:49 +02:00

feat: motor_control.py MotorControl widgets - Absolute + Relative movement, MotorSelection, ErrorMessage popups

This commit is contained in:
wyzula-jan
2024-01-25 16:21:57 +01:00
parent 968da6f558
commit 6fe08e6b82
7 changed files with 1217 additions and 4 deletions

View File

@ -1,7 +1,3 @@
# TODO last backup
# todo super last refactor
from __future__ import annotations
import argparse

View File

@ -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

View File

@ -0,0 +1 @@
from .motor_control import MotorControlRelative, MotorControlAbsolute, MotorControlSelection

View File

@ -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"<b>{line.strip()}</b>" 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 = "<br><br>".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()

View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>285</width>
<height>220</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>285</width>
<height>220</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>285</width>
<height>220</height>
</size>
</property>
<property name="windowTitle">
<string>Move Movement Absolute</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="motorControl_absolute">
<property name="minimumSize">
<size>
<width>261</width>
<height>195</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>261</width>
<height>195</height>
</size>
</property>
<property name="title">
<string>Move Movement Absolute</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="checkBox_save_with_go">
<property name="text">
<string>Save position with Go</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="spinBox_absolute_y">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="minimum">
<double>-500.000000000000000</double>
</property>
<property name="maximum">
<double>500.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QDoubleSpinBox" name="spinBox_absolute_x">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="minimum">
<double>-500.000000000000000</double>
</property>
<property name="maximum">
<double>500.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Y</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>X</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="pushButton_save">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_set">
<property name="text">
<string>Set</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_go_absolute">
<property name="text">
<string>Go</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButton_stop">
<property name="text">
<string>Stop Movement</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,298 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>285</width>
<height>405</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>285</width>
<height>405</height>
</size>
</property>
<property name="windowTitle">
<string>Motor Control Relative</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="motorControl">
<property name="minimumSize">
<size>
<width>261</width>
<height>394</height>
</size>
</property>
<property name="title">
<string>Motor Control Relative</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="checkBox_enableArrows">
<property name="text">
<string>Move with arrow keys</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_same_xy">
<property name="text">
<string>Step [X] = Step [Y]</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="step_grid">
<item row="2" column="0">
<widget class="QLabel" name="label_step_y">
<property name="minimumSize">
<size>
<width>111</width>
<height>19</height>
</size>
</property>
<property name="text">
<string>Step [Y]</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="minimumSize">
<size>
<width>111</width>
<height>19</height>
</size>
</property>
<property name="text">
<string>Decimal</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="spinBox_step_x">
<property name="minimumSize">
<size>
<width>110</width>
<height>19</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>99.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_step_x">
<property name="minimumSize">
<size>
<width>111</width>
<height>19</height>
</size>
</property>
<property name="text">
<string>Step [X]</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="spinBox_step_y">
<property name="minimumSize">
<size>
<width>110</width>
<height>19</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>99.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinBox_precision">
<property name="minimumSize">
<size>
<width>110</width>
<height>19</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="maximum">
<number>8</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="direction_grid">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item row="1" column="2" alignment="Qt::AlignHCenter|Qt::AlignVCenter">
<widget class="QToolButton" name="toolButton_up">
<property name="minimumSize">
<size>
<width>26</width>
<height>26</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
<property name="arrowType">
<enum>Qt::UpArrow</enum>
</property>
</widget>
</item>
<item row="2" column="4">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="2" alignment="Qt::AlignHCenter|Qt::AlignVCenter">
<widget class="QToolButton" name="toolButton_down">
<property name="minimumSize">
<size>
<width>26</width>
<height>26</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
<property name="arrowType">
<enum>Qt::DownArrow</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QToolButton" name="toolButton_left">
<property name="minimumSize">
<size>
<width>26</width>
<height>26</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
<property name="arrowType">
<enum>Qt::LeftArrow</enum>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="3">
<widget class="QToolButton" name="toolButton_right">
<property name="minimumSize">
<size>
<width>26</width>
<height>26</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
<property name="arrowType">
<enum>Qt::RightArrow</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButton_stop">
<property name="text">
<string>Stop Movement</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>285</width>
<height>156</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>285</width>
<height>156</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="motorSelection">
<property name="minimumSize">
<size>
<width>261</width>
<height>145</height>
</size>
</property>
<property name="title">
<string>Motor Selection</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Motor X</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBox_motor_x"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Motor Y</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QPushButton" name="pushButton_connecMotors">
<property name="text">
<string>Connect Motors</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="comboBox_motor_y"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>