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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 0.70.0
3
+ Version: 0.71.0
4
4
  Summary: BEC Widgets
5
5
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
6
6
  Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
@@ -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=6oy9Q1sEpauu8fxOvqkRcOD4rfjav0_yz6O9LJtNdeo,7057
5
+ CHANGELOG.md,sha256=G8kiuKBLYo8mE65R9TMUCjp2wL0-b_Qmf1jfmVq6xfw,7407
6
6
  LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
7
- PKG-INFO,sha256=_ms-ExvgDL4QW0BhYZRxe6u3FemzKVPW05H22vFUgMo,1302
7
+ PKG-INFO,sha256=yJmWivH4mEeBkmuE4Qt0Tba4AEFW8OJIXt3QQxnUP8A,1302
8
8
  README.md,sha256=y4jB6wvArS7N8_iTbKWnSM_oRAqLA2GqgzUR-FMh5sU,2645
9
- pyproject.toml,sha256=nosm73wZVCwou86NLT-6_lqc0iSTx09_FUvSoSqWqVQ,2215
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=DNsCueEdVwW0MWjBIIg-vhTu_p64qr0QurT7mHM79is,58074
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=f36198CvT_EzWQ_cg2G-4tRRsaMdJ3yVqsZWKJCQEfA,10880
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=ew3G1WXX0srCOQ2uRSjYoK5zB-NBZ9EwwhO-rmR3K4M,3803
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=i4ube17LiqfhgMHKF4BwKoC8L7KgO4_kot-wb0zjQD4,2661
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=-haiyXSWBULxUBo4gbpfkFYHOBYQnR9ItnATXKvf0Yc,3021
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=B5n2U2iVtTCY3Tx93JyBqzGCDCmWhWwAOhbPelLI-bs,17168
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=HesN6jDY1msbPPFsu3-wTcAmA27y92zxIgzt7tDo7MY,2451
187
- tests/unit_tests/test_device_input_widgets.py,sha256=TOtO4z2c_rBmVlB5KD9JvEkyK-GXn1usbM0f2Scjpgc,5875
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=Xf8bGt8lRJobRwBoqUdVXxsHno8ejvC77FqprhF7Z6I,7564
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.70.0.dist-info/METADATA,sha256=_ms-ExvgDL4QW0BhYZRxe6u3FemzKVPW05H22vFUgMo,1302
209
- bec_widgets-0.70.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
210
- bec_widgets-0.70.0.dist-info/entry_points.txt,sha256=3otEkCdDB9LZJuBLzG4pFLK5Di0CVybN_12IsZrQ-58,166
211
- bec_widgets-0.70.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
212
- bec_widgets-0.70.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.24.2
2
+ Generator: hatchling 1.25.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
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
+ ![ScanControl](./scan_control.gif)
32
+
33
+ ```python
34
+ scan_control = gui.add_dock().add_widget("ScanControl")
35
+ ```
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_widgets"
7
- version = "0.70.0"
7
+ version = "0.71.0"
8
8
  description = "BEC Widgets"
9
9
  requires-python = ">=3.10"
10
10
  classifiers = [
@@ -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.default_device is None
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
- "default_device": "samx",
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.default_device == "samx"
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.default_device == "samx"
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
- "default_device": "samx",
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
- default_device="samx",
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.default_device is None
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.default_device == "samx"
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.default_device == "samx"
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
- "default_device": "samx",
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
- default_device="samx",
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.default_device is None
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.default_device == "samx"
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.default_device == "samx"
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