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.
Files changed (72) hide show
  1. CHANGELOG.md +24 -26
  2. PKG-INFO +1 -1
  3. bec_widgets/cli/client.py +265 -13
  4. bec_widgets/cli/client_utils.py +0 -3
  5. bec_widgets/cli/generate_cli.py +10 -5
  6. bec_widgets/cli/rpc_wigdet_handler.py +2 -1
  7. bec_widgets/cli/server.py +5 -7
  8. bec_widgets/examples/jupyter_console/jupyter_console_window.py +11 -5
  9. bec_widgets/examples/motor_movement/motor_control_compilations.py +17 -16
  10. bec_widgets/widgets/__init__.py +1 -10
  11. bec_widgets/widgets/figure/figure.py +40 -23
  12. bec_widgets/widgets/figure/plots/__init__.py +0 -0
  13. bec_widgets/widgets/figure/plots/image/__init__.py +0 -0
  14. bec_widgets/widgets/{plots → figure/plots/image}/image.py +6 -416
  15. bec_widgets/widgets/figure/plots/image/image_item.py +277 -0
  16. bec_widgets/widgets/figure/plots/image/image_processor.py +152 -0
  17. bec_widgets/widgets/figure/plots/motor_map/__init__.py +0 -0
  18. bec_widgets/widgets/{plots → figure/plots/motor_map}/motor_map.py +2 -2
  19. bec_widgets/widgets/figure/plots/waveform/__init__.py +0 -0
  20. bec_widgets/widgets/{plots → figure/plots/waveform}/waveform.py +9 -222
  21. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +227 -0
  22. bec_widgets/widgets/motor_control/__init__.py +0 -7
  23. bec_widgets/widgets/motor_control/motor_control.py +2 -948
  24. bec_widgets/widgets/motor_control/motor_table/__init__.py +0 -0
  25. bec_widgets/widgets/motor_control/motor_table/motor_table.py +483 -0
  26. bec_widgets/widgets/motor_control/movement_absolute/__init__.py +0 -0
  27. bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +157 -0
  28. bec_widgets/widgets/motor_control/movement_relative/__init__.py +0 -0
  29. bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +227 -0
  30. bec_widgets/widgets/motor_control/selection/__init__.py +0 -0
  31. bec_widgets/widgets/motor_control/selection/selection.py +110 -0
  32. bec_widgets/widgets/spiral_progress_bar/__init__.py +1 -0
  33. bec_widgets/widgets/spiral_progress_bar/ring.py +184 -0
  34. bec_widgets/widgets/spiral_progress_bar/spiral_progress_bar.py +594 -0
  35. {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/METADATA +1 -1
  36. {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/RECORD +56 -53
  37. docs/requirements.txt +1 -0
  38. pyproject.toml +1 -1
  39. tests/end-2-end/test_bec_dock_rpc_e2e.py +82 -1
  40. tests/end-2-end/test_bec_figure_rpc_e2e.py +4 -4
  41. tests/end-2-end/test_rpc_register_e2e.py +1 -1
  42. tests/unit_tests/test_bec_dock.py +1 -1
  43. tests/unit_tests/test_bec_figure.py +6 -4
  44. tests/unit_tests/test_bec_motor_map.py +2 -3
  45. tests/unit_tests/test_motor_control.py +6 -5
  46. tests/unit_tests/test_spiral_progress_bar.py +338 -0
  47. tests/unit_tests/test_waveform1d.py +13 -1
  48. bec_widgets/validation/__init__.py +0 -2
  49. bec_widgets/validation/monitor_config_validator.py +0 -258
  50. bec_widgets/widgets/monitor/__init__.py +0 -1
  51. bec_widgets/widgets/monitor/config_dialog.py +0 -574
  52. bec_widgets/widgets/monitor/config_dialog.ui +0 -210
  53. bec_widgets/widgets/monitor/example_configs/config_device.yaml +0 -60
  54. bec_widgets/widgets/monitor/example_configs/config_scans.yaml +0 -92
  55. bec_widgets/widgets/monitor/monitor.py +0 -845
  56. bec_widgets/widgets/monitor/tab_template.ui +0 -180
  57. bec_widgets/widgets/motor_map/__init__.py +0 -1
  58. bec_widgets/widgets/motor_map/motor_map.py +0 -594
  59. bec_widgets/widgets/plots/__init__.py +0 -4
  60. tests/unit_tests/test_bec_monitor.py +0 -220
  61. tests/unit_tests/test_config_dialog.py +0 -178
  62. tests/unit_tests/test_motor_map.py +0 -171
  63. tests/unit_tests/test_validator_errors.py +0 -110
  64. /bec_widgets/{cli → assets}/bec_widgets_icon.png +0 -0
  65. /bec_widgets/{examples/jupyter_console → assets}/terminal_icon.png +0 -0
  66. /bec_widgets/widgets/{plots → figure/plots}/plot_base.py +0 -0
  67. /bec_widgets/widgets/motor_control/{motor_control_table.ui → motor_table/motor_table.ui} +0 -0
  68. /bec_widgets/widgets/motor_control/{motor_control_absolute.ui → movement_absolute/movement_absolute.ui} +0 -0
  69. /bec_widgets/widgets/motor_control/{motor_control_relative.ui → movement_relative/movement_relative.ui} +0 -0
  70. /bec_widgets/widgets/motor_control/{motor_control_selection.ui → selection/selection.ui} +0 -0
  71. {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/WHEEL +0 -0
  72. {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)
@@ -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()