bec-widgets 0.70.0__py3-none-any.whl → 0.71.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 +42 -54
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +19 -0
- bec_widgets/utils/widget_io.py +18 -2
- bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py +12 -4
- bec_widgets/widgets/device_inputs/device_input_base.py +5 -2
- bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.py +18 -5
- bec_widgets/widgets/scan_control/scan_control.py +133 -365
- bec_widgets/widgets/scan_control/scan_group_box.py +223 -0
- {bec_widgets-0.70.0.dist-info → bec_widgets-0.71.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.70.0.dist-info → bec_widgets-0.71.0.dist-info}/RECORD +22 -17
- {bec_widgets-0.70.0.dist-info → bec_widgets-0.71.0.dist-info}/WHEEL +1 -1
- docs/user/widgets/scan_control.gif +0 -0
- docs/user/widgets/scan_control.md +35 -0
- pyproject.toml +1 -1
- tests/end-2-end/test_scan_control_e2e.py +71 -0
- tests/unit_tests/test_device_input_base.py +4 -4
- tests/unit_tests/test_device_input_widgets.py +10 -10
- tests/unit_tests/test_scan_control.py +255 -115
- tests/unit_tests/test_scan_control_group_box.py +160 -0
- {bec_widgets-0.70.0.dist-info → bec_widgets-0.71.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.70.0.dist-info → bec_widgets-0.71.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,223 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
3
|
+
from qtpy.QtWidgets import (
|
4
|
+
QCheckBox,
|
5
|
+
QComboBox,
|
6
|
+
QDoubleSpinBox,
|
7
|
+
QGridLayout,
|
8
|
+
QGroupBox,
|
9
|
+
QLabel,
|
10
|
+
QLineEdit,
|
11
|
+
QSpinBox,
|
12
|
+
)
|
13
|
+
|
14
|
+
from bec_widgets.utils.widget_io import WidgetIO
|
15
|
+
from bec_widgets.widgets.device_inputs import DeviceLineEdit
|
16
|
+
|
17
|
+
|
18
|
+
class ScanArgType:
|
19
|
+
DEVICE = "device"
|
20
|
+
FLOAT = "float"
|
21
|
+
INT = "int"
|
22
|
+
BOOL = "bool"
|
23
|
+
STR = "str"
|
24
|
+
DEVICEBASE = "DeviceBase"
|
25
|
+
LITERALS = "dict"
|
26
|
+
|
27
|
+
|
28
|
+
class ScanSpinBox(QSpinBox):
|
29
|
+
def __init__(
|
30
|
+
self, parent=None, arg_name: str = None, default: int | None = None, *args, **kwargs
|
31
|
+
):
|
32
|
+
super().__init__(parent=parent, *args, **kwargs)
|
33
|
+
self.arg_name = arg_name
|
34
|
+
self.setRange(-9999, 9999)
|
35
|
+
if default is not None:
|
36
|
+
self.setValue(default)
|
37
|
+
|
38
|
+
|
39
|
+
class ScanDoubleSpinBox(QDoubleSpinBox):
|
40
|
+
def __init__(
|
41
|
+
self, parent=None, arg_name: str = None, default: float | None = None, *args, **kwargs
|
42
|
+
):
|
43
|
+
super().__init__(parent=parent, *args, **kwargs)
|
44
|
+
self.arg_name = arg_name
|
45
|
+
self.setRange(-9999, 9999)
|
46
|
+
if default is not None:
|
47
|
+
self.setValue(default)
|
48
|
+
|
49
|
+
|
50
|
+
class ScanLineEdit(QLineEdit):
|
51
|
+
def __init__(
|
52
|
+
self, parent=None, arg_name: str = None, default: str | None = None, *args, **kwargs
|
53
|
+
):
|
54
|
+
super().__init__(parent=parent, *args, **kwargs)
|
55
|
+
self.arg_name = arg_name
|
56
|
+
if default is not None:
|
57
|
+
self.setText(default)
|
58
|
+
|
59
|
+
|
60
|
+
class ScanCheckBox(QCheckBox):
|
61
|
+
def __init__(
|
62
|
+
self, parent=None, arg_name: str = None, default: bool | None = None, *args, **kwargs
|
63
|
+
):
|
64
|
+
super().__init__(parent=parent, *args, **kwargs)
|
65
|
+
self.arg_name = arg_name
|
66
|
+
if default is not None:
|
67
|
+
self.setChecked(default)
|
68
|
+
|
69
|
+
|
70
|
+
class ScanGroupBox(QGroupBox):
|
71
|
+
WIDGET_HANDLER = {
|
72
|
+
ScanArgType.DEVICE: DeviceLineEdit,
|
73
|
+
ScanArgType.DEVICEBASE: DeviceLineEdit,
|
74
|
+
ScanArgType.FLOAT: ScanDoubleSpinBox,
|
75
|
+
ScanArgType.INT: ScanSpinBox,
|
76
|
+
ScanArgType.BOOL: ScanCheckBox,
|
77
|
+
ScanArgType.STR: ScanLineEdit,
|
78
|
+
ScanArgType.LITERALS: QComboBox, # TODO figure out combobox logic
|
79
|
+
}
|
80
|
+
|
81
|
+
def __init__(
|
82
|
+
self,
|
83
|
+
parent=None,
|
84
|
+
box_type=Literal["args", "kwargs"],
|
85
|
+
config: dict | None = None,
|
86
|
+
*args,
|
87
|
+
**kwargs,
|
88
|
+
):
|
89
|
+
super().__init__(parent=parent, *args, **kwargs)
|
90
|
+
self.config = config
|
91
|
+
self.box_type = box_type
|
92
|
+
|
93
|
+
self.layout = QGridLayout(self)
|
94
|
+
self.labels = []
|
95
|
+
self.widgets = []
|
96
|
+
|
97
|
+
self.init_box(self.config)
|
98
|
+
|
99
|
+
def init_box(self, config: dict):
|
100
|
+
box_name = config.get("name", "ScanGroupBox")
|
101
|
+
self.inputs = config.get("inputs", {})
|
102
|
+
self.setTitle(box_name)
|
103
|
+
|
104
|
+
# Labels
|
105
|
+
self.add_input_labels(self.inputs, 0)
|
106
|
+
|
107
|
+
# Widgets
|
108
|
+
if self.box_type == "args":
|
109
|
+
min_bundle = self.config.get("min", 1)
|
110
|
+
for i in range(1, min_bundle + 1):
|
111
|
+
self.add_input_widgets(self.inputs, i)
|
112
|
+
else:
|
113
|
+
self.add_input_widgets(self.inputs, 1)
|
114
|
+
|
115
|
+
def add_input_labels(self, group_inputs: dict, row: int) -> None:
|
116
|
+
"""
|
117
|
+
Adds the given arg_group from arg_bundle to the scan control layout. The input labels are always added to the first row.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
group(dict): Dictionary containing the arg_group information.
|
121
|
+
"""
|
122
|
+
for column_index, item in enumerate(group_inputs):
|
123
|
+
arg_name = item.get("name", None)
|
124
|
+
display_name = item.get("display_name", arg_name)
|
125
|
+
label = QLabel(text=display_name)
|
126
|
+
self.layout.addWidget(label, row, column_index)
|
127
|
+
self.labels.append(label)
|
128
|
+
|
129
|
+
def add_input_widgets(self, group_inputs: dict, row) -> None:
|
130
|
+
"""
|
131
|
+
Adds the given arg_group from arg_bundle to the scan control layout.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
group_inputs(dict): Dictionary containing the arg_group information.
|
135
|
+
row(int): The row to add the widgets to.
|
136
|
+
"""
|
137
|
+
for column_index, item in enumerate(group_inputs):
|
138
|
+
arg_name = item.get("name", None)
|
139
|
+
default = item.get("default", None)
|
140
|
+
widget = self.WIDGET_HANDLER.get(item["type"], None)
|
141
|
+
if widget is None:
|
142
|
+
print(f"Unsupported annotation '{item['type']}' for parameter '{item['name']}'")
|
143
|
+
continue
|
144
|
+
if default == "_empty":
|
145
|
+
default = None
|
146
|
+
widget_to_add = widget(arg_name=arg_name, default=default)
|
147
|
+
tooltip = item.get("tooltip", None)
|
148
|
+
if tooltip is not None:
|
149
|
+
widget_to_add.setToolTip(item["tooltip"])
|
150
|
+
self.layout.addWidget(widget_to_add, row, column_index)
|
151
|
+
self.widgets.append(widget_to_add)
|
152
|
+
|
153
|
+
def add_widget_bundle(self):
|
154
|
+
"""
|
155
|
+
Adds a new row of widgets to the scan control layout. Only usable for arg_groups.
|
156
|
+
"""
|
157
|
+
if self.box_type != "args":
|
158
|
+
return
|
159
|
+
arg_max = self.config.get("max", None)
|
160
|
+
row = self.layout.rowCount()
|
161
|
+
if arg_max is not None and row >= arg_max:
|
162
|
+
return
|
163
|
+
|
164
|
+
self.add_input_widgets(self.inputs, row)
|
165
|
+
|
166
|
+
def remove_widget_bundle(self):
|
167
|
+
"""
|
168
|
+
Removes the last row of widgets from the scan control layout. Only usable for arg_groups.
|
169
|
+
"""
|
170
|
+
if self.box_type != "args":
|
171
|
+
return
|
172
|
+
arg_min = self.config.get("min", None)
|
173
|
+
row = self.count_arg_rows()
|
174
|
+
if arg_min is not None and row <= arg_min:
|
175
|
+
return
|
176
|
+
|
177
|
+
for widget in self.widgets[-len(self.inputs) :]:
|
178
|
+
widget.deleteLater()
|
179
|
+
self.widgets = self.widgets[: -len(self.inputs)]
|
180
|
+
|
181
|
+
def get_parameters(self):
|
182
|
+
"""
|
183
|
+
Returns the parameters from the widgets in the scan control layout formated to run scan from BEC.
|
184
|
+
"""
|
185
|
+
if self.box_type == "args":
|
186
|
+
return self._get_arg_parameterts()
|
187
|
+
elif self.box_type == "kwargs":
|
188
|
+
return self._get_kwarg_parameters()
|
189
|
+
|
190
|
+
def _get_arg_parameterts(self):
|
191
|
+
args = []
|
192
|
+
for i in range(1, self.layout.rowCount()):
|
193
|
+
for j in range(self.layout.columnCount()):
|
194
|
+
widget = self.layout.itemAtPosition(i, j).widget()
|
195
|
+
if isinstance(widget, DeviceLineEdit):
|
196
|
+
value = widget.get_device()
|
197
|
+
else:
|
198
|
+
value = WidgetIO.get_value(widget)
|
199
|
+
args.append(value)
|
200
|
+
return args
|
201
|
+
|
202
|
+
def _get_kwarg_parameters(self):
|
203
|
+
kwargs = {}
|
204
|
+
for i in range(self.layout.columnCount()):
|
205
|
+
widget = self.layout.itemAtPosition(1, i).widget()
|
206
|
+
if isinstance(widget, DeviceLineEdit):
|
207
|
+
value = widget.get_device()
|
208
|
+
else:
|
209
|
+
value = WidgetIO.get_value(widget)
|
210
|
+
kwargs[widget.arg_name] = value
|
211
|
+
return kwargs
|
212
|
+
|
213
|
+
def count_arg_rows(self):
|
214
|
+
widget_rows = 0
|
215
|
+
for row in range(self.layout.rowCount()):
|
216
|
+
for col in range(self.layout.columnCount()):
|
217
|
+
item = self.layout.itemAtPosition(row, col)
|
218
|
+
if item is not None:
|
219
|
+
widget = item.widget()
|
220
|
+
if widget is not None:
|
221
|
+
if isinstance(widget, DeviceLineEdit):
|
222
|
+
widget_rows += 1
|
223
|
+
return widget_rows
|
@@ -2,11 +2,11 @@
|
|
2
2
|
.gitlab-ci.yml,sha256=RnYDz4zKXjlqltTryprlB1s5vLXxI2-seW-Vb70NNF0,8162
|
3
3
|
.pylintrc,sha256=OstrgmEyP0smNFBKoIN5_26-UmNZgMHnbjvAWX0UrLs,18535
|
4
4
|
.readthedocs.yaml,sha256=aSOc277LqXcsTI6lgvm_JY80lMlr69GbPKgivua2cS0,603
|
5
|
-
CHANGELOG.md,sha256=
|
5
|
+
CHANGELOG.md,sha256=G8kiuKBLYo8mE65R9TMUCjp2wL0-b_Qmf1jfmVq6xfw,7407
|
6
6
|
LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
7
|
-
PKG-INFO,sha256=
|
7
|
+
PKG-INFO,sha256=yJmWivH4mEeBkmuE4Qt0Tba4AEFW8OJIXt3QQxnUP8A,1302
|
8
8
|
README.md,sha256=y4jB6wvArS7N8_iTbKWnSM_oRAqLA2GqgzUR-FMh5sU,2645
|
9
|
-
pyproject.toml,sha256=
|
9
|
+
pyproject.toml,sha256=srKN62y_l-c28R8DtIAj_uspBsCVVDcoWYMi2hdnbgo,2215
|
10
10
|
.git_hooks/pre-commit,sha256=n3RofIZHJl8zfJJIUomcMyYGFi_rwq4CC19z0snz3FI,286
|
11
11
|
.gitlab/issue_templates/bug_report_template.md,sha256=gAuyEwl7XlnebBrkiJ9AqffSNOywmr8vygUFWKTuQeI,386
|
12
12
|
.gitlab/issue_templates/documentation_update_template.md,sha256=FHLdb3TS_D9aL4CYZCjyXSulbaW5mrN2CmwTaeLPbNw,860
|
@@ -17,7 +17,7 @@ bec_widgets/assets/bec_widgets_icon.png,sha256=K8dgGwIjalDh9PRHUsSQBqgdX7a00nM3i
|
|
17
17
|
bec_widgets/assets/terminal_icon.png,sha256=bJl7Tft4Fi2uxvuXI8o14uMHnI9eAWKSU2uftXCH9ws,3889
|
18
18
|
bec_widgets/cli/__init__.py,sha256=d0Q6Fn44e7wFfLabDOBxpcJ1DPKWlFunGYDUBmO-4hA,22
|
19
19
|
bec_widgets/cli/auto_updates.py,sha256=DyBV3HnjMSH-cvVkYNcDiYKVf0Xut4Qy2qGQqkW47Bw,4833
|
20
|
-
bec_widgets/cli/client.py,sha256=
|
20
|
+
bec_widgets/cli/client.py,sha256=6W85QrsOAmNipucjWBf65AOUd-ODvuzlCAjtE9YMskM,58455
|
21
21
|
bec_widgets/cli/client_utils.py,sha256=_Hb2nl1rKEf7k4By9VZDYl5YyGFczxMuYIFMVrOAZD0,12182
|
22
22
|
bec_widgets/cli/generate_cli.py,sha256=InKBVYM7DRfAVLNJhRJbWWSSPBQBHI8Ek6v7NCsK0ME,4997
|
23
23
|
bec_widgets/cli/rpc_register.py,sha256=QxXUZu5XNg00Yf5O3UHWOXg3-f_pzKjjoZYMOa-MOJc,2216
|
@@ -52,7 +52,7 @@ bec_widgets/utils/rpc_decorator.py,sha256=pIvtqySQLnuS7l2Ti_UAe4WX7CRivZnsE5ZdKA
|
|
52
52
|
bec_widgets/utils/thread_checker.py,sha256=rDNuA3X6KQyA7JPb67mccTg0z8YkInynLAENQDQpbuE,1607
|
53
53
|
bec_widgets/utils/ui_loader.py,sha256=5NktcP1r1HQub7K82fW_jkj8rT2cqJQdMvDxwToLY4E,1650
|
54
54
|
bec_widgets/utils/validator_delegate.py,sha256=Emj1WF6W8Ke1ruBWUfmHdVJpmOSPezuOt4zvQTay_44,442
|
55
|
-
bec_widgets/utils/widget_io.py,sha256=
|
55
|
+
bec_widgets/utils/widget_io.py,sha256=U_02ESf9Ukz63B01AzYioNepSc6SX11nJhPPSDmL4IA,11318
|
56
56
|
bec_widgets/utils/yaml_dialog.py,sha256=cMVif-39SB9WjwGH5FWBJcFs4tnfFJFs5cacydRyhy0,1853
|
57
57
|
bec_widgets/widgets/__init__.py,sha256=6RE9Pot2ud6BNJc_ZKiE--U-lgVRUff2IVR91lPcCbo,214
|
58
58
|
bec_widgets/widgets/bec_status_box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -62,15 +62,15 @@ bec_widgets/widgets/buttons/__init__.py,sha256=74ucIRU6-anoqQ-zT7wbrysmxhg_3_04x
|
|
62
62
|
bec_widgets/widgets/buttons/stop_button/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
63
63
|
bec_widgets/widgets/buttons/stop_button/stop_button.py,sha256=x4a7RvlMkHzOd05zKOGYkyTmBza7Me7jgOL9WIgA_c4,906
|
64
64
|
bec_widgets/widgets/device_inputs/__init__.py,sha256=BcWvcSASPh6YdDu5jfC48xqI2_iBj1epUt4doYJQHEs,122
|
65
|
-
bec_widgets/widgets/device_inputs/device_input_base.py,sha256=
|
65
|
+
bec_widgets/widgets/device_inputs/device_input_base.py,sha256=AP0Pgto87CKqgTuzrmFL3a7MOW9_qm1rmyiI2ZrvCOM,3839
|
66
66
|
bec_widgets/widgets/device_inputs/device_combobox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
67
|
-
bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py,sha256=
|
67
|
+
bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py,sha256=S5QIDis1SKVVUH2bYgROE40xdMBCuoZAGuh5yW0OvU0,2815
|
68
68
|
bec_widgets/widgets/device_inputs/device_combobox/device_combobox.pyproject,sha256=AH6Oz_4aDCzsBbHWnCNK_ALvQ0Qwj-z24sUG1oNSK3I,75
|
69
69
|
bec_widgets/widgets/device_inputs/device_combobox/device_combobox_plugin.py,sha256=VfbP0PrLwME8OB1bz1kVmy8vJCZDRONvas5j5cPyd08,1218
|
70
70
|
bec_widgets/widgets/device_inputs/device_combobox/launch_device_combobox.py,sha256=jr9JJHtIPmbUNLpGLUESS_bYcITnbv-W3EdrZCHq2Rg,267
|
71
71
|
bec_widgets/widgets/device_inputs/device_combobox/register_device_combobox.py,sha256=V6QcxBU9Lli4qy2Vhf9bFfpTsofsc-bT-_vEp0heBsM,518
|
72
72
|
bec_widgets/widgets/device_inputs/device_line_edit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
73
|
-
bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.py,sha256
|
73
|
+
bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.py,sha256=147grp_zFMlYq4MiSffw9QULTD5hQWby7v7xf93-Oxk,3368
|
74
74
|
bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.pyproject,sha256=0GTk3WfNUHl-6tFEAY0pel5XBpVR0oyRX2BU-LNzVps,77
|
75
75
|
bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit_plugin.py,sha256=J1MNrtXkgBrwEsUXnf0pGRoNJ1g_OWqk-xafSpV4ot8,1239
|
76
76
|
bec_widgets/widgets/device_inputs/device_line_edit/launch_device_line_edit.py,sha256=3sVZ-5Hoy1smhgBcnzO9SXHk5_oUEaWFnRfslXAW3_4,267
|
@@ -108,7 +108,8 @@ bec_widgets/widgets/motor_control/selection/__init__.py,sha256=47DEQpj8HBSa-_TIm
|
|
108
108
|
bec_widgets/widgets/motor_control/selection/selection.py,sha256=WNHndvv4JvxeAMnDFBMTvUILcn9u_0mWGRsgNiBZEsM,3988
|
109
109
|
bec_widgets/widgets/motor_control/selection/selection.ui,sha256=vXXpvNWuL6xyHhW7Lx1zmVFX-95Z5AXGlhKQD2HmM1A,1779
|
110
110
|
bec_widgets/widgets/scan_control/__init__.py,sha256=IOfHl15vxb_uC6KN62-PeUzbBha_vQyqkkXbJ2HU674,38
|
111
|
-
bec_widgets/widgets/scan_control/scan_control.py,sha256=
|
111
|
+
bec_widgets/widgets/scan_control/scan_control.py,sha256=u2fjSUiSRYTkIq9WhdfQuQV6Sv3iWWcSfCraVGro1RQ,7686
|
112
|
+
bec_widgets/widgets/scan_control/scan_group_box.py,sha256=8XGpYcdKTEtiqOFbBxZ6xV07ZJ_tg9R-JDfsdTdqXSI,7400
|
112
113
|
bec_widgets/widgets/spiral_progress_bar/__init__.py,sha256=4efbtcqCToMIw5bkQrTzy2TzuBCXvlhuUPh1bYC_Yzg,51
|
113
114
|
bec_widgets/widgets/spiral_progress_bar/ring.py,sha256=7i5oKpW8eUQGvLyKce2-2rlaGDVLec__DoWp6hfJlRw,10524
|
114
115
|
bec_widgets/widgets/spiral_progress_bar/spiral_progress_bar.py,sha256=cMi4g7zNTLrkkzZ9ChiIBTaqigDCYwzrgA2iRmq9dUI,24050
|
@@ -159,6 +160,8 @@ docs/user/widgets/buttons.md,sha256=Yci21PmxlRvfKcrvY7mVI7JkECPmE6j9WyWyH3j7Y2o,
|
|
159
160
|
docs/user/widgets/image_plot.gif,sha256=_mVFhMTXGqwDOcEtrBHMZj5Thn2sLhDAHEeL2XyHN-s,14098977
|
160
161
|
docs/user/widgets/motor.gif,sha256=FtaWdRHx4UZaGJPpq8LNhMMgX4PFcAB6IZ93JCMEh_w,2280719
|
161
162
|
docs/user/widgets/progress_bar.gif,sha256=5jh0Zw2BBGPuNxszV1DBLJCb4_6glIRX-U2ABjnsK2k,5263592
|
163
|
+
docs/user/widgets/scan_control.gif,sha256=zrVOZgleMbu7Jd8AAIn2fQ08tNAEMSud3g0ZLyNUcjQ,1506739
|
164
|
+
docs/user/widgets/scan_control.md,sha256=lsn08uw90uRajxfBqRxIYiq52xWKqRPVO6mfI6qzolI,1721
|
162
165
|
docs/user/widgets/scatter_2D.gif,sha256=yHpsuAUseMafJjI_J5BcOhmE3nu9VFn_Xm9XHzJaH5I,13188862
|
163
166
|
docs/user/widgets/spiral_progress_bar.md,sha256=QTgUDIl6XPuK_HwSfB6sNijZ4bss26biDg6B_mJ8Pxk,2208
|
164
167
|
docs/user/widgets/text_box.md,sha256=_ST7RQWXl67MKLm6dTa995GjoughPUyK_hLnF8SPZcM,925
|
@@ -171,6 +174,7 @@ tests/end-2-end/conftest.py,sha256=-BLnFE-NeCerf6xahGCkbZ4Ktactowi6RkBnboIzRvg,1
|
|
171
174
|
tests/end-2-end/test_bec_dock_rpc_e2e.py,sha256=8iJz4lITspY7eHdSgy9YvGUGTu3fsSperoVGBvTGT0U,9067
|
172
175
|
tests/end-2-end/test_bec_figure_rpc_e2e.py,sha256=zTbB_F4Fs-QG8KhMK24xfsrCQBgZUAguMk3KFdEdP2o,5095
|
173
176
|
tests/end-2-end/test_rpc_register_e2e.py,sha256=3dfCnSvdcRO92pzHt9WlCTK0vzTKAvPtliEoEKrtuzQ,1604
|
177
|
+
tests/end-2-end/test_scan_control_e2e.py,sha256=u7oLgFyltkMW2apSZKDukMIXvYrbhHrU32p4mBdn8VE,2276
|
174
178
|
tests/unit_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
175
179
|
tests/unit_tests/client_mocks.py,sha256=R71MJSu8IEyxcJjaIWCPACO_wOfx2Uv_qkmFVVmv7EQ,4195
|
176
180
|
tests/unit_tests/conftest.py,sha256=KrnktXPWmZhnKNue-xGWOLD1XGEvdz9Vf7V2eO3XQ3A,596
|
@@ -183,15 +187,16 @@ tests/unit_tests/test_bec_status_box.py,sha256=zZ4pe7DaBzzpRsy62yHFkUGgAGb3zZU3I
|
|
183
187
|
tests/unit_tests/test_client_utils.py,sha256=eViJ1Tz-HX9TkMvQH6W8cO-c3_1I8bUc4_Yen6LOc0E,830
|
184
188
|
tests/unit_tests/test_color_validation.py,sha256=csdvVKAohENZIRY-JQ97Hv-TShb1erj4oKMX7QRwo78,1883
|
185
189
|
tests/unit_tests/test_crosshair.py,sha256=3OMAJ2ZaISYXMOtkXf1rPdy94vCr8njeLi6uHblBL9Q,5045
|
186
|
-
tests/unit_tests/test_device_input_base.py,sha256=
|
187
|
-
tests/unit_tests/test_device_input_widgets.py,sha256=
|
190
|
+
tests/unit_tests/test_device_input_base.py,sha256=DiwbNzFQ8o90ELhlT103roqLNEzJ09bvlpNrOmT4lfM,2423
|
191
|
+
tests/unit_tests/test_device_input_widgets.py,sha256=yQ67Xwn-T7NHAIT1XLA4DvcxQEIJYMUr9PfPnT6CAPI,5805
|
188
192
|
tests/unit_tests/test_generate_cli_client.py,sha256=adcMoXjWpFLVjpauCu0r31CMMibUY1LF1MMf8rO-6rw,2815
|
189
193
|
tests/unit_tests/test_motor_control.py,sha256=NBekcGALo5mYkuyBJvBhvJkWiQDV82hI4GmsobRzjTI,20770
|
190
194
|
tests/unit_tests/test_plot_base.py,sha256=Akr_JgglUCrtERtdtsMqWko_MLUYoAYRGzV2sum-YHo,3836
|
191
195
|
tests/unit_tests/test_plugin_utils.py,sha256=PonKNpu4fZaFmKbI2v0tZJjZrsTvBGSF96bPHvKJvrE,608
|
192
196
|
tests/unit_tests/test_rpc_register.py,sha256=hECjZEimd440mwRrO0rg7L3PKN7__3DgjmESN6wx3bo,1179
|
193
197
|
tests/unit_tests/test_rpc_widget_handler.py,sha256=QC85N48UAFsROKRNkoDIfInzy1mLhp2buLVSJifkhiU,236
|
194
|
-
tests/unit_tests/test_scan_control.py,sha256=
|
198
|
+
tests/unit_tests/test_scan_control.py,sha256=Wr6KcE8av4sEIOx5VgYbzVCem3Jgb4Kzx_oOuvwlmkE,13459
|
199
|
+
tests/unit_tests/test_scan_control_group_box.py,sha256=HNqjP10B_NonikspNwKz9upJU-t7xf6hwBerNhbC-uo,5563
|
195
200
|
tests/unit_tests/test_spiral_progress_bar.py,sha256=n5aLSZ2B6K5a1vQuKTERnCSmIz9hYGFyk7jP3TU0AwQ,12438
|
196
201
|
tests/unit_tests/test_stop_button.py,sha256=2OH9dhs_-S5QovPPgU-5hJoViE1YKZa0gxisb4vOY28,712
|
197
202
|
tests/unit_tests/test_text_box_widget.py,sha256=cT0uEHt_6d-FwST0A_wE9sFW9E3F_nJbKhuBAeU4yHg,1862
|
@@ -205,8 +210,8 @@ tests/unit_tests/test_configs/config_device_no_entry.yaml,sha256=hdvue9KLc_kfNzG
|
|
205
210
|
tests/unit_tests/test_configs/config_scan.yaml,sha256=vo484BbWOjA_e-h6bTjSV9k7QaQHrlAvx-z8wtY-P4E,1915
|
206
211
|
tests/unit_tests/test_msgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
207
212
|
tests/unit_tests/test_msgs/available_scans_message.py,sha256=m_z97hIrjHXXMa2Ex-UvsPmTxOYXfjxyJaGkIY6StTY,46532
|
208
|
-
bec_widgets-0.
|
209
|
-
bec_widgets-0.
|
210
|
-
bec_widgets-0.
|
211
|
-
bec_widgets-0.
|
212
|
-
bec_widgets-0.
|
213
|
+
bec_widgets-0.71.0.dist-info/METADATA,sha256=yJmWivH4mEeBkmuE4Qt0Tba4AEFW8OJIXt3QQxnUP8A,1302
|
214
|
+
bec_widgets-0.71.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
215
|
+
bec_widgets-0.71.0.dist-info/entry_points.txt,sha256=3otEkCdDB9LZJuBLzG4pFLK5Di0CVybN_12IsZrQ-58,166
|
216
|
+
bec_widgets-0.71.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
217
|
+
bec_widgets-0.71.0.dist-info/RECORD,,
|
Binary file
|
@@ -0,0 +1,35 @@
|
|
1
|
+
(user.widgets.scan_control)=
|
2
|
+
|
3
|
+
# Scan Control
|
4
|
+
|
5
|
+
**Purpose:**
|
6
|
+
|
7
|
+
The `ScanControl` widget is designed to generate a graphical user interface (GUI) to control various scan operations
|
8
|
+
based on the scan's signature and `gui_config`. The widget is used to control the scan operations, such as starting,
|
9
|
+
stopping, and pausing the scan. The widget also provides a graphical representation of the scan progress and the scan
|
10
|
+
status. The widget is designed to be used in conjunction with the `ScanServer` and `ScanBundler` services from the BEC
|
11
|
+
core services.
|
12
|
+
|
13
|
+
By default the widget supports only the scans which have defined `gui_config` and are inhereted from these scan classes:
|
14
|
+
|
15
|
+
- [ScanBase](https://beamline-experiment-control.readthedocs.io/en/latest/api_reference/_autosummary/bec_server.scan_server.scans.ScanBase.html)
|
16
|
+
- [SyncFlyScanBase](https://beamline-experiment-control.readthedocs.io/en/latest/api_reference/_autosummary/bec_server.scan_server.scans.SyncFlyScanBase.html)
|
17
|
+
- [AsyncFlyScanBase](https://beamline-experiment-control.readthedocs.io/en/latest/api_reference/_autosummary/bec_server.scan_server.scans.AsyncFlyScanBase.html)
|
18
|
+
|
19
|
+
**Key Features:**
|
20
|
+
|
21
|
+
- Automatically generates a control interface based on scan signatures and `gui_config`.
|
22
|
+
- Supports adding and removing argument bundles dynamically.
|
23
|
+
- Provides a visual representation of scan parameters grouped by functionality.
|
24
|
+
- Integrates start and stop controls for executing and halting scans.
|
25
|
+
|
26
|
+
**Example of Use:**
|
27
|
+
|
28
|
+
**Code example:**
|
29
|
+
The following code snipped demonstrates how to create a `ScanControl` widget using BEC Widgets within `BECIPythonClient`
|
30
|
+
|
31
|
+

|
32
|
+
|
33
|
+
```python
|
34
|
+
scan_control = gui.add_dock().add_widget("ScanControl")
|
35
|
+
```
|
pyproject.toml
CHANGED
@@ -0,0 +1,71 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from bec_widgets.utils.widget_io import WidgetIO
|
6
|
+
from bec_widgets.widgets.scan_control import ScanControl
|
7
|
+
|
8
|
+
|
9
|
+
@pytest.fixture(scope="function")
|
10
|
+
def scan_control(qtbot, bec_client_lib): # , mock_dev):
|
11
|
+
widget = ScanControl(client=bec_client_lib)
|
12
|
+
qtbot.addWidget(widget)
|
13
|
+
qtbot.waitExposed(widget)
|
14
|
+
yield widget
|
15
|
+
|
16
|
+
|
17
|
+
def test_scan_control_populate_scans_e2e(scan_control):
|
18
|
+
expected_scans = [
|
19
|
+
"grid_scan",
|
20
|
+
"fermat_scan",
|
21
|
+
"round_scan",
|
22
|
+
"cont_line_scan",
|
23
|
+
"cont_line_fly_scan",
|
24
|
+
"round_scan_fly",
|
25
|
+
"round_roi_scan",
|
26
|
+
"time_scan",
|
27
|
+
"monitor_scan",
|
28
|
+
"acquire",
|
29
|
+
"line_scan",
|
30
|
+
]
|
31
|
+
items = [
|
32
|
+
scan_control.comboBox_scan_selection.itemText(i)
|
33
|
+
for i in range(scan_control.comboBox_scan_selection.count())
|
34
|
+
]
|
35
|
+
assert scan_control.comboBox_scan_selection.count() == len(expected_scans)
|
36
|
+
assert sorted(items) == sorted(expected_scans)
|
37
|
+
|
38
|
+
|
39
|
+
def test_run_line_scan_with_parameters_e2e(scan_control, bec_client_lib, qtbot):
|
40
|
+
client = bec_client_lib
|
41
|
+
queue = client.queue
|
42
|
+
|
43
|
+
scan_name = "line_scan"
|
44
|
+
kwargs = {"exp_time": 0.01, "steps": 10, "relative": True, "burst_at_each_point": 1}
|
45
|
+
args = {"device": "samx", "start": -5, "stop": 5}
|
46
|
+
|
47
|
+
scan_control.comboBox_scan_selection.setCurrentText(scan_name)
|
48
|
+
|
49
|
+
# Set kwargs in the UI
|
50
|
+
for kwarg_box in scan_control.kwarg_boxes:
|
51
|
+
for widget in kwarg_box.widgets:
|
52
|
+
for key, value in kwargs.items():
|
53
|
+
if widget.arg_name == key:
|
54
|
+
WidgetIO.set_value(widget, value)
|
55
|
+
break
|
56
|
+
# Set args in the UI
|
57
|
+
for widget in scan_control.arg_box.widgets:
|
58
|
+
for key, value in args.items():
|
59
|
+
if widget.arg_name == key:
|
60
|
+
WidgetIO.set_value(widget, value)
|
61
|
+
break
|
62
|
+
|
63
|
+
# Run the scan
|
64
|
+
scan_control.button_run_scan.click()
|
65
|
+
time.sleep(2)
|
66
|
+
|
67
|
+
last_scan = queue.scan_storage.storage[-1]
|
68
|
+
assert last_scan.status_message.info["scan_name"] == scan_name
|
69
|
+
assert last_scan.status_message.info["exp_time"] == kwargs["exp_time"]
|
70
|
+
assert last_scan.status_message.info["scan_motors"] == [args["device"]]
|
71
|
+
assert last_scan.status_message.info["num_points"] == kwargs["steps"]
|
@@ -17,7 +17,7 @@ def test_device_input_base_init(device_input_base):
|
|
17
17
|
assert isinstance(device_input_base, DeviceInputBase)
|
18
18
|
assert device_input_base.config.widget_class == "DeviceInputBase"
|
19
19
|
assert device_input_base.config.device_filter is None
|
20
|
-
assert device_input_base.config.
|
20
|
+
assert device_input_base.config.default is None
|
21
21
|
assert device_input_base.devices == []
|
22
22
|
|
23
23
|
|
@@ -26,12 +26,12 @@ def test_device_input_base_init_with_config(mocked_client):
|
|
26
26
|
"widget_class": "DeviceInputBase",
|
27
27
|
"gui_id": "test_gui_id",
|
28
28
|
"device_filter": "FakePositioner",
|
29
|
-
"
|
29
|
+
"default": "samx",
|
30
30
|
}
|
31
31
|
widget = DeviceInputBase(client=mocked_client, config=config)
|
32
32
|
assert widget.config.gui_id == "test_gui_id"
|
33
33
|
assert widget.config.device_filter == "FakePositioner"
|
34
|
-
assert widget.config.
|
34
|
+
assert widget.config.default == "samx"
|
35
35
|
|
36
36
|
|
37
37
|
def test_device_input_base_set_device_filter(device_input_base):
|
@@ -47,7 +47,7 @@ def test_device_input_base_set_device_filter_error(device_input_base):
|
|
47
47
|
|
48
48
|
def test_device_input_base_set_default_device(device_input_base):
|
49
49
|
device_input_base.set_default_device("samx")
|
50
|
-
assert device_input_base.config.
|
50
|
+
assert device_input_base.config.default == "samx"
|
51
51
|
|
52
52
|
|
53
53
|
def test_device_input_base_set_default_device_error(device_input_base):
|
@@ -21,7 +21,7 @@ def device_input_combobox_with_config(qtbot, mocked_client):
|
|
21
21
|
"widget_class": "DeviceComboBox",
|
22
22
|
"gui_id": "test_gui_id",
|
23
23
|
"device_filter": "FakePositioner",
|
24
|
-
"
|
24
|
+
"default": "samx",
|
25
25
|
"arg_name": "test_arg_name",
|
26
26
|
}
|
27
27
|
widget = DeviceComboBox(client=mocked_client, config=config)
|
@@ -37,7 +37,7 @@ def device_input_combobox_with_kwargs(qtbot, mocked_client):
|
|
37
37
|
client=mocked_client,
|
38
38
|
gui_id="test_gui_id",
|
39
39
|
device_filter="FakePositioner",
|
40
|
-
|
40
|
+
default="samx",
|
41
41
|
arg_name="test_arg_name",
|
42
42
|
)
|
43
43
|
qtbot.addWidget(widget)
|
@@ -52,7 +52,7 @@ def test_device_input_combobox_init(device_input_combobox):
|
|
52
52
|
assert isinstance(device_input_combobox, DeviceComboBox)
|
53
53
|
assert device_input_combobox.config.widget_class == "DeviceComboBox"
|
54
54
|
assert device_input_combobox.config.device_filter is None
|
55
|
-
assert device_input_combobox.config.
|
55
|
+
assert device_input_combobox.config.default is None
|
56
56
|
assert device_input_combobox.devices == [
|
57
57
|
"samx",
|
58
58
|
"samy",
|
@@ -72,14 +72,14 @@ def test_device_input_combobox_init(device_input_combobox):
|
|
72
72
|
def test_device_input_combobox_init_with_config(device_input_combobox_with_config):
|
73
73
|
assert device_input_combobox_with_config.config.gui_id == "test_gui_id"
|
74
74
|
assert device_input_combobox_with_config.config.device_filter == "FakePositioner"
|
75
|
-
assert device_input_combobox_with_config.config.
|
75
|
+
assert device_input_combobox_with_config.config.default == "samx"
|
76
76
|
assert device_input_combobox_with_config.config.arg_name == "test_arg_name"
|
77
77
|
|
78
78
|
|
79
79
|
def test_device_input_combobox_init_with_kwargs(device_input_combobox_with_kwargs):
|
80
80
|
assert device_input_combobox_with_kwargs.config.gui_id == "test_gui_id"
|
81
81
|
assert device_input_combobox_with_kwargs.config.device_filter == "FakePositioner"
|
82
|
-
assert device_input_combobox_with_kwargs.config.
|
82
|
+
assert device_input_combobox_with_kwargs.config.default == "samx"
|
83
83
|
assert device_input_combobox_with_kwargs.config.arg_name == "test_arg_name"
|
84
84
|
|
85
85
|
|
@@ -106,7 +106,7 @@ def device_input_line_edit_with_config(qtbot, mocked_client):
|
|
106
106
|
"widget_class": "DeviceLineEdit",
|
107
107
|
"gui_id": "test_gui_id",
|
108
108
|
"device_filter": "FakePositioner",
|
109
|
-
"
|
109
|
+
"default": "samx",
|
110
110
|
"arg_name": "test_arg_name",
|
111
111
|
}
|
112
112
|
widget = DeviceLineEdit(client=mocked_client, config=config)
|
@@ -122,7 +122,7 @@ def device_input_line_edit_with_kwargs(qtbot, mocked_client):
|
|
122
122
|
client=mocked_client,
|
123
123
|
gui_id="test_gui_id",
|
124
124
|
device_filter="FakePositioner",
|
125
|
-
|
125
|
+
default="samx",
|
126
126
|
arg_name="test_arg_name",
|
127
127
|
)
|
128
128
|
qtbot.addWidget(widget)
|
@@ -137,7 +137,7 @@ def test_device_input_line_edit_init(device_input_line_edit):
|
|
137
137
|
assert isinstance(device_input_line_edit, DeviceLineEdit)
|
138
138
|
assert device_input_line_edit.config.widget_class == "DeviceLineEdit"
|
139
139
|
assert device_input_line_edit.config.device_filter is None
|
140
|
-
assert device_input_line_edit.config.
|
140
|
+
assert device_input_line_edit.config.default is None
|
141
141
|
assert device_input_line_edit.devices == [
|
142
142
|
"samx",
|
143
143
|
"samy",
|
@@ -157,14 +157,14 @@ def test_device_input_line_edit_init(device_input_line_edit):
|
|
157
157
|
def test_device_input_line_edit_init_with_config(device_input_line_edit_with_config):
|
158
158
|
assert device_input_line_edit_with_config.config.gui_id == "test_gui_id"
|
159
159
|
assert device_input_line_edit_with_config.config.device_filter == "FakePositioner"
|
160
|
-
assert device_input_line_edit_with_config.config.
|
160
|
+
assert device_input_line_edit_with_config.config.default == "samx"
|
161
161
|
assert device_input_line_edit_with_config.config.arg_name == "test_arg_name"
|
162
162
|
|
163
163
|
|
164
164
|
def test_device_input_line_edit_init_with_kwargs(device_input_line_edit_with_kwargs):
|
165
165
|
assert device_input_line_edit_with_kwargs.config.gui_id == "test_gui_id"
|
166
166
|
assert device_input_line_edit_with_kwargs.config.device_filter == "FakePositioner"
|
167
|
-
assert device_input_line_edit_with_kwargs.config.
|
167
|
+
assert device_input_line_edit_with_kwargs.config.default == "samx"
|
168
168
|
assert device_input_line_edit_with_kwargs.config.arg_name == "test_arg_name"
|
169
169
|
|
170
170
|
|