bec-widgets 0.54.0__py3-none-any.whl → 0.56.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 (51) hide show
  1. .gitlab-ci.yml +113 -8
  2. CHANGELOG.md +32 -21
  3. PKG-INFO +3 -1
  4. bec_widgets/cli/client.py +252 -0
  5. bec_widgets/cli/generate_cli.py +4 -1
  6. bec_widgets/cli/rpc_wigdet_handler.py +2 -1
  7. bec_widgets/examples/jupyter_console/jupyter_console_window.py +29 -37
  8. bec_widgets/examples/motor_movement/motor_control_compilations.py +1 -7
  9. bec_widgets/utils/__init__.py +1 -0
  10. bec_widgets/utils/crosshair.py +13 -9
  11. bec_widgets/utils/ui_loader.py +58 -0
  12. bec_widgets/widgets/__init__.py +1 -0
  13. bec_widgets/widgets/motor_control/motor_table/motor_table.py +44 -43
  14. bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +25 -23
  15. bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +51 -48
  16. bec_widgets/widgets/spiral_progress_bar/__init__.py +1 -0
  17. bec_widgets/widgets/spiral_progress_bar/ring.py +184 -0
  18. bec_widgets/widgets/spiral_progress_bar/spiral_progress_bar.py +594 -0
  19. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/METADATA +3 -1
  20. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/RECORD +29 -46
  21. docs/user/apps.md +1 -26
  22. pyproject.toml +2 -1
  23. tests/end-2-end/test_bec_dock_rpc_e2e.py +81 -0
  24. tests/unit_tests/test_client_utils.py +2 -2
  25. tests/unit_tests/test_crosshair.py +5 -5
  26. tests/unit_tests/test_motor_control.py +49 -45
  27. tests/unit_tests/test_spiral_progress_bar.py +338 -0
  28. bec_widgets/examples/eiger_plot/__init__.py +0 -0
  29. bec_widgets/examples/eiger_plot/eiger_plot.py +0 -307
  30. bec_widgets/examples/eiger_plot/eiger_plot.ui +0 -207
  31. bec_widgets/examples/mca_readout/__init__.py +0 -0
  32. bec_widgets/examples/mca_readout/mca_plot.py +0 -159
  33. bec_widgets/examples/mca_readout/mca_sim.py +0 -28
  34. bec_widgets/examples/modular_app/___init__.py +0 -0
  35. bec_widgets/examples/modular_app/modular.ui +0 -92
  36. bec_widgets/examples/modular_app/modular_app.py +0 -197
  37. bec_widgets/examples/motor_movement/config_example.yaml +0 -17
  38. bec_widgets/examples/motor_movement/csax_bec_config.yaml +0 -10
  39. bec_widgets/examples/motor_movement/csaxs_config.yaml +0 -17
  40. bec_widgets/examples/motor_movement/motor_example.py +0 -1344
  41. bec_widgets/examples/stream_plot/__init__.py +0 -0
  42. bec_widgets/examples/stream_plot/line_plot.ui +0 -155
  43. bec_widgets/examples/stream_plot/stream_plot.py +0 -337
  44. docs/user/apps/modular_app.md +0 -6
  45. docs/user/apps/motor_app.md +0 -34
  46. docs/user/apps/motor_app_10fps.gif +0 -0
  47. docs/user/apps/plot_app.md +0 -6
  48. tests/unit_tests/test_eiger_plot.py +0 -115
  49. tests/unit_tests/test_stream_plot.py +0 -158
  50. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/WHEEL +0 -0
  51. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,8 +3,10 @@ import os
3
3
  from qtpy import uic
4
4
  from qtpy.QtCore import Signal as pyqtSignal
5
5
  from qtpy.QtCore import Slot as pyqtSlot
6
+ from qtpy.QtWidgets import QWidget
6
7
 
7
- from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget
8
+ from bec_widgets.utils import UILoader
9
+ from bec_widgets.widgets.motor_control.motor_control import MotorControlErrors, MotorControlWidget
8
10
 
9
11
 
10
12
  class MotorControlAbsolute(MotorControlWidget):
@@ -23,26 +25,26 @@ class MotorControlAbsolute(MotorControlWidget):
23
25
  def _load_ui(self):
24
26
  """Load the UI from the .ui file."""
25
27
  current_path = os.path.dirname(__file__)
26
- uic.loadUi(os.path.join(current_path, "movement_absolute.ui"), self)
28
+ self.ui = UILoader().load_ui(os.path.join(current_path, "movement_absolute.ui"), self)
27
29
 
28
30
  def _init_ui(self):
29
31
  """Initialize the UI."""
30
32
 
31
33
  # Check if there are any motors connected
32
34
  if self.motor_x is None or self.motor_y is None:
33
- self.motorControl_absolute.setEnabled(False)
35
+ self.ui.motorControl_absolute.setEnabled(False)
34
36
  return
35
37
 
36
38
  # Move to absolute coordinates
37
- self.pushButton_go_absolute.clicked.connect(
39
+ self.ui.pushButton_go_absolute.clicked.connect(
38
40
  lambda: self.move_motor_absolute(
39
- self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()
41
+ self.ui.spinBox_absolute_x.value(), self.ui.spinBox_absolute_y.value()
40
42
  )
41
43
  )
42
44
 
43
- self.pushButton_set.clicked.connect(self.save_absolute_coordinates)
44
- self.pushButton_save.clicked.connect(self.save_current_coordinates)
45
- self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
45
+ self.ui.pushButton_set.clicked.connect(self.save_absolute_coordinates)
46
+ self.ui.pushButton_save.clicked.connect(self.save_current_coordinates)
47
+ self.ui.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
46
48
 
47
49
  # Enable/Disable GUI
48
50
  self.motor_thread.lock_gui.connect(self.enable_motor_controls)
@@ -80,11 +82,11 @@ class MotorControlAbsolute(MotorControlWidget):
80
82
  """
81
83
 
82
84
  # Disable or enable all controls within the motorControl_absolute group box
83
- for widget in self.motorControl_absolute.findChildren(QWidget):
85
+ for widget in self.ui.motorControl_absolute.findChildren(QWidget):
84
86
  widget.setEnabled(enable)
85
87
 
86
88
  # Enable the pushButton_stop if the motor is moving
87
- self.pushButton_stop.setEnabled(True)
89
+ self.ui.pushButton_stop.setEnabled(True)
88
90
 
89
91
  @pyqtSlot(str, str)
90
92
  def change_motors(self, motor_x: str, motor_y: str):
@@ -109,8 +111,8 @@ class MotorControlAbsolute(MotorControlWidget):
109
111
  """
110
112
  self.precision = precision
111
113
  self.config["motor_control"]["precision"] = precision
112
- self.spinBox_absolute_x.setDecimals(precision)
113
- self.spinBox_absolute_y.setDecimals(precision)
114
+ self.ui.spinBox_absolute_x.setDecimals(precision)
115
+ self.ui.spinBox_absolute_y.setDecimals(precision)
114
116
 
115
117
  def move_motor_absolute(self, x: float, y: float) -> None:
116
118
  """
@@ -122,32 +124,32 @@ class MotorControlAbsolute(MotorControlWidget):
122
124
  # self._enable_motor_controls(False)
123
125
  target_coordinates = (x, y)
124
126
  self.motor_thread.move_absolute(self.motor_x, self.motor_y, target_coordinates)
125
- if self.checkBox_save_with_go.isChecked():
127
+ if self.ui.checkBox_save_with_go.isChecked():
126
128
  self.save_absolute_coordinates()
127
129
 
128
130
  def _init_keyboard_shortcuts(self):
129
131
  """Initialize the keyboard shortcuts."""
130
132
  # Go absolute button
131
- self.pushButton_go_absolute.setShortcut("Ctrl+G")
132
- self.pushButton_go_absolute.setToolTip("Ctrl+G")
133
+ self.ui.pushButton_go_absolute.setShortcut("Ctrl+G")
134
+ self.ui.pushButton_go_absolute.setToolTip("Ctrl+G")
133
135
 
134
136
  # Set absolute coordinates
135
- self.pushButton_set.setShortcut("Ctrl+D")
136
- self.pushButton_set.setToolTip("Ctrl+D")
137
+ self.ui.pushButton_set.setShortcut("Ctrl+D")
138
+ self.ui.pushButton_set.setToolTip("Ctrl+D")
137
139
 
138
140
  # Save Current coordinates
139
- self.pushButton_save.setShortcut("Ctrl+S")
140
- self.pushButton_save.setToolTip("Ctrl+S")
141
+ self.ui.pushButton_save.setShortcut("Ctrl+S")
142
+ self.ui.pushButton_save.setToolTip("Ctrl+S")
141
143
 
142
144
  # Stop Button
143
- self.pushButton_stop.setShortcut("Ctrl+X")
144
- self.pushButton_stop.setToolTip("Ctrl+X")
145
+ self.ui.pushButton_stop.setShortcut("Ctrl+X")
146
+ self.ui.pushButton_stop.setToolTip("Ctrl+X")
145
147
 
146
148
  def save_absolute_coordinates(self):
147
149
  """Emit the setup coordinates from the spinboxes"""
148
150
 
149
- x, y = round(self.spinBox_absolute_x.value(), self.precision), round(
150
- self.spinBox_absolute_y.value(), self.precision
151
+ x, y = round(self.ui.spinBox_absolute_x.value(), self.precision), round(
152
+ self.ui.spinBox_absolute_y.value(), self.precision
151
153
  )
152
154
  self.coordinates_signal.emit((x, y))
153
155
 
@@ -7,6 +7,7 @@ from qtpy.QtCore import Slot as pyqtSlot
7
7
  from qtpy.QtGui import QKeySequence
8
8
  from qtpy.QtWidgets import QDoubleSpinBox, QShortcut, QWidget
9
9
 
10
+ from bec_widgets.utils import UILoader
10
11
  from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget
11
12
 
12
13
 
@@ -27,7 +28,7 @@ class MotorControlRelative(MotorControlWidget):
27
28
  """Load the UI from the .ui file."""
28
29
  # Loading UI
29
30
  current_path = os.path.dirname(__file__)
30
- uic.loadUi(os.path.join(current_path, "movement_relative.ui"), self)
31
+ self.ui = UILoader().load_ui(os.path.join(current_path, "movement_relative.ui"), self)
31
32
 
32
33
  def _init_ui(self):
33
34
  """Initialize the UI."""
@@ -51,15 +52,15 @@ class MotorControlRelative(MotorControlWidget):
51
52
 
52
53
  # Update step precision
53
54
  self.precision = self.config["motor_control"]["precision"]
54
- self.spinBox_precision.setValue(self.precision)
55
+ self.ui.spinBox_precision.setValue(self.precision)
55
56
 
56
57
  # 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"])
58
+ self.ui.spinBox_step_x.setValue(self.config["motor_control"]["step_size_x"])
59
+ self.ui.spinBox_step_y.setValue(self.config["motor_control"]["step_size_y"])
59
60
 
60
61
  # 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"])
62
+ self.ui.checkBox_same_xy.setChecked(self.config["motor_control"]["step_x_y_same"])
63
+ self.ui.checkBox_enableArrows.setChecked(self.config["motor_control"]["move_with_arrows"])
63
64
 
64
65
  self._init_ui()
65
66
 
@@ -67,30 +68,32 @@ class MotorControlRelative(MotorControlWidget):
67
68
  """Initialize the motor control elements"""
68
69
 
69
70
  # 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)
71
+ self.ui.checkBox_same_xy.stateChanged.connect(self._sync_step_sizes)
72
+ self.ui.spinBox_step_x.valueChanged.connect(self._update_step_size_x)
73
+ self.ui.spinBox_step_y.valueChanged.connect(self._update_step_size_y)
73
74
 
74
- self.toolButton_right.clicked.connect(
75
+ self.ui.toolButton_right.clicked.connect(
75
76
  lambda: self.move_motor_relative(self.motor_x, "x", 1)
76
77
  )
77
- self.toolButton_left.clicked.connect(
78
+ self.ui.toolButton_left.clicked.connect(
78
79
  lambda: self.move_motor_relative(self.motor_x, "x", -1)
79
80
  )
80
- self.toolButton_up.clicked.connect(lambda: self.move_motor_relative(self.motor_y, "y", 1))
81
- self.toolButton_down.clicked.connect(
81
+ self.ui.toolButton_up.clicked.connect(
82
+ lambda: self.move_motor_relative(self.motor_y, "y", 1)
83
+ )
84
+ self.ui.toolButton_down.clicked.connect(
82
85
  lambda: self.move_motor_relative(self.motor_y, "y", -1)
83
86
  )
84
87
 
85
88
  # Switch between key shortcuts active
86
- self.checkBox_enableArrows.stateChanged.connect(self._update_arrow_key_shortcuts)
89
+ self.ui.checkBox_enableArrows.stateChanged.connect(self._update_arrow_key_shortcuts)
87
90
  self._update_arrow_key_shortcuts()
88
91
 
89
92
  # Enable/Disable GUI
90
93
  self.motor_thread.lock_gui.connect(self.enable_motor_controls)
91
94
 
92
95
  # Precision update
93
- self.spinBox_precision.valueChanged.connect(lambda x: self._update_precision(x))
96
+ self.ui.spinBox_precision.valueChanged.connect(lambda x: self._update_precision(x))
94
97
 
95
98
  # Error messages
96
99
  self.motor_thread.motor_error.connect(
@@ -98,7 +101,7 @@ class MotorControlRelative(MotorControlWidget):
98
101
  )
99
102
 
100
103
  # Stop Button
101
- self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
104
+ self.ui.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
102
105
 
103
106
  def _init_keyboard_shortcuts(self) -> None:
104
107
  """Initialize the keyboard shortcuts"""
@@ -107,42 +110,42 @@ class MotorControlRelative(MotorControlWidget):
107
110
  increase_x_shortcut = QShortcut(QKeySequence("Ctrl+A"), self)
108
111
  decrease_x_shortcut = QShortcut(QKeySequence("Ctrl+Z"), self)
109
112
  increase_x_shortcut.activated.connect(
110
- lambda: self._change_step_size(self.spinBox_step_x, 2)
113
+ lambda: self._change_step_size(self.ui.spinBox_step_x, 2)
111
114
  )
112
115
  decrease_x_shortcut.activated.connect(
113
- lambda: self._change_step_size(self.spinBox_step_x, 0.5)
116
+ lambda: self._change_step_size(self.ui.spinBox_step_x, 0.5)
114
117
  )
115
- self.spinBox_step_x.setToolTip("Increase step size: Ctrl+A\nDecrease step size: Ctrl+Z")
118
+ self.ui.spinBox_step_x.setToolTip("Increase step size: Ctrl+A\nDecrease step size: Ctrl+Z")
116
119
 
117
120
  # Increase/decrease step size for Y motor
118
121
  increase_y_shortcut = QShortcut(QKeySequence("Alt+A"), self)
119
122
  decrease_y_shortcut = QShortcut(QKeySequence("Alt+Z"), self)
120
123
  increase_y_shortcut.activated.connect(
121
- lambda: self._change_step_size(self.spinBox_step_y, 2)
124
+ lambda: self._change_step_size(self.ui.spinBox_step_y, 2)
122
125
  )
123
126
  decrease_y_shortcut.activated.connect(
124
- lambda: self._change_step_size(self.spinBox_step_y, 0.5)
127
+ lambda: self._change_step_size(self.ui.spinBox_step_y, 0.5)
125
128
  )
126
- self.spinBox_step_y.setToolTip("Increase step size: Alt+A\nDecrease step size: Alt+Z")
129
+ self.ui.spinBox_step_y.setToolTip("Increase step size: Alt+A\nDecrease step size: Alt+Z")
127
130
 
128
131
  # Stop Button
129
- self.pushButton_stop.setShortcut("Ctrl+X")
130
- self.pushButton_stop.setToolTip("Ctrl+X")
132
+ self.ui.pushButton_stop.setShortcut("Ctrl+X")
133
+ self.ui.pushButton_stop.setToolTip("Ctrl+X")
131
134
 
132
135
  def _update_arrow_key_shortcuts(self) -> None:
133
136
  """Update the arrow key shortcuts based on the checkbox state."""
134
- if self.checkBox_enableArrows.isChecked():
137
+ if self.ui.checkBox_enableArrows.isChecked():
135
138
  # 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)
139
+ self.ui.toolButton_right.setShortcut(Qt.Key_Right)
140
+ self.ui.toolButton_left.setShortcut(Qt.Key_Left)
141
+ self.ui.toolButton_up.setShortcut(Qt.Key_Up)
142
+ self.ui.toolButton_down.setShortcut(Qt.Key_Down)
140
143
  else:
141
144
  # Clear the shortcuts
142
- self.toolButton_right.setShortcut("")
143
- self.toolButton_left.setShortcut("")
144
- self.toolButton_up.setShortcut("")
145
- self.toolButton_down.setShortcut("")
145
+ self.ui.toolButton_right.setShortcut("")
146
+ self.ui.toolButton_left.setShortcut("")
147
+ self.ui.toolButton_up.setShortcut("")
148
+ self.ui.toolButton_down.setShortcut("")
146
149
 
147
150
  def _update_precision(self, precision: int) -> None:
148
151
  """
@@ -150,8 +153,8 @@ class MotorControlRelative(MotorControlWidget):
150
153
  Args:
151
154
  precision(int): Precision of the coordinates.
152
155
  """
153
- self.spinBox_step_x.setDecimals(precision)
154
- self.spinBox_step_y.setDecimals(precision)
156
+ self.ui.spinBox_step_x.setDecimals(precision)
157
+ self.ui.spinBox_step_y.setDecimals(precision)
155
158
  self.precision_signal.emit(precision)
156
159
 
157
160
  def _change_step_size(self, spinBox: QDoubleSpinBox, factor: float) -> None:
@@ -167,21 +170,21 @@ class MotorControlRelative(MotorControlWidget):
167
170
 
168
171
  def _sync_step_sizes(self):
169
172
  """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
+ if self.ui.checkBox_same_xy.isChecked():
174
+ value = self.ui.spinBox_step_x.value()
175
+ self.ui.spinBox_step_y.setValue(value)
173
176
 
174
177
  def _update_step_size_x(self):
175
178
  """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
+ if self.ui.checkBox_same_xy.isChecked():
180
+ value = self.ui.spinBox_step_x.value()
181
+ self.ui.spinBox_step_y.setValue(value)
179
182
 
180
183
  def _update_step_size_y(self):
181
184
  """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
+ if self.ui.checkBox_same_xy.isChecked():
186
+ value = self.ui.spinBox_step_y.value()
187
+ self.ui.spinBox_step_x.setValue(value)
185
188
 
186
189
  @pyqtSlot(str, str)
187
190
  def change_motors(self, motor_x: str, motor_y: str):
@@ -206,11 +209,11 @@ class MotorControlRelative(MotorControlWidget):
206
209
  """
207
210
 
208
211
  # Disable or enable all controls within the motorControl_absolute group box
209
- for widget in self.motorControl.findChildren(QWidget):
212
+ for widget in self.ui.motorControl.findChildren(QWidget):
210
213
  widget.setEnabled(disable)
211
214
 
212
215
  # Enable the pushButton_stop if the motor is moving
213
- self.pushButton_stop.setEnabled(True)
216
+ self.ui.pushButton_stop.setEnabled(True)
214
217
 
215
218
  def move_motor_relative(self, motor, axis: str, direction: int) -> None:
216
219
  """
@@ -221,7 +224,7 @@ class MotorControlRelative(MotorControlWidget):
221
224
  direction(int): Direction to move. 1 for positive, -1 for negative.
222
225
  """
223
226
  if axis == "x":
224
- step = direction * self.spinBox_step_x.value()
227
+ step = direction * self.ui.spinBox_step_x.value()
225
228
  elif axis == "y":
226
- step = direction * self.spinBox_step_y.value()
229
+ step = direction * self.ui.spinBox_step_y.value()
227
230
  self.motor_thread.move_relative(motor, step)
@@ -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()