bec-widgets 0.53.3__py3-none-any.whl → 0.55.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. CHANGELOG.md +24 -26
  2. PKG-INFO +1 -1
  3. bec_widgets/cli/client.py +265 -13
  4. bec_widgets/cli/client_utils.py +0 -3
  5. bec_widgets/cli/generate_cli.py +10 -5
  6. bec_widgets/cli/rpc_wigdet_handler.py +2 -1
  7. bec_widgets/cli/server.py +5 -7
  8. bec_widgets/examples/jupyter_console/jupyter_console_window.py +11 -5
  9. bec_widgets/examples/motor_movement/motor_control_compilations.py +17 -16
  10. bec_widgets/widgets/__init__.py +1 -10
  11. bec_widgets/widgets/figure/figure.py +40 -23
  12. bec_widgets/widgets/figure/plots/__init__.py +0 -0
  13. bec_widgets/widgets/figure/plots/image/__init__.py +0 -0
  14. bec_widgets/widgets/{plots → figure/plots/image}/image.py +6 -416
  15. bec_widgets/widgets/figure/plots/image/image_item.py +277 -0
  16. bec_widgets/widgets/figure/plots/image/image_processor.py +152 -0
  17. bec_widgets/widgets/figure/plots/motor_map/__init__.py +0 -0
  18. bec_widgets/widgets/{plots → figure/plots/motor_map}/motor_map.py +2 -2
  19. bec_widgets/widgets/figure/plots/waveform/__init__.py +0 -0
  20. bec_widgets/widgets/{plots → figure/plots/waveform}/waveform.py +9 -222
  21. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +227 -0
  22. bec_widgets/widgets/motor_control/__init__.py +0 -7
  23. bec_widgets/widgets/motor_control/motor_control.py +2 -948
  24. bec_widgets/widgets/motor_control/motor_table/__init__.py +0 -0
  25. bec_widgets/widgets/motor_control/motor_table/motor_table.py +483 -0
  26. bec_widgets/widgets/motor_control/movement_absolute/__init__.py +0 -0
  27. bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +157 -0
  28. bec_widgets/widgets/motor_control/movement_relative/__init__.py +0 -0
  29. bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +227 -0
  30. bec_widgets/widgets/motor_control/selection/__init__.py +0 -0
  31. bec_widgets/widgets/motor_control/selection/selection.py +110 -0
  32. bec_widgets/widgets/spiral_progress_bar/__init__.py +1 -0
  33. bec_widgets/widgets/spiral_progress_bar/ring.py +184 -0
  34. bec_widgets/widgets/spiral_progress_bar/spiral_progress_bar.py +594 -0
  35. {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/METADATA +1 -1
  36. {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/RECORD +56 -53
  37. docs/requirements.txt +1 -0
  38. pyproject.toml +1 -1
  39. tests/end-2-end/test_bec_dock_rpc_e2e.py +82 -1
  40. tests/end-2-end/test_bec_figure_rpc_e2e.py +4 -4
  41. tests/end-2-end/test_rpc_register_e2e.py +1 -1
  42. tests/unit_tests/test_bec_dock.py +1 -1
  43. tests/unit_tests/test_bec_figure.py +6 -4
  44. tests/unit_tests/test_bec_motor_map.py +2 -3
  45. tests/unit_tests/test_motor_control.py +6 -5
  46. tests/unit_tests/test_spiral_progress_bar.py +338 -0
  47. tests/unit_tests/test_waveform1d.py +13 -1
  48. bec_widgets/validation/__init__.py +0 -2
  49. bec_widgets/validation/monitor_config_validator.py +0 -258
  50. bec_widgets/widgets/monitor/__init__.py +0 -1
  51. bec_widgets/widgets/monitor/config_dialog.py +0 -574
  52. bec_widgets/widgets/monitor/config_dialog.ui +0 -210
  53. bec_widgets/widgets/monitor/example_configs/config_device.yaml +0 -60
  54. bec_widgets/widgets/monitor/example_configs/config_scans.yaml +0 -92
  55. bec_widgets/widgets/monitor/monitor.py +0 -845
  56. bec_widgets/widgets/monitor/tab_template.ui +0 -180
  57. bec_widgets/widgets/motor_map/__init__.py +0 -1
  58. bec_widgets/widgets/motor_map/motor_map.py +0 -594
  59. bec_widgets/widgets/plots/__init__.py +0 -4
  60. tests/unit_tests/test_bec_monitor.py +0 -220
  61. tests/unit_tests/test_config_dialog.py +0 -178
  62. tests/unit_tests/test_motor_map.py +0 -171
  63. tests/unit_tests/test_validator_errors.py +0 -110
  64. /bec_widgets/{cli → assets}/bec_widgets_icon.png +0 -0
  65. /bec_widgets/{examples/jupyter_console → assets}/terminal_icon.png +0 -0
  66. /bec_widgets/widgets/{plots → figure/plots}/plot_base.py +0 -0
  67. /bec_widgets/widgets/motor_control/{motor_control_table.ui → motor_table/motor_table.ui} +0 -0
  68. /bec_widgets/widgets/motor_control/{motor_control_absolute.ui → movement_absolute/movement_absolute.ui} +0 -0
  69. /bec_widgets/widgets/motor_control/{motor_control_relative.ui → movement_relative/movement_relative.ui} +0 -0
  70. /bec_widgets/widgets/motor_control/{motor_control_selection.ui → selection/selection.ui} +0 -0
  71. {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/WHEEL +0 -0
  72. {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,26 +1,12 @@
1
1
  # pylint: disable = no-name-in-module,missing-module-docstring
2
- import os
3
2
  from enum import Enum
4
3
 
5
4
  from bec_lib.alarm_handler import AlarmBase
6
5
  from bec_lib.device import Positioner
7
- from qtpy import uic
8
- from qtpy.QtCore import Qt, QThread
6
+ from qtpy.QtCore import QThread
9
7
  from qtpy.QtCore import Signal as pyqtSignal
10
8
  from qtpy.QtCore import Slot as pyqtSlot
11
- from qtpy.QtGui import QDoubleValidator, QKeySequence
12
- from qtpy.QtWidgets import (
13
- QCheckBox,
14
- QComboBox,
15
- QDoubleSpinBox,
16
- QLineEdit,
17
- QMessageBox,
18
- QPushButton,
19
- QShortcut,
20
- QTableWidget,
21
- QTableWidgetItem,
22
- QWidget,
23
- )
9
+ from qtpy.QtWidgets import QMessageBox, QWidget
24
10
 
25
11
  from bec_widgets.utils.bec_dispatcher import BECDispatcher
26
12
 
@@ -77,938 +63,6 @@ class MotorControlWidget(QWidget):
77
63
  self._init_ui()
78
64
 
79
65
 
80
- class MotorControlSelection(MotorControlWidget):
81
- """
82
- Widget for selecting the motors to control.
83
-
84
- Signals:
85
- selected_motors_signal (pyqtSignal(str,str)): Signal to emit the selected motors.
86
- Slots:
87
- get_available_motors (pyqtSlot): Slot to populate the available motors in the combo boxes and set the index based on the configuration.
88
- enable_motor_controls (pyqtSlot(bool)): Slot to enable/disable the motor controls GUI.
89
- on_config_update (pyqtSlot(dict)): Slot to update the config dict.
90
- """
91
-
92
- selected_motors_signal = pyqtSignal(str, str)
93
-
94
- def _load_ui(self):
95
- """Load the UI from the .ui file."""
96
- current_path = os.path.dirname(__file__)
97
- uic.loadUi(os.path.join(current_path, "motor_control_selection.ui"), self)
98
-
99
- def _init_ui(self):
100
- """Initialize the UI."""
101
- # Lock GUI while motors are moving
102
- self.motor_thread.lock_gui.connect(self.enable_motor_controls)
103
-
104
- self.pushButton_connecMotors.clicked.connect(self.select_motor)
105
- self.get_available_motors()
106
-
107
- # Connect change signals to change color
108
- self.comboBox_motor_x.currentIndexChanged.connect(
109
- lambda: self.set_combobox_style(self.comboBox_motor_x, "#ffa700")
110
- )
111
- self.comboBox_motor_y.currentIndexChanged.connect(
112
- lambda: self.set_combobox_style(self.comboBox_motor_y, "#ffa700")
113
- )
114
-
115
- @pyqtSlot(dict)
116
- def on_config_update(self, config: dict) -> None:
117
- """
118
- Update config dict
119
- Args:
120
- config(dict): New config dict
121
- """
122
- self.config = config
123
-
124
- # Get motor names
125
- self.motor_x, self.motor_y = (
126
- self.config["motor_control"]["motor_x"],
127
- self.config["motor_control"]["motor_y"],
128
- )
129
-
130
- self._init_ui()
131
-
132
- @pyqtSlot(bool)
133
- def enable_motor_controls(self, enable: bool) -> None:
134
- """
135
- Enable or disable the motor controls.
136
- Args:
137
- enable(bool): True to enable, False to disable.
138
- """
139
- self.motorSelection.setEnabled(enable)
140
-
141
- @pyqtSlot()
142
- def get_available_motors(self) -> None:
143
- """
144
- Slot to populate the available motors in the combo boxes and set the index based on the configuration.
145
- """
146
- # Get all available motors
147
- self.motor_list = self.motor_thread.get_all_motors_names()
148
-
149
- # Populate the combo boxes
150
- self.comboBox_motor_x.addItems(self.motor_list)
151
- self.comboBox_motor_y.addItems(self.motor_list)
152
-
153
- # Set the index based on the config if provided
154
- if self.config:
155
- index_x = self.comboBox_motor_x.findText(self.motor_x)
156
- index_y = self.comboBox_motor_y.findText(self.motor_y)
157
- self.comboBox_motor_x.setCurrentIndex(index_x if index_x != -1 else 0)
158
- self.comboBox_motor_y.setCurrentIndex(index_y if index_y != -1 else 0)
159
-
160
- def set_combobox_style(self, combobox: QComboBox, color: str) -> None:
161
- """
162
- Set the combobox style to a specific color.
163
- Args:
164
- combobox(QComboBox): Combobox to change the color.
165
- color(str): Color to set the combobox to.
166
- """
167
- combobox.setStyleSheet(f"QComboBox {{ background-color: {color}; }}")
168
-
169
- def select_motor(self):
170
- """Emit the selected motors"""
171
- motor_x = self.comboBox_motor_x.currentText()
172
- motor_y = self.comboBox_motor_y.currentText()
173
-
174
- # Reset the combobox color to normal after selection
175
- self.set_combobox_style(self.comboBox_motor_x, "")
176
- self.set_combobox_style(self.comboBox_motor_y, "")
177
-
178
- self.selected_motors_signal.emit(motor_x, motor_y)
179
-
180
-
181
- class MotorControlAbsolute(MotorControlWidget):
182
- """
183
- Widget for controlling the motors to absolute coordinates.
184
-
185
- Signals:
186
- coordinates_signal (pyqtSignal(tuple)): Signal to emit the coordinates.
187
- Slots:
188
- change_motors (pyqtSlot): Slot to change the active motors.
189
- enable_motor_controls (pyqtSlot(bool)): Slot to enable/disable the motor controls.
190
- """
191
-
192
- coordinates_signal = pyqtSignal(tuple)
193
-
194
- def _load_ui(self):
195
- """Load the UI from the .ui file."""
196
- current_path = os.path.dirname(__file__)
197
- uic.loadUi(os.path.join(current_path, "motor_control_absolute.ui"), self)
198
-
199
- def _init_ui(self):
200
- """Initialize the UI."""
201
-
202
- # Check if there are any motors connected
203
- if self.motor_x is None or self.motor_y is None:
204
- self.motorControl_absolute.setEnabled(False)
205
- return
206
-
207
- # Move to absolute coordinates
208
- self.pushButton_go_absolute.clicked.connect(
209
- lambda: self.move_motor_absolute(
210
- self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()
211
- )
212
- )
213
-
214
- self.pushButton_set.clicked.connect(self.save_absolute_coordinates)
215
- self.pushButton_save.clicked.connect(self.save_current_coordinates)
216
- self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
217
-
218
- # Enable/Disable GUI
219
- self.motor_thread.lock_gui.connect(self.enable_motor_controls)
220
-
221
- # Error messages
222
- self.motor_thread.motor_error.connect(
223
- lambda error: MotorControlErrors.display_error_message(error)
224
- )
225
-
226
- # Keyboard shortcuts
227
- self._init_keyboard_shortcuts()
228
-
229
- @pyqtSlot(dict)
230
- def on_config_update(self, config: dict) -> None:
231
- """Update config dict"""
232
- self.config = config
233
-
234
- # Get motor names
235
- self.motor_x, self.motor_y = (
236
- self.config["motor_control"]["motor_x"],
237
- self.config["motor_control"]["motor_y"],
238
- )
239
-
240
- # Update step precision
241
- self.precision = self.config["motor_control"]["precision"]
242
-
243
- self._init_ui()
244
-
245
- @pyqtSlot(bool)
246
- def enable_motor_controls(self, enable: bool) -> None:
247
- """
248
- Enable or disable the motor controls.
249
- Args:
250
- enable(bool): True to enable, False to disable.
251
- """
252
-
253
- # Disable or enable all controls within the motorControl_absolute group box
254
- for widget in self.motorControl_absolute.findChildren(QWidget):
255
- widget.setEnabled(enable)
256
-
257
- # Enable the pushButton_stop if the motor is moving
258
- self.pushButton_stop.setEnabled(True)
259
-
260
- @pyqtSlot(str, str)
261
- def change_motors(self, motor_x: str, motor_y: str):
262
- """
263
- Change the active motors and update config.
264
- Can be connected to the selected_motors_signal from MotorControlSelection.
265
- Args:
266
- motor_x(str): New motor X to be controlled.
267
- motor_y(str): New motor Y to be controlled.
268
- """
269
- self.motor_x = motor_x
270
- self.motor_y = motor_y
271
- self.config["motor_control"]["motor_x"] = motor_x
272
- self.config["motor_control"]["motor_y"] = motor_y
273
-
274
- @pyqtSlot(int)
275
- def set_precision(self, precision: int) -> None:
276
- """
277
- Set the precision of the coordinates.
278
- Args:
279
- precision(int): Precision of the coordinates.
280
- """
281
- self.precision = precision
282
- self.config["motor_control"]["precision"] = precision
283
- self.spinBox_absolute_x.setDecimals(precision)
284
- self.spinBox_absolute_y.setDecimals(precision)
285
-
286
- def move_motor_absolute(self, x: float, y: float) -> None:
287
- """
288
- Move the motor to the target coordinates.
289
- Args:
290
- x(float): Target x coordinate.
291
- y(float): Target y coordinate.
292
- """
293
- # self._enable_motor_controls(False)
294
- target_coordinates = (x, y)
295
- self.motor_thread.move_absolute(self.motor_x, self.motor_y, target_coordinates)
296
- if self.checkBox_save_with_go.isChecked():
297
- self.save_absolute_coordinates()
298
-
299
- def _init_keyboard_shortcuts(self):
300
- """Initialize the keyboard shortcuts."""
301
- # Go absolute button
302
- self.pushButton_go_absolute.setShortcut("Ctrl+G")
303
- self.pushButton_go_absolute.setToolTip("Ctrl+G")
304
-
305
- # Set absolute coordinates
306
- self.pushButton_set.setShortcut("Ctrl+D")
307
- self.pushButton_set.setToolTip("Ctrl+D")
308
-
309
- # Save Current coordinates
310
- self.pushButton_save.setShortcut("Ctrl+S")
311
- self.pushButton_save.setToolTip("Ctrl+S")
312
-
313
- # Stop Button
314
- self.pushButton_stop.setShortcut("Ctrl+X")
315
- self.pushButton_stop.setToolTip("Ctrl+X")
316
-
317
- def save_absolute_coordinates(self):
318
- """Emit the setup coordinates from the spinboxes"""
319
-
320
- x, y = round(self.spinBox_absolute_x.value(), self.precision), round(
321
- self.spinBox_absolute_y.value(), self.precision
322
- )
323
- self.coordinates_signal.emit((x, y))
324
-
325
- def save_current_coordinates(self):
326
- """Emit the current coordinates from the motor thread"""
327
- x, y = self.motor_thread.get_coordinates(self.motor_x, self.motor_y)
328
- self.coordinates_signal.emit((round(x, self.precision), round(y, self.precision)))
329
-
330
-
331
- class MotorControlRelative(MotorControlWidget):
332
- """
333
- Widget for controlling the motors to relative coordinates.
334
-
335
- Signals:
336
- precision_signal (pyqtSignal): Signal to emit the precision of the coordinates.
337
- Slots:
338
- change_motors (pyqtSlot(str,str)): Slot to change the active motors.
339
- enable_motor_controls (pyqtSlot): Slot to enable/disable the motor controls.
340
- """
341
-
342
- precision_signal = pyqtSignal(int)
343
-
344
- def _load_ui(self):
345
- """Load the UI from the .ui file."""
346
- # Loading UI
347
- current_path = os.path.dirname(__file__)
348
- uic.loadUi(os.path.join(current_path, "motor_control_relative.ui"), self)
349
-
350
- def _init_ui(self):
351
- """Initialize the UI."""
352
- self._init_ui_motor_control()
353
- self._init_keyboard_shortcuts()
354
-
355
- @pyqtSlot(dict)
356
- def on_config_update(self, config: dict) -> None:
357
- """
358
- Update config dict
359
- Args:
360
- config(dict): New config dict
361
- """
362
- self.config = config
363
-
364
- # Get motor names
365
- self.motor_x, self.motor_y = (
366
- self.config["motor_control"]["motor_x"],
367
- self.config["motor_control"]["motor_y"],
368
- )
369
-
370
- # Update step precision
371
- self.precision = self.config["motor_control"]["precision"]
372
- self.spinBox_precision.setValue(self.precision)
373
-
374
- # Update step sizes
375
- self.spinBox_step_x.setValue(self.config["motor_control"]["step_size_x"])
376
- self.spinBox_step_y.setValue(self.config["motor_control"]["step_size_y"])
377
-
378
- # Checkboxes for keyboard shortcuts and x/y step size link
379
- self.checkBox_same_xy.setChecked(self.config["motor_control"]["step_x_y_same"])
380
- self.checkBox_enableArrows.setChecked(self.config["motor_control"]["move_with_arrows"])
381
-
382
- self._init_ui()
383
-
384
- def _init_ui_motor_control(self) -> None:
385
- """Initialize the motor control elements"""
386
-
387
- # Connect checkbox and spinBoxes
388
- self.checkBox_same_xy.stateChanged.connect(self._sync_step_sizes)
389
- self.spinBox_step_x.valueChanged.connect(self._update_step_size_x)
390
- self.spinBox_step_y.valueChanged.connect(self._update_step_size_y)
391
-
392
- self.toolButton_right.clicked.connect(
393
- lambda: self.move_motor_relative(self.motor_x, "x", 1)
394
- )
395
- self.toolButton_left.clicked.connect(
396
- lambda: self.move_motor_relative(self.motor_x, "x", -1)
397
- )
398
- self.toolButton_up.clicked.connect(lambda: self.move_motor_relative(self.motor_y, "y", 1))
399
- self.toolButton_down.clicked.connect(
400
- lambda: self.move_motor_relative(self.motor_y, "y", -1)
401
- )
402
-
403
- # Switch between key shortcuts active
404
- self.checkBox_enableArrows.stateChanged.connect(self._update_arrow_key_shortcuts)
405
- self._update_arrow_key_shortcuts()
406
-
407
- # Enable/Disable GUI
408
- self.motor_thread.lock_gui.connect(self.enable_motor_controls)
409
-
410
- # Precision update
411
- self.spinBox_precision.valueChanged.connect(lambda x: self._update_precision(x))
412
-
413
- # Error messages
414
- self.motor_thread.motor_error.connect(
415
- lambda error: MotorControlErrors.display_error_message(error)
416
- )
417
-
418
- # Stop Button
419
- self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
420
-
421
- def _init_keyboard_shortcuts(self) -> None:
422
- """Initialize the keyboard shortcuts"""
423
-
424
- # Increase/decrease step size for X motor
425
- increase_x_shortcut = QShortcut(QKeySequence("Ctrl+A"), self)
426
- decrease_x_shortcut = QShortcut(QKeySequence("Ctrl+Z"), self)
427
- increase_x_shortcut.activated.connect(
428
- lambda: self._change_step_size(self.spinBox_step_x, 2)
429
- )
430
- decrease_x_shortcut.activated.connect(
431
- lambda: self._change_step_size(self.spinBox_step_x, 0.5)
432
- )
433
- self.spinBox_step_x.setToolTip("Increase step size: Ctrl+A\nDecrease step size: Ctrl+Z")
434
-
435
- # Increase/decrease step size for Y motor
436
- increase_y_shortcut = QShortcut(QKeySequence("Alt+A"), self)
437
- decrease_y_shortcut = QShortcut(QKeySequence("Alt+Z"), self)
438
- increase_y_shortcut.activated.connect(
439
- lambda: self._change_step_size(self.spinBox_step_y, 2)
440
- )
441
- decrease_y_shortcut.activated.connect(
442
- lambda: self._change_step_size(self.spinBox_step_y, 0.5)
443
- )
444
- self.spinBox_step_y.setToolTip("Increase step size: Alt+A\nDecrease step size: Alt+Z")
445
-
446
- # Stop Button
447
- self.pushButton_stop.setShortcut("Ctrl+X")
448
- self.pushButton_stop.setToolTip("Ctrl+X")
449
-
450
- def _update_arrow_key_shortcuts(self) -> None:
451
- """Update the arrow key shortcuts based on the checkbox state."""
452
- if self.checkBox_enableArrows.isChecked():
453
- # Set the arrow key shortcuts for motor movement
454
- self.toolButton_right.setShortcut(Qt.Key_Right)
455
- self.toolButton_left.setShortcut(Qt.Key_Left)
456
- self.toolButton_up.setShortcut(Qt.Key_Up)
457
- self.toolButton_down.setShortcut(Qt.Key_Down)
458
- else:
459
- # Clear the shortcuts
460
- self.toolButton_right.setShortcut("")
461
- self.toolButton_left.setShortcut("")
462
- self.toolButton_up.setShortcut("")
463
- self.toolButton_down.setShortcut("")
464
-
465
- def _update_precision(self, precision: int) -> None:
466
- """
467
- Update the precision of the coordinates.
468
- Args:
469
- precision(int): Precision of the coordinates.
470
- """
471
- self.spinBox_step_x.setDecimals(precision)
472
- self.spinBox_step_y.setDecimals(precision)
473
- self.precision_signal.emit(precision)
474
-
475
- def _change_step_size(self, spinBox: QDoubleSpinBox, factor: float) -> None:
476
- """
477
- Change the step size of the spinbox.
478
- Args:
479
- spinBox(QDoubleSpinBox): Spinbox to change the step size.
480
- factor(float): Factor to change the step size.
481
- """
482
- old_step = spinBox.value()
483
- new_step = old_step * factor
484
- spinBox.setValue(new_step)
485
-
486
- def _sync_step_sizes(self):
487
- """Sync step sizes based on checkbox state."""
488
- if self.checkBox_same_xy.isChecked():
489
- value = self.spinBox_step_x.value()
490
- self.spinBox_step_y.setValue(value)
491
-
492
- def _update_step_size_x(self):
493
- """Update step size for x if checkbox is checked."""
494
- if self.checkBox_same_xy.isChecked():
495
- value = self.spinBox_step_x.value()
496
- self.spinBox_step_y.setValue(value)
497
-
498
- def _update_step_size_y(self):
499
- """Update step size for y if checkbox is checked."""
500
- if self.checkBox_same_xy.isChecked():
501
- value = self.spinBox_step_y.value()
502
- self.spinBox_step_x.setValue(value)
503
-
504
- @pyqtSlot(str, str)
505
- def change_motors(self, motor_x: str, motor_y: str):
506
- """
507
- Change the active motors and update config.
508
- Can be connected to the selected_motors_signal from MotorControlSelection.
509
- Args:
510
- motor_x(str): New motor X to be controlled.
511
- motor_y(str): New motor Y to be controlled.
512
- """
513
- self.motor_x = motor_x
514
- self.motor_y = motor_y
515
- self.config["motor_control"]["motor_x"] = motor_x
516
- self.config["motor_control"]["motor_y"] = motor_y
517
-
518
- @pyqtSlot(bool)
519
- def enable_motor_controls(self, disable: bool) -> None:
520
- """
521
- Enable or disable the motor controls.
522
- Args:
523
- disable(bool): True to disable, False to enable.
524
- """
525
-
526
- # Disable or enable all controls within the motorControl_absolute group box
527
- for widget in self.motorControl.findChildren(QWidget):
528
- widget.setEnabled(disable)
529
-
530
- # Enable the pushButton_stop if the motor is moving
531
- self.pushButton_stop.setEnabled(True)
532
-
533
- def move_motor_relative(self, motor, axis: str, direction: int) -> None:
534
- """
535
- Move the motor relative to the current position.
536
- Args:
537
- motor: Motor to move.
538
- axis(str): Axis to move.
539
- direction(int): Direction to move. 1 for positive, -1 for negative.
540
- """
541
- if axis == "x":
542
- step = direction * self.spinBox_step_x.value()
543
- elif axis == "y":
544
- step = direction * self.spinBox_step_y.value()
545
- self.motor_thread.move_relative(motor, step)
546
-
547
-
548
- class MotorCoordinateTable(MotorControlWidget):
549
- """
550
- Widget to save coordinates from motor, display them in the table and move back to them.
551
- There are two modes of operation:
552
- - Individual: Each row is a single coordinate.
553
- - Start/Stop: Each pair of rows is a start and end coordinate.
554
- Signals:
555
- plot_coordinates_signal (pyqtSignal(list, str, str)): Signal to plot the coordinates in the MotorMap.
556
- Slots:
557
- add_coordinate (pyqtSlot(tuple)): Slot to add a coordinate to the table.
558
- mode_switch (pyqtSlot): Slot to switch between individual and start/stop mode.
559
- """
560
-
561
- plot_coordinates_signal = pyqtSignal(list, str, str)
562
-
563
- def _load_ui(self):
564
- """Load the UI for the coordinate table."""
565
- current_path = os.path.dirname(__file__)
566
- uic.loadUi(os.path.join(current_path, "motor_control_table.ui"), self)
567
-
568
- def _init_ui(self):
569
- """Initialize the UI"""
570
- # Setup table behaviour
571
- self._setup_table()
572
- self.table.setSelectionBehavior(QTableWidget.SelectRows)
573
-
574
- # for tag columns default tag
575
- self.tag_counter = 1
576
-
577
- # Connect signals and slots
578
- self.checkBox_resize_auto.stateChanged.connect(self.resize_table_auto)
579
- self.comboBox_mode.currentIndexChanged.connect(self.mode_switch)
580
-
581
- # Keyboard shortcuts for deleting a row
582
- self.delete_shortcut = QShortcut(QKeySequence(Qt.Key_Delete), self.table)
583
- self.delete_shortcut.activated.connect(self.delete_selected_row)
584
- self.backspace_shortcut = QShortcut(QKeySequence(Qt.Key_Backspace), self.table)
585
- self.backspace_shortcut.activated.connect(self.delete_selected_row)
586
-
587
- # Warning message for mode switch enable/disable
588
- self.warning_message = True
589
-
590
- @pyqtSlot(dict)
591
- def on_config_update(self, config: dict) -> None:
592
- """
593
- Update config dict
594
- Args:
595
- config(dict): New config dict
596
- """
597
- self.config = config
598
-
599
- # Get motor names
600
- self.motor_x, self.motor_y = (
601
- self.config["motor_control"]["motor_x"],
602
- self.config["motor_control"]["motor_y"],
603
- )
604
-
605
- # Decimal precision of the table coordinates
606
- self.precision = self.config["motor_control"].get("precision", 3)
607
-
608
- # Mode switch default option
609
- self.mode = self.config["motor_control"].get("mode", "Individual")
610
-
611
- # Set combobox to default mode
612
- self.comboBox_mode.setCurrentText(self.mode)
613
-
614
- self._init_ui()
615
-
616
- def _setup_table(self):
617
- """Setup the table with appropriate headers and configurations."""
618
- mode = self.comboBox_mode.currentText()
619
-
620
- if mode == "Individual":
621
- self._setup_individual_mode()
622
- elif mode == "Start/Stop":
623
- self._setup_start_stop_mode()
624
- self.start_stop_counter = 0 # TODO: remove this??
625
-
626
- self.wipe_motor_map_coordinates()
627
-
628
- def _setup_individual_mode(self):
629
- """Setup the table for individual mode."""
630
- self.table.setColumnCount(5)
631
- self.table.setHorizontalHeaderLabels(["Show", "Move", "Tag", "X", "Y"])
632
- self.table.verticalHeader().setVisible(False)
633
-
634
- def _setup_start_stop_mode(self):
635
- """Setup the table for start/stop mode."""
636
- self.table.setColumnCount(8)
637
- self.table.setHorizontalHeaderLabels(
638
- [
639
- "Show",
640
- "Move [start]",
641
- "Move [end]",
642
- "Tag",
643
- "X [start]",
644
- "Y [start]",
645
- "X [end]",
646
- "Y [end]",
647
- ]
648
- )
649
- self.table.verticalHeader().setVisible(False)
650
- # Set flag to track if the coordinate is stat or the end of the entry
651
- self.is_next_entry_end = False
652
-
653
- def mode_switch(self):
654
- """Switch between individual and start/stop mode."""
655
- last_selected_index = self.comboBox_mode.currentIndex()
656
-
657
- if self.table.rowCount() > 0 and self.warning_message is True:
658
- msgBox = QMessageBox()
659
- msgBox.setIcon(QMessageBox.Critical)
660
- msgBox.setText(
661
- "Switching modes will delete all table entries. Do you want to continue?"
662
- )
663
- msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
664
- returnValue = msgBox.exec()
665
-
666
- if returnValue is QMessageBox.Cancel:
667
- self.comboBox_mode.blockSignals(True) # Block signals
668
- self.comboBox_mode.setCurrentIndex(last_selected_index)
669
- self.comboBox_mode.blockSignals(False) # Unblock signals
670
- return
671
-
672
- # Wipe table
673
- self.wipe_motor_map_coordinates()
674
-
675
- # Initiate new table with new mode
676
- self._setup_table()
677
-
678
- @pyqtSlot(tuple)
679
- def add_coordinate(self, coordinates: tuple):
680
- """
681
- Add a coordinate to the table.
682
- Args:
683
- coordinates(tuple): Coordinates (x,y) to add to the table.
684
- """
685
- tag = f"Pos {self.tag_counter}"
686
- self.tag_counter += 1
687
- x, y = coordinates
688
- self._add_row(tag, x, y)
689
-
690
- def _add_row(self, tag: str, x: float, y: float) -> None:
691
- """
692
- Add a row to the table.
693
- Args:
694
- tag(str): Tag of the coordinate.
695
- x(float): X coordinate.
696
- y(float): Y coordinate.
697
- """
698
-
699
- mode = self.comboBox_mode.currentText()
700
- if mode == "Individual":
701
- checkbox_pos = 0
702
- button_pos = 1
703
- tag_pos = 2
704
- x_pos = 3
705
- y_pos = 4
706
- coordinate_reference = "Individual"
707
- color = "green"
708
-
709
- # Add new row -> new entry
710
- row_count = self.table.rowCount()
711
- self.table.insertRow(row_count)
712
-
713
- # Add Widgets
714
- self._add_widgets(
715
- tag,
716
- x,
717
- y,
718
- row_count,
719
- checkbox_pos,
720
- tag_pos,
721
- button_pos,
722
- x_pos,
723
- y_pos,
724
- coordinate_reference,
725
- color,
726
- )
727
-
728
- if mode == "Start/Stop":
729
- # These positions are always fixed
730
- checkbox_pos = 0
731
- tag_pos = 3
732
-
733
- if self.is_next_entry_end is False: # It is the start position of the entry
734
- print("Start position")
735
- button_pos = 1
736
- x_pos = 4
737
- y_pos = 5
738
- coordinate_reference = "Start"
739
- color = "blue"
740
-
741
- # Add new row -> new entry
742
- row_count = self.table.rowCount()
743
- self.table.insertRow(row_count)
744
-
745
- # Add Widgets
746
- self._add_widgets(
747
- tag,
748
- x,
749
- y,
750
- row_count,
751
- checkbox_pos,
752
- tag_pos,
753
- button_pos,
754
- x_pos,
755
- y_pos,
756
- coordinate_reference,
757
- color,
758
- )
759
-
760
- # Next entry will be the end of the current entry
761
- self.is_next_entry_end = True
762
-
763
- elif self.is_next_entry_end is True: # It is the end position of the entry
764
- print("End position")
765
- row_count = self.table.rowCount() - 1 # Current row
766
- button_pos = 2
767
- x_pos = 6
768
- y_pos = 7
769
- coordinate_reference = "Stop"
770
- color = "red"
771
-
772
- # Add Widgets
773
- self._add_widgets(
774
- tag,
775
- x,
776
- y,
777
- row_count,
778
- checkbox_pos,
779
- tag_pos,
780
- button_pos,
781
- x_pos,
782
- y_pos,
783
- coordinate_reference,
784
- color,
785
- )
786
- self.is_next_entry_end = False # Next entry will be the start of the new entry
787
-
788
- # Auto table resize
789
- self.resize_table_auto()
790
-
791
- def _add_widgets(
792
- self,
793
- tag: str,
794
- x: float,
795
- y: float,
796
- row: int,
797
- checkBox_pos: int,
798
- tag_pos: int,
799
- button_pos: int,
800
- x_pos: int,
801
- y_pos: int,
802
- coordinate_reference: str,
803
- color: str,
804
- ) -> None:
805
- """
806
- Add widgets to the table.
807
- Args:
808
- tag(str): Tag of the coordinate.
809
- x(float): X coordinate.
810
- y(float): Y coordinate.
811
- row(int): Row of the QTableWidget where to add the widgets.
812
- checkBox_pos(int): Column where to put CheckBox.
813
- tag_pos(int): Column where to put Tag.
814
- button_pos(int): Column where to put Move button.
815
- x_pos(int): Column where to link x coordinate.
816
- y_pos(int): Column where to link y coordinate.
817
- coordinate_reference(str): Reference to the coordinate for MotorMap.
818
- color(str): Color of the coordinate for MotorMap.
819
- """
820
- # Add widgets
821
- self._add_checkbox(row, checkBox_pos, x_pos, y_pos)
822
- self._add_move_button(row, button_pos, x_pos, y_pos)
823
- self.table.setItem(row, tag_pos, QTableWidgetItem(tag))
824
- self._add_line_edit(x, row, x_pos, x_pos, y_pos, coordinate_reference, color)
825
- self._add_line_edit(y, row, y_pos, x_pos, y_pos, coordinate_reference, color)
826
-
827
- # # Emit the coordinates to be plotted
828
- self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color)
829
-
830
- # Connect item edit to emit coordinates
831
- self.table.itemChanged.connect(
832
- lambda: print(f"item changed from {coordinate_reference} slot \n {x}-{y}-{color}")
833
- )
834
- self.table.itemChanged.connect(
835
- lambda: self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color)
836
- )
837
-
838
- def _add_checkbox(self, row: int, checkBox_pos: int, x_pos: int, y_pos: int):
839
- """
840
- Add a checkbox to the table.
841
- Args:
842
- row(int): Row of QTableWidget where to add the checkbox.
843
- checkBox_pos(int): Column where to put CheckBox.
844
- x_pos(int): Column where to link x coordinate.
845
- y_pos(int): Column where to link y coordinate.
846
- """
847
- show_checkbox = QCheckBox()
848
- show_checkbox.setChecked(True)
849
- show_checkbox.stateChanged.connect(lambda: self.emit_plot_coordinates(x_pos, y_pos))
850
- self.table.setCellWidget(row, checkBox_pos, show_checkbox)
851
-
852
- def _add_move_button(self, row: int, button_pos: int, x_pos: int, y_pos: int) -> None:
853
- """
854
- Add a move button to the table.
855
- Args:
856
- row(int): Row of QTableWidget where to add the move button.
857
- button_pos(int): Column where to put move button.
858
- x_pos(int): Column where to link x coordinate.
859
- y_pos(int): Column where to link y coordinate.
860
- """
861
- move_button = QPushButton("Move")
862
- move_button.clicked.connect(lambda: self.handle_move_button_click(x_pos, y_pos))
863
- self.table.setCellWidget(row, button_pos, move_button)
864
-
865
- def _add_line_edit(
866
- self,
867
- value: float,
868
- row: int,
869
- line_pos: int,
870
- x_pos: int,
871
- y_pos: int,
872
- coordinate_reference: str,
873
- color: str,
874
- ) -> None:
875
- """
876
- Add a QLineEdit to the table.
877
- Args:
878
- value(float): Initial value of the QLineEdit.
879
- row(int): Row of QTableWidget where to add the QLineEdit.
880
- line_pos(int): Column where to put QLineEdit.
881
- x_pos(int): Column where to link x coordinate.
882
- y_pos(int): Column where to link y coordinate.
883
- coordinate_reference(str): Reference to the coordinate for MotorMap.
884
- color(str): Color of the coordinate for MotorMap.
885
- """
886
- # Adding validator
887
- validator = QDoubleValidator()
888
- validator.setDecimals(self.precision)
889
-
890
- # Create line edit
891
- edit = QLineEdit(str(f"{value:.{self.precision}f}"))
892
- edit.setValidator(validator)
893
- edit.setAlignment(Qt.AlignmentFlag.AlignCenter)
894
-
895
- # Add line edit to the table
896
- self.table.setCellWidget(row, line_pos, edit)
897
- edit.textChanged.connect(
898
- lambda: self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color)
899
- )
900
-
901
- def wipe_motor_map_coordinates(self):
902
- """Wipe the motor map coordinates."""
903
- try:
904
- self.table.itemChanged.disconnect() # Disconnect all previous connections
905
- except TypeError:
906
- print("No previous connections to disconnect")
907
- self.table.setRowCount(0)
908
- reference_tags = ["Individual", "Start", "Stop"]
909
- for reference_tag in reference_tags:
910
- self.plot_coordinates_signal.emit([], reference_tag, "green")
911
-
912
- def handle_move_button_click(self, x_pos: int, y_pos: int) -> None:
913
- """
914
- Handle the move button click.
915
- Args:
916
- x_pos(int): X position of the coordinate.
917
- y_pos(int): Y position of the coordinate.
918
- """
919
- button = self.sender()
920
- row = self.table.indexAt(button.pos()).row()
921
-
922
- x = self.get_coordinate(row, x_pos)
923
- y = self.get_coordinate(row, y_pos)
924
- self.move_motor(x, y)
925
-
926
- def emit_plot_coordinates(self, x_pos: float, y_pos: float, reference_tag: str, color: str):
927
- """
928
- Emit the coordinates to be plotted.
929
- Args:
930
- x_pos(float): X position of the coordinate.
931
- y_pos(float): Y position of the coordinate.
932
- reference_tag(str): Reference tag of the coordinate.
933
- color(str): Color of the coordinate.
934
- """
935
- print(
936
- f"Emitting plot coordinates: x_pos={x_pos}, y_pos={y_pos}, reference_tag={reference_tag}, color={color}"
937
- )
938
- coordinates = []
939
- for row in range(self.table.rowCount()):
940
- show = self.table.cellWidget(row, 0).isChecked()
941
- x = self.get_coordinate(row, x_pos)
942
- y = self.get_coordinate(row, y_pos)
943
-
944
- coordinates.append((x, y, show)) # (x, y, show_flag)
945
- self.plot_coordinates_signal.emit(coordinates, reference_tag, color)
946
-
947
- def get_coordinate(self, row: int, column: int) -> float:
948
- """
949
- Helper function to get the coordinate from the table QLineEdit cells.
950
- Args:
951
- row(int): Row of the table.
952
- column(int): Column of the table.
953
- Returns:
954
- float: Value of the coordinate.
955
- """
956
- edit = self.table.cellWidget(row, column)
957
- value = float(edit.text()) if edit and edit.text() != "" else None
958
- if value:
959
- return value
960
-
961
- def delete_selected_row(self):
962
- """Delete the selected row from the table."""
963
- selected_rows = self.table.selectionModel().selectedRows()
964
- for row in selected_rows:
965
- self.table.removeRow(row.row())
966
- if self.comboBox_mode.currentText() == "Start/Stop":
967
- self.emit_plot_coordinates(x_pos=4, y_pos=5, reference_tag="Start", color="blue")
968
- self.emit_plot_coordinates(x_pos=6, y_pos=7, reference_tag="Stop", color="red")
969
- self.is_next_entry_end = False
970
- elif self.comboBox_mode.currentText() == "Individual":
971
- self.emit_plot_coordinates(x_pos=3, y_pos=4, reference_tag="Individual", color="green")
972
-
973
- def resize_table_auto(self):
974
- """Resize the table to fit the contents."""
975
- if self.checkBox_resize_auto.isChecked():
976
- self.table.resizeColumnsToContents()
977
-
978
- def move_motor(self, x: float, y: float) -> None:
979
- """
980
- Move the motor to the target coordinates.
981
- Args:
982
- x(float): Target x coordinate.
983
- y(float): Target y coordinate.
984
- """
985
- self.motor_thread.move_absolute(self.motor_x, self.motor_y, (x, y))
986
-
987
- @pyqtSlot(str, str)
988
- def change_motors(self, motor_x: str, motor_y: str) -> None:
989
- """
990
- Change the active motors and update config.
991
- Can be connected to the selected_motors_signal from MotorControlSelection.
992
- Args:
993
- motor_x(str): New motor X to be controlled.
994
- motor_y(str): New motor Y to be controlled.
995
- """
996
- self.motor_x = motor_x
997
- self.motor_y = motor_y
998
- self.config["motor_control"]["motor_x"] = motor_x
999
- self.config["motor_control"]["motor_y"] = motor_y
1000
-
1001
- @pyqtSlot(int)
1002
- def set_precision(self, precision: int) -> None:
1003
- """
1004
- Set the precision of the coordinates.
1005
- Args:
1006
- precision(int): Precision of the coordinates.
1007
- """
1008
- self.precision = precision
1009
- self.config["motor_control"]["precision"] = precision
1010
-
1011
-
1012
66
  class MotorControlErrors:
1013
67
  """Class for displaying formatted error messages."""
1014
68