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 MotorCoordinateTable added basic version to store coordinates and show them in motor_map.py

This commit is contained in:
wyzula-jan
2024-02-06 13:20:05 +01:00
parent 8afc5f0c0c
commit 031cb094e7
7 changed files with 355 additions and 1 deletions

View File

@ -0,0 +1,6 @@
from .motor_movement import (
MotorControlMap,
MotorControlPanel,
MotorControlPanelRelative,
MotorControlPanelAbsolute,
)

View File

@ -0,0 +1,6 @@
from .motor_control_compilations import (
MotorControlMap,
MotorControlPanel,
MotorControlPanelRelative,
MotorControlPanelAbsolute,
)

View File

@ -9,4 +9,5 @@ from .motor_control import (
MotorControlAbsolute,
MotorControlSelection,
MotorThread,
MotorCoordinateTable,
)

View File

@ -3,4 +3,5 @@ from .motor_control import (
MotorControlAbsolute,
MotorControlSelection,
MotorThread,
MotorCoordinateTable,
)

View File

@ -3,6 +3,8 @@ import os
from enum import Enum
import qdarktheme
from PyQt6.QtGui import QDoubleValidator
from PyQt6.QtWidgets import QPushButton, QTableWidgetItem, QCheckBox, QLineEdit
from qtpy import uic
from qtpy.QtCore import QThread, Slot as pyqtSlot
@ -10,10 +12,13 @@ from qtpy.QtCore import Signal as pyqtSignal, Qt
from qtpy.QtGui import QKeySequence
from qtpy.QtWidgets import (
QApplication,
QHeaderView,
QWidget,
QSpinBox,
QDoubleSpinBox,
QShortcut,
QVBoxLayout,
QTableWidget,
)
from qtpy.QtWidgets import QApplication, QMessageBox
@ -205,7 +210,6 @@ class MotorControlAbsolute(MotorControlWidget):
# 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(
@ -508,6 +512,237 @@ class MotorControlRelative(MotorControlWidget):
self.motor_thread.move_relative(motor, step)
class MotorCoordinateTable(MotorControlWidget):
plot_coordinates_signal = pyqtSignal(list)
def _load_ui(self):
"""Load the UI for the coordinate table."""
current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "motor_control_table.ui"), self)
def _init_ui(self):
"""Initialize the UI"""
# Setup table behaviour
self._setup_table()
self.table.setSelectionBehavior(QTableWidget.SelectRows)
# for tag columns default tag
self.tag_counter = 1
# Connect signals and slots
self.checkBox_resize_auto.stateChanged.connect(self.resize_table_auto)
# Keyboard shortcuts for deleting a row
self.delete_shortcut = QShortcut(QKeySequence(Qt.Key_Delete), self.table)
self.delete_shortcut.activated.connect(self.delete_selected_row)
self.backspace_shortcut = QShortcut(QKeySequence(Qt.Key_Backspace), self.table)
self.backspace_shortcut.activated.connect(self.delete_selected_row)
@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.mode_combobox.setCurrentText(self.mode)
self._init_ui()
def _setup_table(self):
"""Setup the table with appropriate headers and configurations."""
mode = self.mode_combobox.currentText()
if mode == "Individual":
self._setup_individual_mode()
elif mode == "Start/Stop":
self._setup_start_stop_mode()
self.start_stop_counter = 0
def _setup_individual_mode(self):
self.table.setColumnCount(5)
self.table.setHorizontalHeaderLabels(["Show", "Move", "Tag", "X", "Y"])
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.table.verticalHeader().setVisible(False)
def _setup_start_stop_mode(self):
self.table.setColumnCount(8)
self.table.setHorizontalHeaderLabels(
[
"Show",
"Move [start]",
"Move [end]" "Tag",
"X [start]",
"Y [start]",
"X [end]",
"Y [end]",
]
)
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.table.verticalHeader().setVisible(False)
@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, x, y):
"""Internal method to add a row to the table."""
row_count = self.table.rowCount()
self.table.insertRow(row_count)
# Checkbox for toggling visibility
show_checkbox = QCheckBox()
show_checkbox.setChecked(True)
show_checkbox.stateChanged.connect(self.emit_plot_coordinates)
self.table.setCellWidget(row_count, 0, show_checkbox)
# TODO add mode switch recognision
# Move button
move_button = QPushButton("Move")
move_button.clicked.connect(self.handle_move_button_click)
self.table.setCellWidget(row_count, 1, move_button)
# Tag
self.table.setItem(row_count, 2, QTableWidgetItem(tag))
# Adding validator
validator = QDoubleValidator()
validator.setDecimals(self.precision)
# X as QLineEdit with validator
x_edit = QLineEdit(str(f"{x:.{self.precision}f}"))
x_edit.setValidator(validator)
self.table.setCellWidget(row_count, 3, x_edit)
x_edit.textChanged.connect(self.emit_plot_coordinates)
# Y as QLineEdit with validator
y_edit = QLineEdit(str(f"{y:.{self.precision}f}"))
y_edit.setValidator(validator)
self.table.setCellWidget(row_count, 4, y_edit)
y_edit.textChanged.connect(self.emit_plot_coordinates)
# Emit the coordinates to be plotted
self.emit_plot_coordinates()
# Connect item edit to emit coordinates
self.table.itemChanged.connect(self.emit_plot_coordinates)
# Auto table resize
self.resize_table_auto()
# Align table center
self._align_table_center()
def handle_move_button_click(self):
"""Handle the click event of the move button."""
button = self.sender()
row = self.table.indexAt(button.pos()).row()
x = self.get_coordinate(row, 3)
y = self.get_coordinate(row, 4)
self.move_motor(x, y)
# Emit updated coordinates to update the map
self.emit_plot_coordinates()
def emit_plot_coordinates(self):
"""Emit the coordinates to be plotted."""
coordinates = []
for row in range(self.table.rowCount()):
show = self.table.cellWidget(row, 0).isChecked()
x = self.get_coordinate(row, 3)
y = self.get_coordinate(row, 4)
coordinates.append((x, y, show)) # (x, y, show_flag)
self.plot_coordinates_signal.emit(coordinates)
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.table.cellWidget(row, column)
if edit.text() is not None and edit.text() != "":
value = float(edit.text()) if edit else None
return value
def delete_selected_row(self):
"""Delete the selected row from the table."""
selected_rows = self.table.selectionModel().selectedRows()
for row in selected_rows:
self.table.removeRow(row.row())
self.emit_plot_coordinates()
def resize_table_auto(self):
"""Resize the table to fit the contents."""
if self.checkBox_resize_auto.isChecked():
self.table.resizeColumnsToContents()
def _align_table_center(self) -> None:
for row in range(self.table.rowCount()):
for col in range(self.table.columnCount()):
item = self.table.item(row, col)
if item:
item.setTextAlignment(Qt.AlignCenter)
def move_motor(self, x, y):
"""Move the motor to the specified coordinates."""
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):
"""
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
class MotorControlErrors:
"""Class for displaying formatted error messages."""

View File

@ -0,0 +1,85 @@
<?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>676</width>
<height>667</height>
</rect>
</property>
<property name="windowTitle">
<string>Motor Coordinates Table</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer_4">
<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>
<widget class="QCheckBox" name="checkBox_resize_auto">
<property name="text">
<string>Resize Auto</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_editColumns">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Edit Custom Column</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Entries Mode:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="mode_combobox">
<property name="enabled">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>Individual</string>
</property>
</item>
<item>
<property name="text">
<string>Start/Stop</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="table"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -513,6 +513,26 @@ class MotorMap(pg.GraphicsLayoutWidget):
self.curves_data[plot_name]["highlight_V"].setPos(current_x)
self.curves_data[plot_name]["highlight_H"].setPos(current_y)
@pyqtSlot(list)
def plot_saved_coordinates(self, coordinates):
"""Plot the saved coordinates on the map."""
for plot_name in self.plots:
plot = self.plots[plot_name]
# Clear previous saved points
if "saved_points" in self.curves_data[plot_name]:
plot.removeItem(self.curves_data[plot_name]["saved_points"])
# Filter coordinates to be shown
visible_coords = [coord[:2] for coord in coordinates if coord[2]]
if visible_coords:
saved_points = pg.ScatterPlotItem(
pos=np.array(visible_coords), brush=pg.mkBrush(0, 255, 0, 255)
)
plot.addItem(saved_points)
self.curves_data[plot_name]["saved_points"] = saved_points
@pyqtSlot(dict)
def on_device_readback(self, msg: dict):
"""