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,594 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Literal, Optional
|
4
|
+
|
5
|
+
import pyqtgraph as pg
|
6
|
+
from bec_lib.endpoints import MessageEndpoints
|
7
|
+
from pydantic import Field, field_validator
|
8
|
+
from pydantic_core import PydanticCustomError
|
9
|
+
from qtpy import QtCore, QtGui
|
10
|
+
from qtpy.QtCore import QSize, Slot
|
11
|
+
from qtpy.QtWidgets import QSizePolicy, QWidget
|
12
|
+
|
13
|
+
from bec_widgets.utils import BECConnector, Colors, ConnectionConfig, EntryValidator
|
14
|
+
from bec_widgets.widgets.spiral_progress_bar.ring import Ring, RingConfig
|
15
|
+
|
16
|
+
|
17
|
+
class SpiralProgressBarConfig(ConnectionConfig):
|
18
|
+
color_map: str | None = Field("magma", description="Color scheme for the progress bars.")
|
19
|
+
min_number_of_bars: int | None = Field(
|
20
|
+
1, description="Minimum number of progress bars to display."
|
21
|
+
)
|
22
|
+
max_number_of_bars: int | None = Field(
|
23
|
+
10, description="Maximum number of progress bars to display."
|
24
|
+
)
|
25
|
+
num_bars: int | None = Field(1, description="Number of progress bars to display.")
|
26
|
+
gap: int | None = Field(10, description="Gap between progress bars.")
|
27
|
+
auto_updates: bool | None = Field(
|
28
|
+
True, description="Enable or disable updates based on scan queue status."
|
29
|
+
)
|
30
|
+
rings: list[RingConfig] | None = Field([], description="List of ring configurations.")
|
31
|
+
|
32
|
+
@field_validator("num_bars")
|
33
|
+
def validate_num_bars(cls, v, values):
|
34
|
+
min_number_of_bars = values.data.get("min_number_of_bars", None)
|
35
|
+
max_number_of_bars = values.data.get("max_number_of_bars", None)
|
36
|
+
if min_number_of_bars is not None and max_number_of_bars is not None:
|
37
|
+
print(
|
38
|
+
f"Number of bars adjusted to be between defined min:{min_number_of_bars} and max:{max_number_of_bars} number of bars."
|
39
|
+
)
|
40
|
+
v = max(min_number_of_bars, min(v, max_number_of_bars))
|
41
|
+
return v
|
42
|
+
|
43
|
+
@field_validator("rings")
|
44
|
+
def validate_rings(cls, v, values):
|
45
|
+
if v is not None and v is not []:
|
46
|
+
num_bars = values.data.get("num_bars", None)
|
47
|
+
if len(v) != num_bars:
|
48
|
+
raise PydanticCustomError(
|
49
|
+
"different number of configs",
|
50
|
+
f"Length of rings configuration ({len(v)}) does not match the number of bars ({num_bars}).",
|
51
|
+
{"wrong_value": len(v)},
|
52
|
+
)
|
53
|
+
indices = [ring.index for ring in v]
|
54
|
+
if sorted(indices) != list(range(len(indices))):
|
55
|
+
raise PydanticCustomError(
|
56
|
+
"wrong indices",
|
57
|
+
f"Indices of ring configurations must be unique and in order from 0 to num_bars {num_bars}.",
|
58
|
+
{"wrong_value": indices},
|
59
|
+
)
|
60
|
+
return v
|
61
|
+
|
62
|
+
@field_validator("color_map")
|
63
|
+
def validate_color_map(cls, v, values):
|
64
|
+
if v is not None and v != "":
|
65
|
+
if v not in pg.colormap.listMaps():
|
66
|
+
raise PydanticCustomError(
|
67
|
+
"unsupported colormap",
|
68
|
+
f"Colormap '{v}' not found in the current installation of pyqtgraph",
|
69
|
+
{"wrong_value": v},
|
70
|
+
)
|
71
|
+
return v
|
72
|
+
|
73
|
+
|
74
|
+
class SpiralProgressBar(BECConnector, QWidget):
|
75
|
+
USER_ACCESS = [
|
76
|
+
"get_all_rpc",
|
77
|
+
"rpc_id",
|
78
|
+
"config_dict",
|
79
|
+
"rings",
|
80
|
+
"update_config",
|
81
|
+
"add_ring",
|
82
|
+
"remove_ring",
|
83
|
+
"set_precision",
|
84
|
+
"set_min_max_values",
|
85
|
+
"set_number_of_bars",
|
86
|
+
"set_value",
|
87
|
+
"set_colors_from_map",
|
88
|
+
"set_colors_directly",
|
89
|
+
"set_line_widths",
|
90
|
+
"set_gap",
|
91
|
+
"set_diameter",
|
92
|
+
"reset_diameter",
|
93
|
+
"enable_auto_updates",
|
94
|
+
]
|
95
|
+
|
96
|
+
def __init__(
|
97
|
+
self,
|
98
|
+
parent=None,
|
99
|
+
config: SpiralProgressBarConfig | dict | None = None,
|
100
|
+
client=None,
|
101
|
+
gui_id: str | None = None,
|
102
|
+
num_bars: int | None = None,
|
103
|
+
):
|
104
|
+
if config is None:
|
105
|
+
config = SpiralProgressBarConfig(widget_class=self.__class__.__name__)
|
106
|
+
self.config = config
|
107
|
+
else:
|
108
|
+
if isinstance(config, dict):
|
109
|
+
config = SpiralProgressBarConfig(**config, widget_class=self.__class__.__name__)
|
110
|
+
self.config = config
|
111
|
+
super().__init__(client=client, config=config, gui_id=gui_id)
|
112
|
+
QWidget.__init__(self, parent=None)
|
113
|
+
|
114
|
+
self.get_bec_shortcuts()
|
115
|
+
self.entry_validator = EntryValidator(self.dev)
|
116
|
+
|
117
|
+
self.RID = None
|
118
|
+
self.values = None
|
119
|
+
|
120
|
+
# For updating bar behaviour
|
121
|
+
self._auto_updates = True
|
122
|
+
self._rings = []
|
123
|
+
|
124
|
+
if num_bars is not None:
|
125
|
+
self.config.num_bars = max(
|
126
|
+
self.config.min_number_of_bars, min(num_bars, self.config.max_number_of_bars)
|
127
|
+
)
|
128
|
+
self.initialize_bars()
|
129
|
+
|
130
|
+
self.enable_auto_updates(self.config.auto_updates)
|
131
|
+
|
132
|
+
@property
|
133
|
+
def rings(self):
|
134
|
+
return self._rings
|
135
|
+
|
136
|
+
@rings.setter
|
137
|
+
def rings(self, value):
|
138
|
+
self._rings = value
|
139
|
+
|
140
|
+
def update_config(self, config: SpiralProgressBarConfig | dict):
|
141
|
+
"""
|
142
|
+
Update the configuration of the widget.
|
143
|
+
|
144
|
+
Args:
|
145
|
+
config(SpiralProgressBarConfig|dict): Configuration to update.
|
146
|
+
"""
|
147
|
+
if isinstance(config, dict):
|
148
|
+
config = SpiralProgressBarConfig(**config, widget_class=self.__class__.__name__)
|
149
|
+
self.config = config
|
150
|
+
self.clear_all()
|
151
|
+
|
152
|
+
def initialize_bars(self):
|
153
|
+
"""
|
154
|
+
Initialize the progress bars.
|
155
|
+
"""
|
156
|
+
start_positions = [90 * 16] * self.config.num_bars
|
157
|
+
directions = [-1] * self.config.num_bars
|
158
|
+
|
159
|
+
self.config.rings = [
|
160
|
+
RingConfig(
|
161
|
+
widget_class="Ring",
|
162
|
+
index=i,
|
163
|
+
start_positions=start_positions[i],
|
164
|
+
directions=directions[i],
|
165
|
+
)
|
166
|
+
for i in range(self.config.num_bars)
|
167
|
+
]
|
168
|
+
self._rings = [
|
169
|
+
Ring(parent_progress_widget=self, config=config) for config in self.config.rings
|
170
|
+
]
|
171
|
+
|
172
|
+
if self.config.color_map:
|
173
|
+
self.set_colors_from_map(self.config.color_map)
|
174
|
+
|
175
|
+
min_size = self._calculate_minimum_size()
|
176
|
+
self.setMinimumSize(min_size)
|
177
|
+
self.update()
|
178
|
+
|
179
|
+
def add_ring(self, **kwargs) -> Ring:
|
180
|
+
"""
|
181
|
+
Add a new progress bar.
|
182
|
+
|
183
|
+
Args:
|
184
|
+
**kwargs: Keyword arguments for the new progress bar.
|
185
|
+
|
186
|
+
Returns:
|
187
|
+
Ring: Ring object.
|
188
|
+
"""
|
189
|
+
if self.config.num_bars < self.config.max_number_of_bars:
|
190
|
+
ring = Ring(parent_progress_widget=self, **kwargs)
|
191
|
+
ring.config.index = self.config.num_bars
|
192
|
+
self.config.num_bars += 1
|
193
|
+
self._rings.append(ring)
|
194
|
+
self.config.rings.append(ring.config)
|
195
|
+
if self.config.color_map:
|
196
|
+
self.set_colors_from_map(self.config.color_map)
|
197
|
+
self.update()
|
198
|
+
return ring
|
199
|
+
|
200
|
+
def remove_ring(self, index: int):
|
201
|
+
"""
|
202
|
+
Remove a progress bar by index.
|
203
|
+
|
204
|
+
Args:
|
205
|
+
index(int): Index of the progress bar to remove.
|
206
|
+
"""
|
207
|
+
ring = self._find_ring_by_index(index)
|
208
|
+
ring.cleanup()
|
209
|
+
self._rings.remove(ring)
|
210
|
+
self.config.rings.remove(ring.config)
|
211
|
+
self.config.num_bars -= 1
|
212
|
+
self._reindex_rings()
|
213
|
+
if self.config.color_map:
|
214
|
+
self.set_colors_from_map(self.config.color_map)
|
215
|
+
self.update()
|
216
|
+
|
217
|
+
def _reindex_rings(self):
|
218
|
+
"""
|
219
|
+
Reindex the progress bars.
|
220
|
+
"""
|
221
|
+
for i, ring in enumerate(self._rings):
|
222
|
+
ring.config.index = i
|
223
|
+
|
224
|
+
def set_precision(self, precision: int, bar_index: int = None):
|
225
|
+
"""
|
226
|
+
Set the precision for the progress bars. If bar_index is not provide, the precision will be set for all progress bars.
|
227
|
+
|
228
|
+
Args:
|
229
|
+
precision(int): Precision for the progress bars.
|
230
|
+
bar_index(int): Index of the progress bar to set the precision for. If provided, only a single precision can be set.
|
231
|
+
"""
|
232
|
+
if bar_index is not None:
|
233
|
+
bar_index = self._bar_index_check(bar_index)
|
234
|
+
ring = self._find_ring_by_index(bar_index)
|
235
|
+
ring.config.precision = precision
|
236
|
+
else:
|
237
|
+
for ring in self._rings:
|
238
|
+
ring.config.precision = precision
|
239
|
+
self.update()
|
240
|
+
|
241
|
+
def set_min_max_values(
|
242
|
+
self,
|
243
|
+
min_values: int | float | list[int | float],
|
244
|
+
max_values: int | float | list[int | float],
|
245
|
+
):
|
246
|
+
"""
|
247
|
+
Set the minimum and maximum values for the progress bars.
|
248
|
+
|
249
|
+
Args:
|
250
|
+
min_values(int|float | list[float]): Minimum value(s) for the progress bars. If multiple progress bars are displayed, provide a list of minimum values for each progress bar.
|
251
|
+
max_values(int|float | list[float]): Maximum value(s) for the progress bars. If multiple progress bars are displayed, provide a list of maximum values for each progress bar.
|
252
|
+
"""
|
253
|
+
if isinstance(min_values, int) or isinstance(min_values, float):
|
254
|
+
min_values = [min_values]
|
255
|
+
if isinstance(max_values, int) or isinstance(max_values, float):
|
256
|
+
max_values = [max_values]
|
257
|
+
min_values = self._adjust_list_to_bars(min_values)
|
258
|
+
max_values = self._adjust_list_to_bars(max_values)
|
259
|
+
for ring, min_value, max_value in zip(self._rings, min_values, max_values):
|
260
|
+
ring.set_min_max_values(min_value, max_value)
|
261
|
+
self.update()
|
262
|
+
|
263
|
+
def set_number_of_bars(self, num_bars: int):
|
264
|
+
"""
|
265
|
+
Set the number of progress bars to display.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
num_bars(int): Number of progress bars to display.
|
269
|
+
"""
|
270
|
+
num_bars = max(
|
271
|
+
self.config.min_number_of_bars, min(num_bars, self.config.max_number_of_bars)
|
272
|
+
)
|
273
|
+
if num_bars != self.config.num_bars:
|
274
|
+
self.config.num_bars = num_bars
|
275
|
+
self.initialize_bars()
|
276
|
+
|
277
|
+
def set_value(self, values: int | list, ring_index: int = None):
|
278
|
+
"""
|
279
|
+
Set the values for the progress bars.
|
280
|
+
|
281
|
+
Args:
|
282
|
+
values(int | tuple): Value(s) for the progress bars. If multiple progress bars are displayed, provide a tuple of values for each progress bar.
|
283
|
+
ring_index(int): Index of the progress bar to set the value for. If provided, only a single value can be set.
|
284
|
+
|
285
|
+
Examples:
|
286
|
+
>>> SpiralProgressBar.set_value(50)
|
287
|
+
>>> SpiralProgressBar.set_value([30, 40, 50]) # (outer, middle, inner)
|
288
|
+
>>> SpiralProgressBar.set_value(60, bar_index=1) # Set the value for the middle progress bar.
|
289
|
+
"""
|
290
|
+
if ring_index is not None:
|
291
|
+
ring = self._find_ring_by_index(ring_index)
|
292
|
+
if isinstance(values, list):
|
293
|
+
values = values[0]
|
294
|
+
print(
|
295
|
+
f"Warning: Only a single value can be set for a single progress bar. Using the first value in the list {values}"
|
296
|
+
)
|
297
|
+
ring.set_value(values)
|
298
|
+
else:
|
299
|
+
if isinstance(values, int):
|
300
|
+
values = [values]
|
301
|
+
values = self._adjust_list_to_bars(values)
|
302
|
+
for ring, value in zip(self._rings, values):
|
303
|
+
ring.set_value(value)
|
304
|
+
self.update()
|
305
|
+
|
306
|
+
def set_colors_from_map(self, colormap, color_format: Literal["RGB", "HEX"] = "RGB"):
|
307
|
+
"""
|
308
|
+
Set the colors for the progress bars from a colormap.
|
309
|
+
|
310
|
+
Args:
|
311
|
+
colormap(str): Name of the colormap.
|
312
|
+
color_format(Literal["RGB","HEX"]): Format of the returned colors ('RGB', 'HEX').
|
313
|
+
"""
|
314
|
+
if colormap not in pg.colormap.listMaps():
|
315
|
+
raise ValueError(
|
316
|
+
f"Colormap '{colormap}' not found in the current installation of pyqtgraph"
|
317
|
+
)
|
318
|
+
colors = Colors.golden_angle_color(colormap, self.config.num_bars, color_format)
|
319
|
+
self.set_colors_directly(colors)
|
320
|
+
self.config.color_map = colormap
|
321
|
+
self.update()
|
322
|
+
|
323
|
+
def set_colors_directly(self, colors: list[str | tuple] | str | tuple, bar_index: int = None):
|
324
|
+
"""
|
325
|
+
Set the colors for the progress bars directly.
|
326
|
+
|
327
|
+
Args:
|
328
|
+
colors(list[str | tuple] | str | tuple): Color(s) for the progress bars. If multiple progress bars are displayed, provide a list of colors for each progress bar.
|
329
|
+
bar_index(int): Index of the progress bar to set the color for. If provided, only a single color can be set.
|
330
|
+
"""
|
331
|
+
if bar_index is not None and isinstance(colors, (str, tuple)):
|
332
|
+
bar_index = self._bar_index_check(bar_index)
|
333
|
+
ring = self._find_ring_by_index(bar_index)
|
334
|
+
ring.set_color(colors)
|
335
|
+
else:
|
336
|
+
if isinstance(colors, (str, tuple)):
|
337
|
+
colors = [colors]
|
338
|
+
colors = self._adjust_list_to_bars(colors)
|
339
|
+
for ring, color in zip(self._rings, colors):
|
340
|
+
ring.set_color(color)
|
341
|
+
self.config.color_map = None
|
342
|
+
self.update()
|
343
|
+
|
344
|
+
def set_line_widths(self, widths: int | list[int], bar_index: int = None):
|
345
|
+
"""
|
346
|
+
Set the line widths for the progress bars.
|
347
|
+
|
348
|
+
Args:
|
349
|
+
widths(int | list[int]): Line width(s) for the progress bars. If multiple progress bars are displayed, provide a list of line widths for each progress bar.
|
350
|
+
bar_index(int): Index of the progress bar to set the line width for. If provided, only a single line width can be set.
|
351
|
+
"""
|
352
|
+
if bar_index is not None:
|
353
|
+
bar_index = self._bar_index_check(bar_index)
|
354
|
+
ring = self._find_ring_by_index(bar_index)
|
355
|
+
if isinstance(widths, list):
|
356
|
+
widths = widths[0]
|
357
|
+
print(
|
358
|
+
f"Warning: Only a single line width can be set for a single progress bar. Using the first value in the list {widths}"
|
359
|
+
)
|
360
|
+
ring.set_line_width(widths)
|
361
|
+
else:
|
362
|
+
if isinstance(widths, int):
|
363
|
+
widths = [widths]
|
364
|
+
widths = self._adjust_list_to_bars(widths)
|
365
|
+
self.config.gap = max(widths) * 2
|
366
|
+
for ring, width in zip(self._rings, widths):
|
367
|
+
ring.set_line_width(width)
|
368
|
+
min_size = self._calculate_minimum_size()
|
369
|
+
self.setMinimumSize(min_size)
|
370
|
+
self.update()
|
371
|
+
|
372
|
+
def set_gap(self, gap: int):
|
373
|
+
"""
|
374
|
+
Set the gap between the progress bars.
|
375
|
+
|
376
|
+
Args:
|
377
|
+
gap(int): Gap between the progress bars.
|
378
|
+
"""
|
379
|
+
self.config.gap = gap
|
380
|
+
self.update()
|
381
|
+
|
382
|
+
def set_diameter(self, diameter: int):
|
383
|
+
"""
|
384
|
+
Set the diameter of the widget.
|
385
|
+
|
386
|
+
Args:
|
387
|
+
diameter(int): Diameter of the widget.
|
388
|
+
"""
|
389
|
+
size = QSize(diameter, diameter)
|
390
|
+
self.resize(size)
|
391
|
+
self.setFixedSize(size)
|
392
|
+
|
393
|
+
def _find_ring_by_index(self, index: int) -> Ring:
|
394
|
+
"""
|
395
|
+
Find the ring by index.
|
396
|
+
|
397
|
+
Args:
|
398
|
+
index(int): Index of the ring.
|
399
|
+
|
400
|
+
Returns:
|
401
|
+
Ring: Ring object.
|
402
|
+
"""
|
403
|
+
found_ring = None
|
404
|
+
for ring in self._rings:
|
405
|
+
if ring.config.index == index:
|
406
|
+
found_ring = ring
|
407
|
+
break
|
408
|
+
if found_ring is None:
|
409
|
+
raise ValueError(f"Ring with index {index} not found.")
|
410
|
+
return found_ring
|
411
|
+
|
412
|
+
def enable_auto_updates(self, enable: bool = True):
|
413
|
+
"""
|
414
|
+
Enable or disable updates based on scan status. Overrides manual updates.
|
415
|
+
The behaviour of the whole progress bar widget will be driven by the scan queue status.
|
416
|
+
|
417
|
+
Args:
|
418
|
+
enable(bool): True or False.
|
419
|
+
|
420
|
+
Returns:
|
421
|
+
bool: True if scan segment updates are enabled.
|
422
|
+
"""
|
423
|
+
|
424
|
+
self._auto_updates = enable
|
425
|
+
if enable is True:
|
426
|
+
self.bec_dispatcher.connect_slot(
|
427
|
+
self.on_scan_queue_status, MessageEndpoints.scan_queue_status()
|
428
|
+
)
|
429
|
+
else:
|
430
|
+
self.bec_dispatcher.disconnect_slot(
|
431
|
+
self.on_scan_queue_status, MessageEndpoints.scan_queue_status()
|
432
|
+
)
|
433
|
+
return self._auto_updates
|
434
|
+
|
435
|
+
@Slot(dict, dict)
|
436
|
+
def on_scan_queue_status(self, msg, meta):
|
437
|
+
primary_queue = msg.get("queue").get("primary")
|
438
|
+
info = primary_queue.get("info", None)
|
439
|
+
|
440
|
+
if info:
|
441
|
+
active_request_block = info[0].get("active_request_block", None)
|
442
|
+
if active_request_block:
|
443
|
+
report_instructions = active_request_block.get("report_instructions", None)
|
444
|
+
if report_instructions:
|
445
|
+
instruction_type = list(report_instructions[0].keys())[0]
|
446
|
+
if instruction_type == "scan_progress":
|
447
|
+
if self.config.num_bars != 1:
|
448
|
+
self.set_number_of_bars(1)
|
449
|
+
self._hook_scan_progress(ring_index=0)
|
450
|
+
elif instruction_type == "readback":
|
451
|
+
devices = report_instructions[0].get("readback").get("devices")
|
452
|
+
start = report_instructions[0].get("readback").get("start")
|
453
|
+
end = report_instructions[0].get("readback").get("end")
|
454
|
+
if self.config.num_bars != len(devices):
|
455
|
+
self.set_number_of_bars(len(devices))
|
456
|
+
for index, device in enumerate(devices):
|
457
|
+
self._hook_readback(index, device, start[index], end[index])
|
458
|
+
else:
|
459
|
+
print(f"{instruction_type} not supported yet.")
|
460
|
+
|
461
|
+
# elif instruction_type == "device_progress":
|
462
|
+
# print("hook device_progress")
|
463
|
+
|
464
|
+
def _hook_scan_progress(self, ring_index: int = None):
|
465
|
+
if ring_index is not None:
|
466
|
+
ring = self._find_ring_by_index(ring_index)
|
467
|
+
else:
|
468
|
+
ring = self._rings[0]
|
469
|
+
|
470
|
+
if ring.config.connections.slot == "on_scan_progress":
|
471
|
+
return
|
472
|
+
else:
|
473
|
+
ring.set_connections("on_scan_progress", MessageEndpoints.scan_progress())
|
474
|
+
|
475
|
+
def _hook_readback(self, bar_index: int, device: str, min: float | int, max: float | int):
|
476
|
+
ring = self._find_ring_by_index(bar_index)
|
477
|
+
ring.set_min_max_values(min, max)
|
478
|
+
endpoint = MessageEndpoints.device_readback(device)
|
479
|
+
ring.set_connections("on_device_readback", endpoint)
|
480
|
+
|
481
|
+
def _adjust_list_to_bars(self, items: list) -> list:
|
482
|
+
"""
|
483
|
+
Utility method to adjust the list of parameters to match the number of progress bars.
|
484
|
+
|
485
|
+
Args:
|
486
|
+
items(list): List of parameters for the progress bars.
|
487
|
+
|
488
|
+
Returns:
|
489
|
+
list: List of parameters for the progress bars.
|
490
|
+
"""
|
491
|
+
if items is None:
|
492
|
+
raise ValueError(
|
493
|
+
"Items cannot be None. Please provide a list for parameters for the progress bars."
|
494
|
+
)
|
495
|
+
if not isinstance(items, list):
|
496
|
+
items = [items]
|
497
|
+
if len(items) < self.config.num_bars:
|
498
|
+
last_item = items[-1]
|
499
|
+
items.extend([last_item] * (self.config.num_bars - len(items)))
|
500
|
+
elif len(items) > self.config.num_bars:
|
501
|
+
items = items[: self.config.num_bars]
|
502
|
+
return items
|
503
|
+
|
504
|
+
def _bar_index_check(self, bar_index: int):
|
505
|
+
"""
|
506
|
+
Utility method to check if the bar index is within the range of the number of progress bars.
|
507
|
+
|
508
|
+
Args:
|
509
|
+
bar_index(int): Index of the progress bar to set the value for.
|
510
|
+
"""
|
511
|
+
if not (0 <= bar_index < self.config.num_bars):
|
512
|
+
raise ValueError(
|
513
|
+
f"bar_index {bar_index} out of range of number of bars {self.config.num_bars}."
|
514
|
+
)
|
515
|
+
return bar_index
|
516
|
+
|
517
|
+
def paintEvent(self, event):
|
518
|
+
painter = QtGui.QPainter(self)
|
519
|
+
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
520
|
+
size = min(self.width(), self.height())
|
521
|
+
rect = QtCore.QRect(0, 0, size, size)
|
522
|
+
rect.adjust(
|
523
|
+
max(ring.config.line_width for ring in self._rings),
|
524
|
+
max(ring.config.line_width for ring in self._rings),
|
525
|
+
-max(ring.config.line_width for ring in self._rings),
|
526
|
+
-max(ring.config.line_width for ring in self._rings),
|
527
|
+
)
|
528
|
+
|
529
|
+
for i, ring in enumerate(self._rings):
|
530
|
+
# Background arc
|
531
|
+
painter.setPen(
|
532
|
+
QtGui.QPen(ring.background_color, ring.config.line_width, QtCore.Qt.SolidLine)
|
533
|
+
)
|
534
|
+
offset = self.config.gap * i
|
535
|
+
adjusted_rect = QtCore.QRect(
|
536
|
+
rect.left() + offset,
|
537
|
+
rect.top() + offset,
|
538
|
+
rect.width() - 2 * offset,
|
539
|
+
rect.height() - 2 * offset,
|
540
|
+
)
|
541
|
+
painter.drawArc(adjusted_rect, ring.config.start_position, 360 * 16)
|
542
|
+
|
543
|
+
# Foreground arc
|
544
|
+
pen = QtGui.QPen(ring.color, ring.config.line_width, QtCore.Qt.SolidLine)
|
545
|
+
pen.setCapStyle(QtCore.Qt.RoundCap)
|
546
|
+
painter.setPen(pen)
|
547
|
+
proportion = (ring.value - ring.config.min_value) / (
|
548
|
+
(ring.config.max_value - ring.config.min_value) + 1e-3
|
549
|
+
)
|
550
|
+
angle = int(proportion * 360 * 16 * ring.config.direction)
|
551
|
+
painter.drawArc(adjusted_rect, ring.start_position, angle)
|
552
|
+
|
553
|
+
def reset_diameter(self):
|
554
|
+
"""
|
555
|
+
Reset the fixed size of the widget.
|
556
|
+
"""
|
557
|
+
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
|
558
|
+
self.setMinimumSize(self._calculate_minimum_size())
|
559
|
+
self.setMaximumSize(16777215, 16777215)
|
560
|
+
|
561
|
+
def _calculate_minimum_size(self):
|
562
|
+
"""
|
563
|
+
Calculate the minimum size of the widget.
|
564
|
+
"""
|
565
|
+
if not self.config.rings:
|
566
|
+
print("no rings to get size from setting size to 10x10")
|
567
|
+
return QSize(10, 10)
|
568
|
+
ring_widths = [self.config.rings[i].line_width for i in range(self.config.num_bars)]
|
569
|
+
total_width = sum(ring_widths) + self.config.gap * (self.config.num_bars - 1)
|
570
|
+
diameter = total_width * 2
|
571
|
+
if diameter < 50:
|
572
|
+
diameter = 50
|
573
|
+
return QSize(diameter, diameter)
|
574
|
+
|
575
|
+
def sizeHint(self):
|
576
|
+
min_size = self._calculate_minimum_size()
|
577
|
+
return min_size
|
578
|
+
|
579
|
+
def clear_all(self):
|
580
|
+
for ring in self._rings:
|
581
|
+
ring.cleanup()
|
582
|
+
del ring
|
583
|
+
self._rings = []
|
584
|
+
self.update()
|
585
|
+
self.initialize_bars()
|
586
|
+
|
587
|
+
def cleanup(self):
|
588
|
+
self.bec_dispatcher.disconnect_slot(
|
589
|
+
self.on_scan_queue_status, MessageEndpoints.scan_queue_status()
|
590
|
+
)
|
591
|
+
for ring in self._rings:
|
592
|
+
ring.cleanup()
|
593
|
+
del ring
|
594
|
+
super().cleanup()
|