bec-widgets 0.53.3__py3-none-any.whl → 0.55.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- CHANGELOG.md +24 -26
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +265 -13
- bec_widgets/cli/client_utils.py +0 -3
- bec_widgets/cli/generate_cli.py +10 -5
- bec_widgets/cli/rpc_wigdet_handler.py +2 -1
- bec_widgets/cli/server.py +5 -7
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +11 -5
- bec_widgets/examples/motor_movement/motor_control_compilations.py +17 -16
- bec_widgets/widgets/__init__.py +1 -10
- bec_widgets/widgets/figure/figure.py +40 -23
- bec_widgets/widgets/figure/plots/__init__.py +0 -0
- bec_widgets/widgets/figure/plots/image/__init__.py +0 -0
- bec_widgets/widgets/{plots → figure/plots/image}/image.py +6 -416
- bec_widgets/widgets/figure/plots/image/image_item.py +277 -0
- bec_widgets/widgets/figure/plots/image/image_processor.py +152 -0
- bec_widgets/widgets/figure/plots/motor_map/__init__.py +0 -0
- bec_widgets/widgets/{plots → figure/plots/motor_map}/motor_map.py +2 -2
- bec_widgets/widgets/figure/plots/waveform/__init__.py +0 -0
- bec_widgets/widgets/{plots → figure/plots/waveform}/waveform.py +9 -222
- bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +227 -0
- bec_widgets/widgets/motor_control/__init__.py +0 -7
- bec_widgets/widgets/motor_control/motor_control.py +2 -948
- bec_widgets/widgets/motor_control/motor_table/__init__.py +0 -0
- bec_widgets/widgets/motor_control/motor_table/motor_table.py +483 -0
- bec_widgets/widgets/motor_control/movement_absolute/__init__.py +0 -0
- bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +157 -0
- bec_widgets/widgets/motor_control/movement_relative/__init__.py +0 -0
- bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +227 -0
- bec_widgets/widgets/motor_control/selection/__init__.py +0 -0
- bec_widgets/widgets/motor_control/selection/selection.py +110 -0
- bec_widgets/widgets/spiral_progress_bar/__init__.py +1 -0
- bec_widgets/widgets/spiral_progress_bar/ring.py +184 -0
- bec_widgets/widgets/spiral_progress_bar/spiral_progress_bar.py +594 -0
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/RECORD +56 -53
- docs/requirements.txt +1 -0
- pyproject.toml +1 -1
- tests/end-2-end/test_bec_dock_rpc_e2e.py +82 -1
- tests/end-2-end/test_bec_figure_rpc_e2e.py +4 -4
- tests/end-2-end/test_rpc_register_e2e.py +1 -1
- tests/unit_tests/test_bec_dock.py +1 -1
- tests/unit_tests/test_bec_figure.py +6 -4
- tests/unit_tests/test_bec_motor_map.py +2 -3
- tests/unit_tests/test_motor_control.py +6 -5
- tests/unit_tests/test_spiral_progress_bar.py +338 -0
- tests/unit_tests/test_waveform1d.py +13 -1
- bec_widgets/validation/__init__.py +0 -2
- bec_widgets/validation/monitor_config_validator.py +0 -258
- bec_widgets/widgets/monitor/__init__.py +0 -1
- bec_widgets/widgets/monitor/config_dialog.py +0 -574
- bec_widgets/widgets/monitor/config_dialog.ui +0 -210
- bec_widgets/widgets/monitor/example_configs/config_device.yaml +0 -60
- bec_widgets/widgets/monitor/example_configs/config_scans.yaml +0 -92
- bec_widgets/widgets/monitor/monitor.py +0 -845
- bec_widgets/widgets/monitor/tab_template.ui +0 -180
- bec_widgets/widgets/motor_map/__init__.py +0 -1
- bec_widgets/widgets/motor_map/motor_map.py +0 -594
- bec_widgets/widgets/plots/__init__.py +0 -4
- tests/unit_tests/test_bec_monitor.py +0 -220
- tests/unit_tests/test_config_dialog.py +0 -178
- tests/unit_tests/test_motor_map.py +0 -171
- tests/unit_tests/test_validator_errors.py +0 -110
- /bec_widgets/{cli → assets}/bec_widgets_icon.png +0 -0
- /bec_widgets/{examples/jupyter_console → assets}/terminal_icon.png +0 -0
- /bec_widgets/widgets/{plots → figure/plots}/plot_base.py +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_table.ui → motor_table/motor_table.ui} +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_absolute.ui → movement_absolute/movement_absolute.ui} +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_relative.ui → movement_relative/movement_relative.ui} +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_selection.ui → selection/selection.ui} +0 -0
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,227 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from qtpy import uic
|
4
|
+
from qtpy.QtCore import Qt
|
5
|
+
from qtpy.QtCore import Signal as pyqtSignal
|
6
|
+
from qtpy.QtCore import Slot as pyqtSlot
|
7
|
+
from qtpy.QtGui import QKeySequence
|
8
|
+
from qtpy.QtWidgets import QDoubleSpinBox, QShortcut, QWidget
|
9
|
+
|
10
|
+
from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget
|
11
|
+
|
12
|
+
|
13
|
+
class MotorControlRelative(MotorControlWidget):
|
14
|
+
"""
|
15
|
+
Widget for controlling the motors to relative coordinates.
|
16
|
+
|
17
|
+
Signals:
|
18
|
+
precision_signal (pyqtSignal): Signal to emit the precision of the coordinates.
|
19
|
+
Slots:
|
20
|
+
change_motors (pyqtSlot(str,str)): Slot to change the active motors.
|
21
|
+
enable_motor_controls (pyqtSlot): Slot to enable/disable the motor controls.
|
22
|
+
"""
|
23
|
+
|
24
|
+
precision_signal = pyqtSignal(int)
|
25
|
+
|
26
|
+
def _load_ui(self):
|
27
|
+
"""Load the UI from the .ui file."""
|
28
|
+
# Loading UI
|
29
|
+
current_path = os.path.dirname(__file__)
|
30
|
+
uic.loadUi(os.path.join(current_path, "movement_relative.ui"), self)
|
31
|
+
|
32
|
+
def _init_ui(self):
|
33
|
+
"""Initialize the UI."""
|
34
|
+
self._init_ui_motor_control()
|
35
|
+
self._init_keyboard_shortcuts()
|
36
|
+
|
37
|
+
@pyqtSlot(dict)
|
38
|
+
def on_config_update(self, config: dict) -> None:
|
39
|
+
"""
|
40
|
+
Update config dict
|
41
|
+
Args:
|
42
|
+
config(dict): New config dict
|
43
|
+
"""
|
44
|
+
self.config = config
|
45
|
+
|
46
|
+
# Get motor names
|
47
|
+
self.motor_x, self.motor_y = (
|
48
|
+
self.config["motor_control"]["motor_x"],
|
49
|
+
self.config["motor_control"]["motor_y"],
|
50
|
+
)
|
51
|
+
|
52
|
+
# Update step precision
|
53
|
+
self.precision = self.config["motor_control"]["precision"]
|
54
|
+
self.spinBox_precision.setValue(self.precision)
|
55
|
+
|
56
|
+
# Update step sizes
|
57
|
+
self.spinBox_step_x.setValue(self.config["motor_control"]["step_size_x"])
|
58
|
+
self.spinBox_step_y.setValue(self.config["motor_control"]["step_size_y"])
|
59
|
+
|
60
|
+
# Checkboxes for keyboard shortcuts and x/y step size link
|
61
|
+
self.checkBox_same_xy.setChecked(self.config["motor_control"]["step_x_y_same"])
|
62
|
+
self.checkBox_enableArrows.setChecked(self.config["motor_control"]["move_with_arrows"])
|
63
|
+
|
64
|
+
self._init_ui()
|
65
|
+
|
66
|
+
def _init_ui_motor_control(self) -> None:
|
67
|
+
"""Initialize the motor control elements"""
|
68
|
+
|
69
|
+
# Connect checkbox and spinBoxes
|
70
|
+
self.checkBox_same_xy.stateChanged.connect(self._sync_step_sizes)
|
71
|
+
self.spinBox_step_x.valueChanged.connect(self._update_step_size_x)
|
72
|
+
self.spinBox_step_y.valueChanged.connect(self._update_step_size_y)
|
73
|
+
|
74
|
+
self.toolButton_right.clicked.connect(
|
75
|
+
lambda: self.move_motor_relative(self.motor_x, "x", 1)
|
76
|
+
)
|
77
|
+
self.toolButton_left.clicked.connect(
|
78
|
+
lambda: self.move_motor_relative(self.motor_x, "x", -1)
|
79
|
+
)
|
80
|
+
self.toolButton_up.clicked.connect(lambda: self.move_motor_relative(self.motor_y, "y", 1))
|
81
|
+
self.toolButton_down.clicked.connect(
|
82
|
+
lambda: self.move_motor_relative(self.motor_y, "y", -1)
|
83
|
+
)
|
84
|
+
|
85
|
+
# Switch between key shortcuts active
|
86
|
+
self.checkBox_enableArrows.stateChanged.connect(self._update_arrow_key_shortcuts)
|
87
|
+
self._update_arrow_key_shortcuts()
|
88
|
+
|
89
|
+
# Enable/Disable GUI
|
90
|
+
self.motor_thread.lock_gui.connect(self.enable_motor_controls)
|
91
|
+
|
92
|
+
# Precision update
|
93
|
+
self.spinBox_precision.valueChanged.connect(lambda x: self._update_precision(x))
|
94
|
+
|
95
|
+
# Error messages
|
96
|
+
self.motor_thread.motor_error.connect(
|
97
|
+
lambda error: MotorControlErrors.display_error_message(error)
|
98
|
+
)
|
99
|
+
|
100
|
+
# Stop Button
|
101
|
+
self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
|
102
|
+
|
103
|
+
def _init_keyboard_shortcuts(self) -> None:
|
104
|
+
"""Initialize the keyboard shortcuts"""
|
105
|
+
|
106
|
+
# Increase/decrease step size for X motor
|
107
|
+
increase_x_shortcut = QShortcut(QKeySequence("Ctrl+A"), self)
|
108
|
+
decrease_x_shortcut = QShortcut(QKeySequence("Ctrl+Z"), self)
|
109
|
+
increase_x_shortcut.activated.connect(
|
110
|
+
lambda: self._change_step_size(self.spinBox_step_x, 2)
|
111
|
+
)
|
112
|
+
decrease_x_shortcut.activated.connect(
|
113
|
+
lambda: self._change_step_size(self.spinBox_step_x, 0.5)
|
114
|
+
)
|
115
|
+
self.spinBox_step_x.setToolTip("Increase step size: Ctrl+A\nDecrease step size: Ctrl+Z")
|
116
|
+
|
117
|
+
# Increase/decrease step size for Y motor
|
118
|
+
increase_y_shortcut = QShortcut(QKeySequence("Alt+A"), self)
|
119
|
+
decrease_y_shortcut = QShortcut(QKeySequence("Alt+Z"), self)
|
120
|
+
increase_y_shortcut.activated.connect(
|
121
|
+
lambda: self._change_step_size(self.spinBox_step_y, 2)
|
122
|
+
)
|
123
|
+
decrease_y_shortcut.activated.connect(
|
124
|
+
lambda: self._change_step_size(self.spinBox_step_y, 0.5)
|
125
|
+
)
|
126
|
+
self.spinBox_step_y.setToolTip("Increase step size: Alt+A\nDecrease step size: Alt+Z")
|
127
|
+
|
128
|
+
# Stop Button
|
129
|
+
self.pushButton_stop.setShortcut("Ctrl+X")
|
130
|
+
self.pushButton_stop.setToolTip("Ctrl+X")
|
131
|
+
|
132
|
+
def _update_arrow_key_shortcuts(self) -> None:
|
133
|
+
"""Update the arrow key shortcuts based on the checkbox state."""
|
134
|
+
if self.checkBox_enableArrows.isChecked():
|
135
|
+
# Set the arrow key shortcuts for motor movement
|
136
|
+
self.toolButton_right.setShortcut(Qt.Key_Right)
|
137
|
+
self.toolButton_left.setShortcut(Qt.Key_Left)
|
138
|
+
self.toolButton_up.setShortcut(Qt.Key_Up)
|
139
|
+
self.toolButton_down.setShortcut(Qt.Key_Down)
|
140
|
+
else:
|
141
|
+
# Clear the shortcuts
|
142
|
+
self.toolButton_right.setShortcut("")
|
143
|
+
self.toolButton_left.setShortcut("")
|
144
|
+
self.toolButton_up.setShortcut("")
|
145
|
+
self.toolButton_down.setShortcut("")
|
146
|
+
|
147
|
+
def _update_precision(self, precision: int) -> None:
|
148
|
+
"""
|
149
|
+
Update the precision of the coordinates.
|
150
|
+
Args:
|
151
|
+
precision(int): Precision of the coordinates.
|
152
|
+
"""
|
153
|
+
self.spinBox_step_x.setDecimals(precision)
|
154
|
+
self.spinBox_step_y.setDecimals(precision)
|
155
|
+
self.precision_signal.emit(precision)
|
156
|
+
|
157
|
+
def _change_step_size(self, spinBox: QDoubleSpinBox, factor: float) -> None:
|
158
|
+
"""
|
159
|
+
Change the step size of the spinbox.
|
160
|
+
Args:
|
161
|
+
spinBox(QDoubleSpinBox): Spinbox to change the step size.
|
162
|
+
factor(float): Factor to change the step size.
|
163
|
+
"""
|
164
|
+
old_step = spinBox.value()
|
165
|
+
new_step = old_step * factor
|
166
|
+
spinBox.setValue(new_step)
|
167
|
+
|
168
|
+
def _sync_step_sizes(self):
|
169
|
+
"""Sync step sizes based on checkbox state."""
|
170
|
+
if self.checkBox_same_xy.isChecked():
|
171
|
+
value = self.spinBox_step_x.value()
|
172
|
+
self.spinBox_step_y.setValue(value)
|
173
|
+
|
174
|
+
def _update_step_size_x(self):
|
175
|
+
"""Update step size for x if checkbox is checked."""
|
176
|
+
if self.checkBox_same_xy.isChecked():
|
177
|
+
value = self.spinBox_step_x.value()
|
178
|
+
self.spinBox_step_y.setValue(value)
|
179
|
+
|
180
|
+
def _update_step_size_y(self):
|
181
|
+
"""Update step size for y if checkbox is checked."""
|
182
|
+
if self.checkBox_same_xy.isChecked():
|
183
|
+
value = self.spinBox_step_y.value()
|
184
|
+
self.spinBox_step_x.setValue(value)
|
185
|
+
|
186
|
+
@pyqtSlot(str, str)
|
187
|
+
def change_motors(self, motor_x: str, motor_y: str):
|
188
|
+
"""
|
189
|
+
Change the active motors and update config.
|
190
|
+
Can be connected to the selected_motors_signal from MotorControlSelection.
|
191
|
+
Args:
|
192
|
+
motor_x(str): New motor X to be controlled.
|
193
|
+
motor_y(str): New motor Y to be controlled.
|
194
|
+
"""
|
195
|
+
self.motor_x = motor_x
|
196
|
+
self.motor_y = motor_y
|
197
|
+
self.config["motor_control"]["motor_x"] = motor_x
|
198
|
+
self.config["motor_control"]["motor_y"] = motor_y
|
199
|
+
|
200
|
+
@pyqtSlot(bool)
|
201
|
+
def enable_motor_controls(self, disable: bool) -> None:
|
202
|
+
"""
|
203
|
+
Enable or disable the motor controls.
|
204
|
+
Args:
|
205
|
+
disable(bool): True to disable, False to enable.
|
206
|
+
"""
|
207
|
+
|
208
|
+
# Disable or enable all controls within the motorControl_absolute group box
|
209
|
+
for widget in self.motorControl.findChildren(QWidget):
|
210
|
+
widget.setEnabled(disable)
|
211
|
+
|
212
|
+
# Enable the pushButton_stop if the motor is moving
|
213
|
+
self.pushButton_stop.setEnabled(True)
|
214
|
+
|
215
|
+
def move_motor_relative(self, motor, axis: str, direction: int) -> None:
|
216
|
+
"""
|
217
|
+
Move the motor relative to the current position.
|
218
|
+
Args:
|
219
|
+
motor: Motor to move.
|
220
|
+
axis(str): Axis to move.
|
221
|
+
direction(int): Direction to move. 1 for positive, -1 for negative.
|
222
|
+
"""
|
223
|
+
if axis == "x":
|
224
|
+
step = direction * self.spinBox_step_x.value()
|
225
|
+
elif axis == "y":
|
226
|
+
step = direction * self.spinBox_step_y.value()
|
227
|
+
self.motor_thread.move_relative(motor, step)
|
File without changes
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# pylint: disable = no-name-in-module,missing-module-docstring
|
2
|
+
import os
|
3
|
+
|
4
|
+
from qtpy import uic
|
5
|
+
from qtpy.QtCore import Signal as pyqtSignal
|
6
|
+
from qtpy.QtCore import Slot as pyqtSlot
|
7
|
+
from qtpy.QtWidgets import QComboBox
|
8
|
+
|
9
|
+
from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget
|
10
|
+
|
11
|
+
|
12
|
+
class MotorControlSelection(MotorControlWidget):
|
13
|
+
"""
|
14
|
+
Widget for selecting the motors to control.
|
15
|
+
|
16
|
+
Signals:
|
17
|
+
selected_motors_signal (pyqtSignal(str,str)): Signal to emit the selected motors.
|
18
|
+
Slots:
|
19
|
+
get_available_motors (pyqtSlot): Slot to populate the available motors in the combo boxes and set the index based on the configuration.
|
20
|
+
enable_motor_controls (pyqtSlot(bool)): Slot to enable/disable the motor controls GUI.
|
21
|
+
on_config_update (pyqtSlot(dict)): Slot to update the config dict.
|
22
|
+
"""
|
23
|
+
|
24
|
+
selected_motors_signal = pyqtSignal(str, str)
|
25
|
+
|
26
|
+
def _load_ui(self):
|
27
|
+
"""Load the UI from the .ui file."""
|
28
|
+
current_path = os.path.dirname(__file__)
|
29
|
+
uic.loadUi(os.path.join(current_path, "selection.ui"), self)
|
30
|
+
|
31
|
+
def _init_ui(self):
|
32
|
+
"""Initialize the UI."""
|
33
|
+
# Lock GUI while motors are moving
|
34
|
+
self.motor_thread.lock_gui.connect(self.enable_motor_controls)
|
35
|
+
|
36
|
+
self.pushButton_connecMotors.clicked.connect(self.select_motor)
|
37
|
+
self.get_available_motors()
|
38
|
+
|
39
|
+
# Connect change signals to change color
|
40
|
+
self.comboBox_motor_x.currentIndexChanged.connect(
|
41
|
+
lambda: self.set_combobox_style(self.comboBox_motor_x, "#ffa700")
|
42
|
+
)
|
43
|
+
self.comboBox_motor_y.currentIndexChanged.connect(
|
44
|
+
lambda: self.set_combobox_style(self.comboBox_motor_y, "#ffa700")
|
45
|
+
)
|
46
|
+
|
47
|
+
@pyqtSlot(dict)
|
48
|
+
def on_config_update(self, config: dict) -> None:
|
49
|
+
"""
|
50
|
+
Update config dict
|
51
|
+
Args:
|
52
|
+
config(dict): New config dict
|
53
|
+
"""
|
54
|
+
self.config = config
|
55
|
+
|
56
|
+
# Get motor names
|
57
|
+
self.motor_x, self.motor_y = (
|
58
|
+
self.config["motor_control"]["motor_x"],
|
59
|
+
self.config["motor_control"]["motor_y"],
|
60
|
+
)
|
61
|
+
|
62
|
+
self._init_ui()
|
63
|
+
|
64
|
+
@pyqtSlot(bool)
|
65
|
+
def enable_motor_controls(self, enable: bool) -> None:
|
66
|
+
"""
|
67
|
+
Enable or disable the motor controls.
|
68
|
+
Args:
|
69
|
+
enable(bool): True to enable, False to disable.
|
70
|
+
"""
|
71
|
+
self.motorSelection.setEnabled(enable)
|
72
|
+
|
73
|
+
@pyqtSlot()
|
74
|
+
def get_available_motors(self) -> None:
|
75
|
+
"""
|
76
|
+
Slot to populate the available motors in the combo boxes and set the index based on the configuration.
|
77
|
+
"""
|
78
|
+
# Get all available motors
|
79
|
+
self.motor_list = self.motor_thread.get_all_motors_names()
|
80
|
+
|
81
|
+
# Populate the combo boxes
|
82
|
+
self.comboBox_motor_x.addItems(self.motor_list)
|
83
|
+
self.comboBox_motor_y.addItems(self.motor_list)
|
84
|
+
|
85
|
+
# Set the index based on the config if provided
|
86
|
+
if self.config:
|
87
|
+
index_x = self.comboBox_motor_x.findText(self.motor_x)
|
88
|
+
index_y = self.comboBox_motor_y.findText(self.motor_y)
|
89
|
+
self.comboBox_motor_x.setCurrentIndex(index_x if index_x != -1 else 0)
|
90
|
+
self.comboBox_motor_y.setCurrentIndex(index_y if index_y != -1 else 0)
|
91
|
+
|
92
|
+
def set_combobox_style(self, combobox, color: str) -> None:
|
93
|
+
"""
|
94
|
+
Set the combobox style to a specific color.
|
95
|
+
Args:
|
96
|
+
combobox(QComboBox): Combobox to change the color.
|
97
|
+
color(str): Color to set the combobox to.
|
98
|
+
"""
|
99
|
+
combobox.setStyleSheet(f"QComboBox {{ background-color: {color}; }}")
|
100
|
+
|
101
|
+
def select_motor(self):
|
102
|
+
"""Emit the selected motors"""
|
103
|
+
motor_x = self.comboBox_motor_x.currentText()
|
104
|
+
motor_y = self.comboBox_motor_y.currentText()
|
105
|
+
|
106
|
+
# Reset the combobox color to normal after selection
|
107
|
+
self.set_combobox_style(self.comboBox_motor_x, "")
|
108
|
+
self.set_combobox_style(self.comboBox_motor_y, "")
|
109
|
+
|
110
|
+
self.selected_motors_signal.emit(motor_x, motor_y)
|
@@ -0,0 +1 @@
|
|
1
|
+
from .spiral_progress_bar import SpiralProgressBar
|
@@ -0,0 +1,184 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Literal, Optional
|
4
|
+
|
5
|
+
from bec_lib.endpoints import EndpointInfo
|
6
|
+
from pydantic import BaseModel, Field, field_validator
|
7
|
+
from pydantic_core import PydanticCustomError
|
8
|
+
from qtpy import QtGui
|
9
|
+
|
10
|
+
from bec_widgets.utils import BECConnector, ConnectionConfig
|
11
|
+
|
12
|
+
|
13
|
+
class RingConnections(BaseModel):
|
14
|
+
slot: Literal["on_scan_progress", "on_device_readback"] = None
|
15
|
+
endpoint: EndpointInfo | str = None
|
16
|
+
|
17
|
+
@field_validator("endpoint")
|
18
|
+
def validate_endpoint(cls, v, values):
|
19
|
+
slot = values.data["slot"]
|
20
|
+
endpoint = v.endpoint if isinstance(v, EndpointInfo) else v
|
21
|
+
if slot == "on_scan_progress":
|
22
|
+
if endpoint != "scans/scan_progress":
|
23
|
+
raise PydanticCustomError(
|
24
|
+
"unsupported endpoint",
|
25
|
+
"For slot 'on_scan_progress', endpoint must be MessageEndpoint.scan_progress or 'scans/scan_progress'.",
|
26
|
+
{"wrong_value": v},
|
27
|
+
)
|
28
|
+
elif slot == "on_device_readback":
|
29
|
+
if not endpoint.startswith("internal/devices/readback/"):
|
30
|
+
raise PydanticCustomError(
|
31
|
+
"unsupported endpoint",
|
32
|
+
"For slot 'on_device_readback', endpoint must be MessageEndpoint.device_readback(device) or 'internal/devices/readback/{device}'.",
|
33
|
+
{"wrong_value": v},
|
34
|
+
)
|
35
|
+
return v
|
36
|
+
|
37
|
+
|
38
|
+
class RingConfig(ConnectionConfig):
|
39
|
+
direction: int | None = Field(
|
40
|
+
-1, description="Direction of the progress bars. -1 for clockwise, 1 for counter-clockwise."
|
41
|
+
)
|
42
|
+
color: str | tuple | None = Field(
|
43
|
+
(0, 159, 227, 255),
|
44
|
+
description="Color for the progress bars. Can be tuple (R, G, B, A) or string HEX Code.",
|
45
|
+
)
|
46
|
+
background_color: str | tuple | None = Field(
|
47
|
+
(200, 200, 200, 50),
|
48
|
+
description="Background color for the progress bars. Can be tuple (R, G, B, A) or string HEX Code.",
|
49
|
+
)
|
50
|
+
index: int | None = Field(0, description="Index of the progress bar. 0 is outer ring.")
|
51
|
+
line_width: int | None = Field(5, description="Line widths for the progress bars.")
|
52
|
+
start_position: int | None = Field(
|
53
|
+
90,
|
54
|
+
description="Start position for the progress bars in degrees. Default is 90 degrees - corespons to "
|
55
|
+
"the top of the ring.",
|
56
|
+
)
|
57
|
+
min_value: int | None = Field(0, description="Minimum value for the progress bars.")
|
58
|
+
max_value: int | None = Field(100, description="Maximum value for the progress bars.")
|
59
|
+
precision: int | None = Field(3, description="Precision for the progress bars.")
|
60
|
+
update_behaviour: Literal["manual", "auto"] | None = Field(
|
61
|
+
"auto", description="Update behaviour for the progress bars."
|
62
|
+
)
|
63
|
+
connections: RingConnections | None = Field(
|
64
|
+
default_factory=RingConnections, description="Connections for the progress bars."
|
65
|
+
)
|
66
|
+
|
67
|
+
|
68
|
+
class Ring(BECConnector):
|
69
|
+
USER_ACCESS = [
|
70
|
+
"get_all_rpc",
|
71
|
+
"rpc_id",
|
72
|
+
"config_dict",
|
73
|
+
"set_value",
|
74
|
+
"set_color",
|
75
|
+
"set_background",
|
76
|
+
"set_line_width",
|
77
|
+
"set_min_max_values",
|
78
|
+
"set_start_angle",
|
79
|
+
"set_connections",
|
80
|
+
"reset_connection",
|
81
|
+
]
|
82
|
+
|
83
|
+
def __init__(
|
84
|
+
self,
|
85
|
+
parent=None,
|
86
|
+
parent_progress_widget=None,
|
87
|
+
config: RingConfig | dict | None = None,
|
88
|
+
client=None,
|
89
|
+
gui_id: Optional[str] = None,
|
90
|
+
):
|
91
|
+
if config is None:
|
92
|
+
config = RingConfig(widget_class=self.__class__.__name__)
|
93
|
+
self.config = config
|
94
|
+
else:
|
95
|
+
if isinstance(config, dict):
|
96
|
+
config = RingConfig(**config)
|
97
|
+
self.config = config
|
98
|
+
super().__init__(client=client, config=config, gui_id=gui_id)
|
99
|
+
|
100
|
+
self.parent_progress_widget = parent_progress_widget
|
101
|
+
self.color = None
|
102
|
+
self.background_color = None
|
103
|
+
self.start_position = None
|
104
|
+
self.config = config
|
105
|
+
self.value = 0
|
106
|
+
self.RID = None
|
107
|
+
self._init_config_params()
|
108
|
+
|
109
|
+
def _init_config_params(self):
|
110
|
+
self.color = self.convert_color(self.config.color)
|
111
|
+
self.background_color = self.convert_color(self.config.background_color)
|
112
|
+
self.set_start_angle(self.config.start_position)
|
113
|
+
if self.config.connections:
|
114
|
+
self.set_connections(self.config.connections.slot, self.config.connections.endpoint)
|
115
|
+
|
116
|
+
def set_value(self, value: int | float):
|
117
|
+
self.value = round(
|
118
|
+
max(self.config.min_value, min(self.config.max_value, value)), self.config.precision
|
119
|
+
)
|
120
|
+
|
121
|
+
def set_color(self, color: str | tuple):
|
122
|
+
self.config.color = color
|
123
|
+
self.color = self.convert_color(color)
|
124
|
+
|
125
|
+
def set_background(self, color: str | tuple):
|
126
|
+
self.config.background_color = color
|
127
|
+
self.color = self.convert_color(color)
|
128
|
+
|
129
|
+
def set_line_width(self, width: int):
|
130
|
+
self.config.line_width = width
|
131
|
+
|
132
|
+
def set_min_max_values(self, min_value: int, max_value: int):
|
133
|
+
self.config.min_value = min_value
|
134
|
+
self.config.max_value = max_value
|
135
|
+
|
136
|
+
def set_start_angle(self, start_angle: int):
|
137
|
+
self.config.start_position = start_angle
|
138
|
+
self.start_position = start_angle * 16
|
139
|
+
|
140
|
+
@staticmethod
|
141
|
+
def convert_color(color):
|
142
|
+
converted_color = None
|
143
|
+
if isinstance(color, str):
|
144
|
+
converted_color = QtGui.QColor(color)
|
145
|
+
elif isinstance(color, tuple):
|
146
|
+
converted_color = QtGui.QColor(*color)
|
147
|
+
return converted_color
|
148
|
+
|
149
|
+
def set_connections(self, slot: str, endpoint: str | EndpointInfo):
|
150
|
+
if self.config.connections.endpoint == endpoint and self.config.connections.slot == slot:
|
151
|
+
return
|
152
|
+
else:
|
153
|
+
self.bec_dispatcher.disconnect_slot(
|
154
|
+
self.config.connections.slot, self.config.connections.endpoint
|
155
|
+
)
|
156
|
+
self.config.connections = RingConnections(slot=slot, endpoint=endpoint)
|
157
|
+
self.bec_dispatcher.connect_slot(getattr(self, slot), endpoint)
|
158
|
+
|
159
|
+
def reset_connection(self):
|
160
|
+
self.bec_dispatcher.disconnect_slot(
|
161
|
+
self.config.connections.slot, self.config.connections.endpoint
|
162
|
+
)
|
163
|
+
self.config.connections = RingConnections()
|
164
|
+
|
165
|
+
def on_scan_progress(self, msg, meta):
|
166
|
+
current_RID = meta.get("RID", None)
|
167
|
+
if current_RID != self.RID:
|
168
|
+
self.set_min_max_values(0, msg.get("max_value", 100))
|
169
|
+
self.set_value(msg.get("value", 0))
|
170
|
+
self.parent_progress_widget.update()
|
171
|
+
|
172
|
+
def on_device_readback(self, msg, meta):
|
173
|
+
if isinstance(self.config.connections.endpoint, EndpointInfo):
|
174
|
+
endpoint = self.config.connections.endpoint.endpoint
|
175
|
+
else:
|
176
|
+
endpoint = self.config.connections.endpoint
|
177
|
+
device = endpoint.split("/")[-1]
|
178
|
+
value = msg.get("signals").get(device).get("value")
|
179
|
+
self.set_value(value)
|
180
|
+
self.parent_progress_widget.update()
|
181
|
+
|
182
|
+
def cleanup(self):
|
183
|
+
self.reset_connection()
|
184
|
+
super().cleanup()
|