bec-widgets 0.55.0__py3-none-any.whl → 0.56.1__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.
- .gitlab-ci.yml +113 -8
- CHANGELOG.md +34 -28
- PKG-INFO +3 -1
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +28 -38
- bec_widgets/examples/motor_movement/motor_control_compilations.py +1 -7
- bec_widgets/utils/__init__.py +1 -0
- bec_widgets/utils/crosshair.py +13 -9
- bec_widgets/utils/ui_loader.py +58 -0
- bec_widgets/widgets/motor_control/motor_table/motor_table.py +44 -43
- bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +25 -23
- bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +51 -48
- bec_widgets/widgets/spiral_progress_bar/ring.py +5 -5
- {bec_widgets-0.55.0.dist-info → bec_widgets-0.56.1.dist-info}/METADATA +3 -1
- {bec_widgets-0.55.0.dist-info → bec_widgets-0.56.1.dist-info}/RECORD +22 -43
- docs/user/apps.md +1 -26
- pyproject.toml +2 -1
- tests/end-2-end/test_bec_dock_rpc_e2e.py +1 -1
- tests/unit_tests/test_client_utils.py +2 -2
- tests/unit_tests/test_crosshair.py +5 -5
- tests/unit_tests/test_motor_control.py +49 -45
- bec_widgets/examples/eiger_plot/__init__.py +0 -0
- bec_widgets/examples/eiger_plot/eiger_plot.py +0 -307
- bec_widgets/examples/eiger_plot/eiger_plot.ui +0 -207
- bec_widgets/examples/mca_readout/__init__.py +0 -0
- bec_widgets/examples/mca_readout/mca_plot.py +0 -159
- bec_widgets/examples/mca_readout/mca_sim.py +0 -28
- bec_widgets/examples/modular_app/___init__.py +0 -0
- bec_widgets/examples/modular_app/modular.ui +0 -92
- bec_widgets/examples/modular_app/modular_app.py +0 -197
- bec_widgets/examples/motor_movement/config_example.yaml +0 -17
- bec_widgets/examples/motor_movement/csax_bec_config.yaml +0 -10
- bec_widgets/examples/motor_movement/csaxs_config.yaml +0 -17
- bec_widgets/examples/motor_movement/motor_example.py +0 -1344
- bec_widgets/examples/stream_plot/__init__.py +0 -0
- bec_widgets/examples/stream_plot/line_plot.ui +0 -155
- bec_widgets/examples/stream_plot/stream_plot.py +0 -337
- docs/user/apps/modular_app.md +0 -6
- docs/user/apps/motor_app.md +0 -34
- docs/user/apps/motor_app_10fps.gif +0 -0
- docs/user/apps/plot_app.md +0 -6
- tests/unit_tests/test_eiger_plot.py +0 -115
- tests/unit_tests/test_stream_plot.py +0 -158
- {bec_widgets-0.55.0.dist-info → bec_widgets-0.56.1.dist-info}/WHEEL +0 -0
- {bec_widgets-0.55.0.dist-info → bec_widgets-0.56.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,1344 +0,0 @@
|
|
1
|
-
import csv
|
2
|
-
import os
|
3
|
-
from enum import Enum
|
4
|
-
from functools import partial
|
5
|
-
|
6
|
-
import numpy as np
|
7
|
-
import pyqtgraph as pg
|
8
|
-
from bec_lib import messages
|
9
|
-
from bec_lib.endpoints import MessageEndpoints
|
10
|
-
from pyqtgraph.Qt import QtCore, QtWidgets, uic
|
11
|
-
from qtpy import QtGui
|
12
|
-
from qtpy.QtCore import Qt, QThread
|
13
|
-
from qtpy.QtCore import Signal as pyqtSignal
|
14
|
-
from qtpy.QtCore import Slot as pyqtSlot
|
15
|
-
from qtpy.QtGui import QDoubleValidator, QKeySequence
|
16
|
-
from qtpy.QtWidgets import (
|
17
|
-
QApplication,
|
18
|
-
QDialog,
|
19
|
-
QFileDialog,
|
20
|
-
QFrame,
|
21
|
-
QLabel,
|
22
|
-
QMessageBox,
|
23
|
-
QPushButton,
|
24
|
-
QShortcut,
|
25
|
-
QVBoxLayout,
|
26
|
-
QWidget,
|
27
|
-
)
|
28
|
-
|
29
|
-
from bec_widgets.utils import DoubleValidationDelegate
|
30
|
-
|
31
|
-
# TODO - General features
|
32
|
-
# - put motor status (moving, stopped, etc)
|
33
|
-
# - add mouse interactions with the plot -> click to select coordinates, double click to move?
|
34
|
-
# - adjust right click actions
|
35
|
-
|
36
|
-
|
37
|
-
class MotorApp(QWidget):
|
38
|
-
"""
|
39
|
-
Main class for MotorApp, designed to control motor positions based on a flexible YAML configuration.
|
40
|
-
|
41
|
-
Attributes:
|
42
|
-
coordinates_updated (pyqtSignal): Signal to trigger coordinate updates.
|
43
|
-
selected_motors (dict): Dictionary containing pre-selected motors from the configuration file.
|
44
|
-
plot_motors (dict): Dictionary containing settings for plotting motor positions.
|
45
|
-
|
46
|
-
Args:
|
47
|
-
selected_motors (dict): Dictionary specifying the selected motors.
|
48
|
-
plot_motors (dict): Dictionary specifying settings for plotting motor positions.
|
49
|
-
parent (QWidget, optional): Parent widget.
|
50
|
-
"""
|
51
|
-
|
52
|
-
coordinates_updated = pyqtSignal(float, float)
|
53
|
-
|
54
|
-
def __init__(self, selected_motors: dict = {}, plot_motors: dict = {}, parent=None):
|
55
|
-
super(MotorApp, self).__init__(parent)
|
56
|
-
current_path = os.path.dirname(__file__)
|
57
|
-
uic.loadUi(os.path.join(current_path, "motor_controller.ui"), self)
|
58
|
-
|
59
|
-
# Motor Control Thread
|
60
|
-
self.motor_thread = MotorControl()
|
61
|
-
|
62
|
-
self.motor_x, self.motor_y = None, None
|
63
|
-
self.limit_x, self.limit_y = None, None
|
64
|
-
|
65
|
-
# Coordinates tracking
|
66
|
-
self.motor_positions = np.array([])
|
67
|
-
|
68
|
-
# Config file settings
|
69
|
-
self.max_points = plot_motors.get("max_points", 5000)
|
70
|
-
self.num_dim_points = plot_motors.get("num_dim_points", 100)
|
71
|
-
self.scatter_size = plot_motors.get("scatter_size", 5)
|
72
|
-
self.precision = plot_motors.get("precision", 2)
|
73
|
-
self.extra_columns = plot_motors.get("extra_columns", None)
|
74
|
-
self.mode_lock = plot_motors.get("mode_lock", False)
|
75
|
-
|
76
|
-
# Saved motors from config file
|
77
|
-
self.selected_motors = selected_motors
|
78
|
-
|
79
|
-
# QThread for motor movement + signals
|
80
|
-
self.motor_thread.motors_loaded.connect(self.get_available_motors)
|
81
|
-
self.motor_thread.motors_selected.connect(self.get_selected_motors)
|
82
|
-
self.motor_thread.limits_retrieved.connect(self.update_limits)
|
83
|
-
|
84
|
-
# UI
|
85
|
-
self.init_ui()
|
86
|
-
self.tag_N = 1 # position label for saved coordinates
|
87
|
-
|
88
|
-
# State tracking for entries
|
89
|
-
self.last_selected_index = -1
|
90
|
-
self.is_next_entry_end = False
|
91
|
-
|
92
|
-
# Get all motors available
|
93
|
-
self.motor_thread.retrieve_all_motors() # TODO link to combobox that it always refresh
|
94
|
-
|
95
|
-
def connect_motor(self, motor_x_name: str, motor_y_name: str):
|
96
|
-
"""
|
97
|
-
Connects to the specified motors and initializes the UI for motor control.
|
98
|
-
|
99
|
-
Args:
|
100
|
-
motor_x_name (str): Name of the motor controlling the x-axis.
|
101
|
-
motor_y_name (str): Name of the motor controlling the y-axis.
|
102
|
-
"""
|
103
|
-
self.motor_thread.connect_motors(motor_x_name, motor_y_name)
|
104
|
-
self.motor_thread.retrieve_motor_limits(self.motor_x, self.motor_y)
|
105
|
-
|
106
|
-
# self.init_motor_map()
|
107
|
-
|
108
|
-
self.motorControl.setEnabled(True)
|
109
|
-
self.motorControl_absolute.setEnabled(True)
|
110
|
-
self.tabWidget_tables.setTabEnabled(1, True)
|
111
|
-
|
112
|
-
self.generate_table_coordinate(
|
113
|
-
self.tableWidget_coordinates,
|
114
|
-
self.motor_thread.retrieve_coordinates(),
|
115
|
-
tag=f"{motor_x_name},{motor_y_name}",
|
116
|
-
precision=self.precision,
|
117
|
-
)
|
118
|
-
|
119
|
-
@pyqtSlot(object, object)
|
120
|
-
def get_selected_motors(self, motor_x, motor_y):
|
121
|
-
"""
|
122
|
-
Slot to receive and set the selected motors.
|
123
|
-
|
124
|
-
Args:
|
125
|
-
motor_x (object): The selected motor for the x-axis.
|
126
|
-
motor_y (object): The selected motor for the y-axis.
|
127
|
-
"""
|
128
|
-
self.motor_x, self.motor_y = motor_x, motor_y
|
129
|
-
|
130
|
-
@pyqtSlot(list, list)
|
131
|
-
def get_available_motors(self, motors_x, motors_y):
|
132
|
-
"""
|
133
|
-
Slot to populate the available motors in the combo boxes and set the index based on the configuration.
|
134
|
-
|
135
|
-
Args:
|
136
|
-
motors_x (list): List of available motors for the x-axis.
|
137
|
-
motors_y (list): List of available motors for the y-axis.
|
138
|
-
"""
|
139
|
-
self.comboBox_motor_x.addItems(motors_x)
|
140
|
-
self.comboBox_motor_y.addItems(motors_y)
|
141
|
-
|
142
|
-
# Set index based on the motor names in the configuration, if available
|
143
|
-
selected_motor_x = ""
|
144
|
-
selected_motor_y = ""
|
145
|
-
|
146
|
-
if self.selected_motors:
|
147
|
-
selected_motor_x = self.selected_motors.get("motor_x", "")
|
148
|
-
selected_motor_y = self.selected_motors.get("motor_y", "")
|
149
|
-
|
150
|
-
index_x = self.comboBox_motor_x.findText(selected_motor_x)
|
151
|
-
index_y = self.comboBox_motor_y.findText(selected_motor_y)
|
152
|
-
|
153
|
-
if index_x != -1:
|
154
|
-
self.comboBox_motor_x.setCurrentIndex(index_x)
|
155
|
-
else:
|
156
|
-
print(
|
157
|
-
f"Warning: Motor '{selected_motor_x}' specified in the config file is not available."
|
158
|
-
)
|
159
|
-
self.comboBox_motor_x.setCurrentIndex(0) # Optionally set to first item or any default
|
160
|
-
|
161
|
-
if index_y != -1:
|
162
|
-
self.comboBox_motor_y.setCurrentIndex(index_y)
|
163
|
-
else:
|
164
|
-
print(
|
165
|
-
f"Warning: Motor '{selected_motor_y}' specified in the config file is not available."
|
166
|
-
)
|
167
|
-
self.comboBox_motor_y.setCurrentIndex(0) # Optionally set to first item or any default
|
168
|
-
|
169
|
-
@pyqtSlot(list, list)
|
170
|
-
def update_limits(self, x_limits: list, y_limits: list) -> None:
|
171
|
-
"""
|
172
|
-
Slot to update the limits for x and y motors.
|
173
|
-
|
174
|
-
Args:
|
175
|
-
x_limits (list): List containing the lower and upper limits for the x-axis motor.
|
176
|
-
y_limits (list): List containing the lower and upper limits for the y-axis motor.
|
177
|
-
"""
|
178
|
-
self.limit_x = x_limits
|
179
|
-
self.limit_y = y_limits
|
180
|
-
self.spinBox_x_min.setValue(self.limit_x[0])
|
181
|
-
self.spinBox_x_max.setValue(self.limit_x[1])
|
182
|
-
self.spinBox_y_min.setValue(self.limit_y[0])
|
183
|
-
self.spinBox_y_max.setValue(self.limit_y[1])
|
184
|
-
|
185
|
-
for spinBox in (
|
186
|
-
self.spinBox_x_min,
|
187
|
-
self.spinBox_x_max,
|
188
|
-
self.spinBox_y_min,
|
189
|
-
self.spinBox_y_max,
|
190
|
-
):
|
191
|
-
spinBox.setStyleSheet("")
|
192
|
-
|
193
|
-
# TODO - names can be get from MotorController
|
194
|
-
self.label_Y_max.setText(f"+ ({self.motor_y.name})")
|
195
|
-
self.label_Y_min.setText(f"- ({self.motor_y.name})")
|
196
|
-
self.label_X_max.setText(f"+ ({self.motor_x.name})")
|
197
|
-
self.label_X_min.setText(f"- ({self.motor_x.name})")
|
198
|
-
|
199
|
-
self.init_motor_map() # reinitialize the map with the new limits
|
200
|
-
|
201
|
-
@pyqtSlot()
|
202
|
-
def enable_motor_control(self):
|
203
|
-
self.motorControl.setEnabled(True)
|
204
|
-
|
205
|
-
def enable_motor_controls(self, disable: bool) -> None:
|
206
|
-
self.motorControl.setEnabled(disable)
|
207
|
-
self.motorSelection.setEnabled(disable)
|
208
|
-
|
209
|
-
# Disable or enable all controls within the motorControl_absolute group box
|
210
|
-
for widget in self.motorControl_absolute.findChildren(QtWidgets.QWidget):
|
211
|
-
widget.setEnabled(disable)
|
212
|
-
|
213
|
-
# Enable the pushButton_stop if the motor is moving
|
214
|
-
self.pushButton_stop.setEnabled(True)
|
215
|
-
|
216
|
-
def move_motor_absolute(self, x: float, y: float) -> None:
|
217
|
-
self.enable_motor_controls(False)
|
218
|
-
target_coordinates = (x, y)
|
219
|
-
self.motor_thread.move_to_coordinates(target_coordinates)
|
220
|
-
if self.checkBox_save_with_go.isChecked():
|
221
|
-
self.save_absolute_coordinates()
|
222
|
-
|
223
|
-
def move_motor_relative(self, motor, axis: str, direction: int) -> None:
|
224
|
-
self.enable_motor_controls(False)
|
225
|
-
if axis == "x":
|
226
|
-
step = direction * self.spinBox_step_x.value()
|
227
|
-
elif axis == "y":
|
228
|
-
step = direction * self.spinBox_step_y.value()
|
229
|
-
self.motor_thread.move_relative(motor, step)
|
230
|
-
|
231
|
-
def update_plot_setting(self, max_points, num_dim_points, scatter_size):
|
232
|
-
self.max_points = max_points
|
233
|
-
self.num_dim_points = num_dim_points
|
234
|
-
self.scatter_size = scatter_size
|
235
|
-
|
236
|
-
for spinBox in (
|
237
|
-
self.spinBox_max_points,
|
238
|
-
self.spinBox_num_dim_points,
|
239
|
-
self.spinBox_scatter_size,
|
240
|
-
):
|
241
|
-
spinBox.setStyleSheet("")
|
242
|
-
|
243
|
-
def set_from_config(self) -> None:
|
244
|
-
"""Set the values from the config file to the UI elements"""
|
245
|
-
|
246
|
-
self.spinBox_max_points.setValue(self.max_points)
|
247
|
-
self.spinBox_num_dim_points.setValue(self.num_dim_points)
|
248
|
-
self.spinBox_scatter_size.setValue(self.scatter_size)
|
249
|
-
self.spinBox_precision.setValue(self.precision)
|
250
|
-
self.update_precision(self.precision)
|
251
|
-
|
252
|
-
def init_ui_plot_elements(self) -> None:
|
253
|
-
"""Initialize the plot elements"""
|
254
|
-
self.label_coorditanes = self.glw.addLabel(f"Motor position: (X, Y)", row=0, col=0)
|
255
|
-
self.plot_map = self.glw.addPlot(row=1, col=0)
|
256
|
-
self.limit_map = pg.ImageItem()
|
257
|
-
self.plot_map.addItem(self.limit_map)
|
258
|
-
self.motor_map = pg.ScatterPlotItem(
|
259
|
-
size=self.scatter_size, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 255)
|
260
|
-
)
|
261
|
-
self.motor_map.setZValue(0)
|
262
|
-
|
263
|
-
self.saved_motor_map_start = pg.ScatterPlotItem(
|
264
|
-
size=self.scatter_size, pen=pg.mkPen(None), brush=pg.mkBrush(255, 0, 0, 255)
|
265
|
-
)
|
266
|
-
self.saved_motor_map_end = pg.ScatterPlotItem(
|
267
|
-
size=self.scatter_size, pen=pg.mkPen(None), brush=pg.mkBrush(0, 0, 255, 255)
|
268
|
-
)
|
269
|
-
|
270
|
-
self.saved_motor_map_individual = pg.ScatterPlotItem(
|
271
|
-
size=self.scatter_size, pen=pg.mkPen(None), brush=pg.mkBrush(0, 255, 0, 255)
|
272
|
-
)
|
273
|
-
|
274
|
-
self.saved_motor_map_start.setZValue(1) # for saved motor positions
|
275
|
-
self.saved_motor_map_end.setZValue(1) # for saved motor positions
|
276
|
-
self.saved_motor_map_individual.setZValue(1) # for saved motor positions
|
277
|
-
|
278
|
-
self.plot_map.addItem(self.motor_map)
|
279
|
-
self.plot_map.addItem(self.saved_motor_map_start)
|
280
|
-
self.plot_map.addItem(self.saved_motor_map_end)
|
281
|
-
self.plot_map.addItem(self.saved_motor_map_individual)
|
282
|
-
self.plot_map.showGrid(x=True, y=True)
|
283
|
-
|
284
|
-
def init_ui_motor_control(self) -> None:
|
285
|
-
"""Initialize the motor control elements"""
|
286
|
-
|
287
|
-
# Connect checkbox and spinBoxes
|
288
|
-
self.checkBox_same_xy.stateChanged.connect(self.sync_step_sizes)
|
289
|
-
self.spinBox_step_x.valueChanged.connect(self.update_step_size_x)
|
290
|
-
self.spinBox_step_y.valueChanged.connect(self.update_step_size_y)
|
291
|
-
|
292
|
-
self.toolButton_right.clicked.connect(
|
293
|
-
lambda: self.move_motor_relative(self.motor_x, "x", 1)
|
294
|
-
)
|
295
|
-
self.toolButton_left.clicked.connect(
|
296
|
-
lambda: self.move_motor_relative(self.motor_x, "x", -1)
|
297
|
-
)
|
298
|
-
self.toolButton_up.clicked.connect(lambda: self.move_motor_relative(self.motor_y, "y", 1))
|
299
|
-
self.toolButton_down.clicked.connect(
|
300
|
-
lambda: self.move_motor_relative(self.motor_y, "y", -1)
|
301
|
-
)
|
302
|
-
|
303
|
-
# Switch between key shortcuts active
|
304
|
-
self.checkBox_enableArrows.stateChanged.connect(self.update_arrow_key_shortcuts)
|
305
|
-
self.update_arrow_key_shortcuts()
|
306
|
-
|
307
|
-
# Move to absolute coordinates
|
308
|
-
self.pushButton_go_absolute.clicked.connect(
|
309
|
-
lambda: self.move_motor_absolute(
|
310
|
-
self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()
|
311
|
-
)
|
312
|
-
)
|
313
|
-
|
314
|
-
self.pushButton_set.clicked.connect(self.save_absolute_coordinates)
|
315
|
-
self.pushButton_save.clicked.connect(self.save_current_coordinates)
|
316
|
-
self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
|
317
|
-
|
318
|
-
# Enable/Disable GUI
|
319
|
-
self.motor_thread.move_finished.connect(lambda: self.enable_motor_controls(True))
|
320
|
-
|
321
|
-
# Precision update
|
322
|
-
self.spinBox_precision.valueChanged.connect(lambda x: self.update_precision(x))
|
323
|
-
|
324
|
-
def init_ui_motor_configs(self) -> None:
|
325
|
-
"""Limit and plot spinBoxes"""
|
326
|
-
|
327
|
-
# SpinBoxes change color to yellow before updated, limits are updated with update button
|
328
|
-
self.spinBox_x_min.valueChanged.connect(lambda: self.param_changed(self.spinBox_x_min))
|
329
|
-
self.spinBox_x_max.valueChanged.connect(lambda: self.param_changed(self.spinBox_x_max))
|
330
|
-
self.spinBox_y_min.valueChanged.connect(lambda: self.param_changed(self.spinBox_y_min))
|
331
|
-
self.spinBox_y_max.valueChanged.connect(lambda: self.param_changed(self.spinBox_y_max))
|
332
|
-
|
333
|
-
# SpinBoxes - Max Points and N Dim Points
|
334
|
-
self.spinBox_max_points.valueChanged.connect(
|
335
|
-
lambda: self.param_changed(self.spinBox_max_points)
|
336
|
-
)
|
337
|
-
self.spinBox_num_dim_points.valueChanged.connect(
|
338
|
-
lambda: self.param_changed(self.spinBox_num_dim_points)
|
339
|
-
)
|
340
|
-
self.spinBox_scatter_size.valueChanged.connect(
|
341
|
-
lambda: self.param_changed(self.spinBox_scatter_size)
|
342
|
-
)
|
343
|
-
|
344
|
-
# Limit Update
|
345
|
-
self.pushButton_updateLimits.clicked.connect(
|
346
|
-
lambda: self.update_all_motor_limits(
|
347
|
-
x_limit=[self.spinBox_x_min.value(), self.spinBox_x_max.value()],
|
348
|
-
y_limit=[self.spinBox_y_min.value(), self.spinBox_y_max.value()],
|
349
|
-
)
|
350
|
-
)
|
351
|
-
|
352
|
-
# Plot Update
|
353
|
-
self.pushButton_update_config.clicked.connect(
|
354
|
-
lambda: self.update_plot_setting(
|
355
|
-
max_points=self.spinBox_max_points.value(),
|
356
|
-
num_dim_points=self.spinBox_num_dim_points.value(),
|
357
|
-
scatter_size=self.spinBox_scatter_size.value(),
|
358
|
-
)
|
359
|
-
)
|
360
|
-
|
361
|
-
self.pushButton_enableGUI.clicked.connect(lambda: self.enable_motor_controls(True))
|
362
|
-
|
363
|
-
def init_ui_motor_connections(self) -> None:
|
364
|
-
# Signal from motor thread to update coordinates
|
365
|
-
self.motor_thread.coordinates_updated.connect(
|
366
|
-
lambda x, y: self.update_image_map(round(x, self.precision), round(y, self.precision))
|
367
|
-
)
|
368
|
-
|
369
|
-
# Motor connections button
|
370
|
-
self.pushButton_connecMotors.clicked.connect(
|
371
|
-
lambda: self.connect_motor(
|
372
|
-
self.comboBox_motor_x.currentText(), self.comboBox_motor_y.currentText()
|
373
|
-
)
|
374
|
-
)
|
375
|
-
|
376
|
-
# Check if there are any motors connected
|
377
|
-
if self.motor_x or self.motor_y is None:
|
378
|
-
self.motorControl.setEnabled(False)
|
379
|
-
self.motorControl_absolute.setEnabled(False)
|
380
|
-
self.tabWidget_tables.setTabEnabled(1, False)
|
381
|
-
|
382
|
-
def init_keyboard_shortcuts(self) -> None:
|
383
|
-
"""Initialize the keyboard shortcuts"""
|
384
|
-
|
385
|
-
# Delete table entry
|
386
|
-
delete_shortcut = QShortcut(QKeySequence("Delete"), self)
|
387
|
-
backspace_shortcut = QShortcut(QKeySequence("Backspace"), self)
|
388
|
-
delete_shortcut.activated.connect(self.delete_selected_row)
|
389
|
-
backspace_shortcut.activated.connect(self.delete_selected_row)
|
390
|
-
|
391
|
-
# Increase/decrease step size for X motor
|
392
|
-
increase_x_shortcut = QShortcut(QKeySequence("Ctrl+A"), self)
|
393
|
-
decrease_x_shortcut = QShortcut(QKeySequence("Ctrl+Z"), self)
|
394
|
-
increase_x_shortcut.activated.connect(lambda: self.change_step_size(self.spinBox_step_x, 2))
|
395
|
-
decrease_x_shortcut.activated.connect(
|
396
|
-
lambda: self.change_step_size(self.spinBox_step_x, 0.5)
|
397
|
-
)
|
398
|
-
|
399
|
-
# Increase/decrease step size for Y motor
|
400
|
-
increase_y_shortcut = QShortcut(QKeySequence("Alt+A"), self)
|
401
|
-
decrease_y_shortcut = QShortcut(QKeySequence("Alt+Z"), self)
|
402
|
-
increase_y_shortcut.activated.connect(lambda: self.change_step_size(self.spinBox_step_y, 2))
|
403
|
-
decrease_y_shortcut.activated.connect(
|
404
|
-
lambda: self.change_step_size(self.spinBox_step_y, 0.5)
|
405
|
-
)
|
406
|
-
|
407
|
-
# Go absolute button
|
408
|
-
self.pushButton_go_absolute.setShortcut("Ctrl+G")
|
409
|
-
self.pushButton_go_absolute.setToolTip("Ctrl+G")
|
410
|
-
|
411
|
-
# Set absolute coordinates
|
412
|
-
self.pushButton_set.setShortcut("Ctrl+D")
|
413
|
-
self.pushButton_set.setToolTip("Ctrl+D")
|
414
|
-
|
415
|
-
# Save Current coordinates
|
416
|
-
self.pushButton_save.setShortcut("Ctrl+S")
|
417
|
-
self.pushButton_save.setToolTip("Ctrl+S")
|
418
|
-
|
419
|
-
# Stop Button
|
420
|
-
self.pushButton_stop.setShortcut("Ctrl+X")
|
421
|
-
self.pushButton_stop.setToolTip("Ctrl+X")
|
422
|
-
|
423
|
-
def init_ui_table(self) -> None:
|
424
|
-
"""Initialize the table validators for x and y coordinates and table signals"""
|
425
|
-
|
426
|
-
# Validators
|
427
|
-
self.double_delegate = DoubleValidationDelegate(self.tableWidget_coordinates)
|
428
|
-
|
429
|
-
# Init Default mode
|
430
|
-
self.mode_switch()
|
431
|
-
|
432
|
-
# Buttons
|
433
|
-
self.pushButton_exportCSV.clicked.connect(
|
434
|
-
lambda: self.export_table_to_csv(self.tableWidget_coordinates)
|
435
|
-
)
|
436
|
-
self.pushButton_importCSV.clicked.connect(
|
437
|
-
lambda: self.load_table_from_csv(self.tableWidget_coordinates, precision=self.precision)
|
438
|
-
)
|
439
|
-
self.pushButton_resize_table.clicked.connect(
|
440
|
-
lambda: self.resizeTable(self.tableWidget_coordinates)
|
441
|
-
)
|
442
|
-
self.pushButton_duplicate.clicked.connect(
|
443
|
-
lambda: self.duplicate_last_row(self.tableWidget_coordinates)
|
444
|
-
)
|
445
|
-
self.pushButton_help.clicked.connect(self.show_help_dialog)
|
446
|
-
|
447
|
-
# Mode switch
|
448
|
-
self.comboBox_mode.currentIndexChanged.connect(self.mode_switch)
|
449
|
-
|
450
|
-
# Manual Edit
|
451
|
-
self.tableWidget_coordinates.itemChanged.connect(self.handle_manual_edit)
|
452
|
-
|
453
|
-
def init_mode_lock(self) -> None:
|
454
|
-
if self.mode_lock is False:
|
455
|
-
return
|
456
|
-
elif self.mode_lock == "Individual":
|
457
|
-
self.comboBox_mode.setCurrentIndex(0)
|
458
|
-
self.comboBox_mode.setEnabled(False)
|
459
|
-
elif self.mode_lock == "Start/Stop":
|
460
|
-
self.comboBox_mode.setCurrentIndex(1)
|
461
|
-
self.comboBox_mode.setEnabled(False)
|
462
|
-
else:
|
463
|
-
self.mode_lock = False
|
464
|
-
print(f"Warning: Mode lock '{self.mode_lock}' not recognized.")
|
465
|
-
print(f"Unlocking mode lock.")
|
466
|
-
|
467
|
-
def init_ui(self) -> None:
|
468
|
-
"""Setup all ui elements"""
|
469
|
-
|
470
|
-
self.set_from_config() # Set default parameters
|
471
|
-
self.init_ui_plot_elements() # 2D Plot
|
472
|
-
self.init_ui_motor_control() # Motor Controls
|
473
|
-
self.init_ui_motor_configs() # Motor Configs
|
474
|
-
self.init_ui_motor_connections() # Motor Connections
|
475
|
-
self.init_keyboard_shortcuts() # Keyboard Shortcuts
|
476
|
-
self.init_ui_table() # Table validators for x and y coordinates
|
477
|
-
self.init_mode_lock() # Mode lock
|
478
|
-
|
479
|
-
def init_motor_map(self):
|
480
|
-
# Get motor limits
|
481
|
-
limit_x_min, limit_x_max = self.motor_thread.get_motor_limits(self.motor_x)
|
482
|
-
limit_y_min, limit_y_max = self.motor_thread.get_motor_limits(self.motor_y)
|
483
|
-
|
484
|
-
self.offset_x = limit_x_min
|
485
|
-
self.offset_y = limit_y_min
|
486
|
-
|
487
|
-
# Define the size of the image map based on the motor's limits
|
488
|
-
map_width = int(limit_x_max - limit_x_min + 1)
|
489
|
-
map_height = int(limit_y_max - limit_y_min + 1)
|
490
|
-
|
491
|
-
# Create an empty image map
|
492
|
-
self.background_value = 25
|
493
|
-
self.limit_map_data = np.full(
|
494
|
-
(map_width, map_height), self.background_value, dtype=np.float32
|
495
|
-
)
|
496
|
-
self.limit_map.setImage(self.limit_map_data)
|
497
|
-
|
498
|
-
# Set the initial position on the map
|
499
|
-
init_pos = self.motor_thread.retrieve_coordinates()
|
500
|
-
self.motor_positions = np.array([init_pos])
|
501
|
-
self.brushes = [pg.mkBrush(255, 255, 255, 255)]
|
502
|
-
|
503
|
-
self.motor_map.setData(pos=self.motor_positions, brush=self.brushes)
|
504
|
-
|
505
|
-
# Translate and scale the image item to match the motor coordinates
|
506
|
-
self.tr = QtGui.QTransform()
|
507
|
-
self.tr.translate(limit_x_min, limit_y_min)
|
508
|
-
self.limit_map.setTransform(self.tr)
|
509
|
-
|
510
|
-
if hasattr(self, "highlight_V") and hasattr(self, "highlight_H"):
|
511
|
-
self.plot_map.removeItem(self.highlight_V)
|
512
|
-
self.plot_map.removeItem(self.highlight_H)
|
513
|
-
|
514
|
-
# Crosshair to highlight the current position
|
515
|
-
self.highlight_V = pg.InfiniteLine(
|
516
|
-
angle=90, movable=False, pen=pg.mkPen(color="r", width=1, style=QtCore.Qt.DashLine)
|
517
|
-
)
|
518
|
-
self.highlight_H = pg.InfiniteLine(
|
519
|
-
angle=0, movable=False, pen=pg.mkPen(color="r", width=1, style=QtCore.Qt.DashLine)
|
520
|
-
)
|
521
|
-
|
522
|
-
self.plot_map.addItem(self.highlight_V)
|
523
|
-
self.plot_map.addItem(self.highlight_H)
|
524
|
-
|
525
|
-
self.highlight_V.setPos(init_pos[0])
|
526
|
-
self.highlight_H.setPos(init_pos[1])
|
527
|
-
|
528
|
-
def update_image_map(self, x, y):
|
529
|
-
# Update label
|
530
|
-
self.label_coorditanes.setText(f"Motor position: ({x}, {y})")
|
531
|
-
|
532
|
-
# Add new point with full brightness
|
533
|
-
new_pos = np.array([x, y])
|
534
|
-
self.motor_positions = np.vstack((self.motor_positions, new_pos))
|
535
|
-
|
536
|
-
# If the number of points exceeds max_points, delete the oldest points
|
537
|
-
if len(self.motor_positions) > self.max_points:
|
538
|
-
self.motor_positions = self.motor_positions[-self.max_points :]
|
539
|
-
|
540
|
-
# Determine brushes based on position in the array
|
541
|
-
self.brushes = [pg.mkBrush(50, 50, 50, 255)] * len(self.motor_positions)
|
542
|
-
|
543
|
-
# Calculate the decrement step based on self.num_dim_points
|
544
|
-
decrement_step = (255 - 50) / self.num_dim_points
|
545
|
-
|
546
|
-
for i in range(1, min(self.num_dim_points + 1, len(self.motor_positions) + 1)):
|
547
|
-
brightness = max(60, 255 - decrement_step * (i - 1))
|
548
|
-
self.brushes[-i] = pg.mkBrush(brightness, brightness, brightness, 255)
|
549
|
-
|
550
|
-
self.brushes[-1] = pg.mkBrush(255, 255, 255, 255) # Newest point is always full brightness
|
551
|
-
|
552
|
-
self.motor_map.setData(pos=self.motor_positions, brush=self.brushes, size=self.scatter_size)
|
553
|
-
|
554
|
-
# Set Highlight
|
555
|
-
self.highlight_V.setPos(x)
|
556
|
-
self.highlight_H.setPos(y)
|
557
|
-
|
558
|
-
def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None:
|
559
|
-
self.motor_thread.update_all_motor_limits(x_limit=x_limit, y_limit=y_limit)
|
560
|
-
|
561
|
-
def update_arrow_key_shortcuts(self):
|
562
|
-
if self.checkBox_enableArrows.isChecked():
|
563
|
-
# Set the arrow key shortcuts for motor movement
|
564
|
-
self.toolButton_right.setShortcut(Qt.Key_Right)
|
565
|
-
self.toolButton_left.setShortcut(Qt.Key_Left)
|
566
|
-
self.toolButton_up.setShortcut(Qt.Key_Up)
|
567
|
-
self.toolButton_down.setShortcut(Qt.Key_Down)
|
568
|
-
else:
|
569
|
-
# Clear the shortcuts
|
570
|
-
self.toolButton_right.setShortcut("")
|
571
|
-
self.toolButton_left.setShortcut("")
|
572
|
-
self.toolButton_up.setShortcut("")
|
573
|
-
self.toolButton_down.setShortcut("")
|
574
|
-
|
575
|
-
def mode_switch(self):
|
576
|
-
current_index = self.comboBox_mode.currentIndex()
|
577
|
-
|
578
|
-
if self.tableWidget_coordinates.rowCount() > 0:
|
579
|
-
msgBox = QMessageBox()
|
580
|
-
msgBox.setIcon(QMessageBox.Warning)
|
581
|
-
msgBox.setText(
|
582
|
-
"Switching modes will delete all table entries. Do you want to continue?"
|
583
|
-
)
|
584
|
-
msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
|
585
|
-
returnValue = msgBox.exec()
|
586
|
-
|
587
|
-
if returnValue == QMessageBox.Cancel:
|
588
|
-
self.comboBox_mode.blockSignals(True) # Block signals
|
589
|
-
self.comboBox_mode.setCurrentIndex(self.last_selected_index)
|
590
|
-
self.comboBox_mode.blockSignals(False) # Unblock signals
|
591
|
-
return
|
592
|
-
|
593
|
-
self.tableWidget_coordinates.setRowCount(0) # Wipe table
|
594
|
-
|
595
|
-
# Clear saved points from map
|
596
|
-
self.saved_motor_map_start.clear()
|
597
|
-
self.saved_motor_map_end.clear()
|
598
|
-
self.saved_motor_map_individual.clear()
|
599
|
-
|
600
|
-
if current_index == 0: # 'individual' is selected
|
601
|
-
header = ["Show", "Move", "Tag", "X", "Y"]
|
602
|
-
|
603
|
-
self.tableWidget_coordinates.setColumnCount(len(header))
|
604
|
-
self.tableWidget_coordinates.setHorizontalHeaderLabels(header)
|
605
|
-
self.tableWidget_coordinates.setItemDelegateForColumn(3, self.double_delegate)
|
606
|
-
self.tableWidget_coordinates.setItemDelegateForColumn(4, self.double_delegate)
|
607
|
-
|
608
|
-
elif current_index == 1: # 'start/stop' is selected
|
609
|
-
header = [
|
610
|
-
"Show",
|
611
|
-
"Move [start]",
|
612
|
-
"Move [end]",
|
613
|
-
"Tag",
|
614
|
-
"X [start]",
|
615
|
-
"Y [start]",
|
616
|
-
"X [end]",
|
617
|
-
"Y [end]",
|
618
|
-
]
|
619
|
-
self.tableWidget_coordinates.setColumnCount(len(header))
|
620
|
-
self.tableWidget_coordinates.setHorizontalHeaderLabels(header)
|
621
|
-
self.tableWidget_coordinates.setItemDelegateForColumn(3, self.double_delegate)
|
622
|
-
self.tableWidget_coordinates.setItemDelegateForColumn(4, self.double_delegate)
|
623
|
-
self.tableWidget_coordinates.setItemDelegateForColumn(5, self.double_delegate)
|
624
|
-
self.tableWidget_coordinates.setItemDelegateForColumn(6, self.double_delegate)
|
625
|
-
|
626
|
-
self.last_selected_index = current_index # Save the last selected index
|
627
|
-
|
628
|
-
def generate_table_coordinate(
|
629
|
-
self, table: QtWidgets.QTableWidget, coordinates: tuple, tag: str = None, precision: int = 0
|
630
|
-
) -> None:
|
631
|
-
# To not call replot points during table generation
|
632
|
-
self.replot_lock = True
|
633
|
-
|
634
|
-
current_index = self.comboBox_mode.currentIndex()
|
635
|
-
|
636
|
-
if current_index == 1 and self.is_next_entry_end:
|
637
|
-
target_row = table.rowCount() - 1 # Last row
|
638
|
-
else:
|
639
|
-
new_row_count = table.rowCount() + 1
|
640
|
-
table.setRowCount(new_row_count)
|
641
|
-
target_row = new_row_count - 1 # New row
|
642
|
-
|
643
|
-
# Create QDoubleValidator
|
644
|
-
validator = QDoubleValidator()
|
645
|
-
validator.setDecimals(precision)
|
646
|
-
|
647
|
-
# Checkbox for visibility switch -> always first column
|
648
|
-
checkBox = QtWidgets.QCheckBox()
|
649
|
-
checkBox.setChecked(True)
|
650
|
-
checkBox.stateChanged.connect(lambda: self.replot_based_on_table(table))
|
651
|
-
table.setCellWidget(target_row, 0, checkBox)
|
652
|
-
|
653
|
-
# Apply validator to x and y coordinate QTableWidgetItem
|
654
|
-
item_x = QtWidgets.QTableWidgetItem(str(f"{coordinates[0]:.{precision}f}"))
|
655
|
-
item_y = QtWidgets.QTableWidgetItem(str(f"{coordinates[1]:.{precision}f}"))
|
656
|
-
item_x.setFlags(item_x.flags() | Qt.ItemIsEditable)
|
657
|
-
item_y.setFlags(item_y.flags() | Qt.ItemIsEditable)
|
658
|
-
|
659
|
-
# Mode switch
|
660
|
-
if current_index == 1: # start/stop mode
|
661
|
-
# Create buttons for start and end coordinates
|
662
|
-
button_start = QPushButton("Go [start]")
|
663
|
-
button_end = QPushButton("Go [end]")
|
664
|
-
|
665
|
-
# Add buttons to table
|
666
|
-
table.setCellWidget(target_row, 1, button_start)
|
667
|
-
table.setCellWidget(target_row, 2, button_end)
|
668
|
-
|
669
|
-
button_end.setEnabled(
|
670
|
-
self.is_next_entry_end
|
671
|
-
) # Enable only if end coordinate is present
|
672
|
-
|
673
|
-
# Connect buttons to the slot
|
674
|
-
button_start.clicked.connect(self.move_to_row_coordinates)
|
675
|
-
button_end.clicked.connect(self.move_to_row_coordinates)
|
676
|
-
|
677
|
-
# Set Tag
|
678
|
-
table.setItem(target_row, 3, QtWidgets.QTableWidgetItem(str(tag)))
|
679
|
-
|
680
|
-
# Add coordinates to table
|
681
|
-
col_index = 8
|
682
|
-
if self.is_next_entry_end:
|
683
|
-
table.setItem(target_row, 6, item_x)
|
684
|
-
table.setItem(target_row, 7, item_y)
|
685
|
-
else:
|
686
|
-
table.setItem(target_row, 4, item_x)
|
687
|
-
table.setItem(target_row, 5, item_y)
|
688
|
-
self.is_next_entry_end = not self.is_next_entry_end
|
689
|
-
else: # Individual mode
|
690
|
-
button_start = QPushButton("Go")
|
691
|
-
table.setCellWidget(target_row, 1, button_start)
|
692
|
-
button_start.clicked.connect(self.move_to_row_coordinates)
|
693
|
-
|
694
|
-
# Set Tag
|
695
|
-
table.setItem(target_row, 2, QtWidgets.QTableWidgetItem(str(tag)))
|
696
|
-
|
697
|
-
col_index = 5
|
698
|
-
table.setItem(target_row, 3, item_x)
|
699
|
-
table.setItem(target_row, 4, item_y)
|
700
|
-
|
701
|
-
# Adding extra columns
|
702
|
-
# TODO simplify nesting
|
703
|
-
if current_index != 1 or self.is_next_entry_end:
|
704
|
-
if self.extra_columns:
|
705
|
-
table.setColumnCount(col_index + len(self.extra_columns))
|
706
|
-
for col_dict in self.extra_columns:
|
707
|
-
for col_name, default_value in col_dict.items():
|
708
|
-
if target_row == 0:
|
709
|
-
item = QtWidgets.QTableWidgetItem(str(default_value))
|
710
|
-
|
711
|
-
else:
|
712
|
-
prev_item = table.item(target_row - 1, col_index)
|
713
|
-
item_text = prev_item.text() if prev_item else ""
|
714
|
-
item = QtWidgets.QTableWidgetItem(item_text)
|
715
|
-
|
716
|
-
item.setFlags(item.flags() | Qt.ItemIsEditable)
|
717
|
-
table.setItem(target_row, col_index, item)
|
718
|
-
|
719
|
-
if target_row == 0 or (current_index == 1 and not self.is_next_entry_end):
|
720
|
-
table.setHorizontalHeaderItem(
|
721
|
-
col_index, QtWidgets.QTableWidgetItem(col_name)
|
722
|
-
)
|
723
|
-
|
724
|
-
col_index += 1
|
725
|
-
|
726
|
-
self.align_table_center(table)
|
727
|
-
|
728
|
-
if self.checkBox_resize_auto.isChecked():
|
729
|
-
table.resizeColumnsToContents()
|
730
|
-
|
731
|
-
# Unlock Replot
|
732
|
-
self.replot_lock = False
|
733
|
-
|
734
|
-
# Replot the saved motor map
|
735
|
-
self.replot_based_on_table(table)
|
736
|
-
|
737
|
-
def duplicate_last_row(self, table: QtWidgets.QTableWidget) -> None:
|
738
|
-
if self.is_next_entry_end is True:
|
739
|
-
msgBox = QMessageBox()
|
740
|
-
msgBox.setIcon(QMessageBox.Warning)
|
741
|
-
msgBox.setText("The end coordinates were not set for previous entry!")
|
742
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
743
|
-
returnValue = msgBox.exec()
|
744
|
-
|
745
|
-
if returnValue == QMessageBox.Ok:
|
746
|
-
return
|
747
|
-
|
748
|
-
last_row = table.rowCount() - 1
|
749
|
-
if last_row == -1:
|
750
|
-
return
|
751
|
-
|
752
|
-
# Get the tag and coordinates from the last row
|
753
|
-
tag = table.item(last_row, 2).text() if table.item(last_row, 2) else None
|
754
|
-
mode_index = self.comboBox_mode.currentIndex()
|
755
|
-
|
756
|
-
if mode_index == 1: # start/stop mode
|
757
|
-
x_start = float(table.item(last_row, 4).text()) if table.item(last_row, 4) else None
|
758
|
-
y_start = float(table.item(last_row, 5).text()) if table.item(last_row, 5) else None
|
759
|
-
x_end = float(table.item(last_row, 6).text()) if table.item(last_row, 6) else None
|
760
|
-
y_end = float(table.item(last_row, 7).text()) if table.item(last_row, 7) else None
|
761
|
-
|
762
|
-
# Duplicate the 'start' coordinates
|
763
|
-
self.generate_table_coordinate(table, (x_start, y_start), tag, precision=self.precision)
|
764
|
-
|
765
|
-
# Duplicate the 'end' coordinates
|
766
|
-
self.generate_table_coordinate(table, (x_end, y_end), tag, precision=self.precision)
|
767
|
-
|
768
|
-
else: # individual mode
|
769
|
-
x = float(table.item(last_row, 3).text()) if table.item(last_row, 3) else None
|
770
|
-
y = float(table.item(last_row, 4).text()) if table.item(last_row, 4) else None
|
771
|
-
|
772
|
-
# Duplicate the coordinates
|
773
|
-
self.generate_table_coordinate(table, (x, y), tag, precision=self.precision)
|
774
|
-
|
775
|
-
self.align_table_center(table)
|
776
|
-
|
777
|
-
if self.checkBox_resize_auto.isChecked():
|
778
|
-
table.resizeColumnsToContents()
|
779
|
-
|
780
|
-
def handle_manual_edit(self, item):
|
781
|
-
table = item.tableWidget()
|
782
|
-
row, col = item.row(), item.column()
|
783
|
-
mode_index = self.comboBox_mode.currentIndex()
|
784
|
-
|
785
|
-
# Determine the columns where the x and y coordinates are stored based on the mode.
|
786
|
-
coord_cols = [3, 4] if mode_index == 0 else [4, 5, 6, 7]
|
787
|
-
|
788
|
-
if col not in coord_cols:
|
789
|
-
return # Only proceed if the edited columns are coordinate columns
|
790
|
-
|
791
|
-
# Replot based on the table
|
792
|
-
self.replot_based_on_table(table)
|
793
|
-
|
794
|
-
@staticmethod
|
795
|
-
def align_table_center(table: QtWidgets.QTableWidget) -> None:
|
796
|
-
for row in range(table.rowCount()):
|
797
|
-
for col in range(table.columnCount()):
|
798
|
-
item = table.item(row, col)
|
799
|
-
if item:
|
800
|
-
item.setTextAlignment(Qt.AlignCenter)
|
801
|
-
|
802
|
-
def move_to_row_coordinates(self):
|
803
|
-
# Find out the mode and decide columns accordingly
|
804
|
-
mode = self.comboBox_mode.currentIndex()
|
805
|
-
|
806
|
-
# Get the button that emitted the signal# Get the button that emitted the signal
|
807
|
-
button = self.sender()
|
808
|
-
|
809
|
-
# Find the row and column where the button is located
|
810
|
-
row = self.tableWidget_coordinates.indexAt(button.pos()).row()
|
811
|
-
col = self.tableWidget_coordinates.indexAt(button.pos()).column()
|
812
|
-
|
813
|
-
# Decide which coordinates to move to based on the column
|
814
|
-
if mode == 1:
|
815
|
-
if col == 1: # Go to 'start' coordinates
|
816
|
-
x_col, y_col = 4, 5
|
817
|
-
elif col == 2: # Go to 'end' coordinates
|
818
|
-
x_col, y_col = 6, 7
|
819
|
-
else: # Default case
|
820
|
-
x_col, y_col = 3, 4 # For "individual" mode
|
821
|
-
|
822
|
-
# Fetch and move coordinates
|
823
|
-
x = float(self.tableWidget_coordinates.item(row, x_col).text())
|
824
|
-
y = float(self.tableWidget_coordinates.item(row, y_col).text())
|
825
|
-
self.move_motor_absolute(x, y)
|
826
|
-
|
827
|
-
def replot_based_on_table(self, table):
|
828
|
-
if self.replot_lock is True:
|
829
|
-
return
|
830
|
-
|
831
|
-
print("Replot Triggered")
|
832
|
-
start_points = []
|
833
|
-
end_points = []
|
834
|
-
individual_points = []
|
835
|
-
# self.rectangles = [] #TODO introduce later
|
836
|
-
|
837
|
-
for row in range(table.rowCount()):
|
838
|
-
visibility = table.cellWidget(row, 0).isChecked()
|
839
|
-
if not visibility:
|
840
|
-
continue
|
841
|
-
|
842
|
-
if self.comboBox_mode.currentIndex() == 1: # start/stop mode
|
843
|
-
x_start = float(table.item(row, 4).text()) if table.item(row, 4) else None
|
844
|
-
y_start = float(table.item(row, 5).text()) if table.item(row, 5) else None
|
845
|
-
x_end = float(table.item(row, 6).text()) if table.item(row, 6) else None
|
846
|
-
y_end = float(table.item(row, 7).text()) if table.item(row, 7) else None
|
847
|
-
|
848
|
-
if x_start is not None and y_start is not None:
|
849
|
-
start_points.append([x_start, y_start])
|
850
|
-
print(f"added start points:{start_points}")
|
851
|
-
if x_end is not None and y_end is not None:
|
852
|
-
end_points.append([x_end, y_end])
|
853
|
-
print(f"added end points:{end_points}")
|
854
|
-
|
855
|
-
else: # individual mode
|
856
|
-
x_ind = float(table.item(row, 3).text()) if table.item(row, 3) else None
|
857
|
-
y_ind = float(table.item(row, 4).text()) if table.item(row, 4) else None
|
858
|
-
if x_ind is not None and y_ind is not None:
|
859
|
-
individual_points.append([x_ind, y_ind])
|
860
|
-
print(f"added individual points:{individual_points}")
|
861
|
-
|
862
|
-
if start_points:
|
863
|
-
self.saved_motor_map_start.setData(pos=np.array(start_points))
|
864
|
-
print("plotted start")
|
865
|
-
if end_points:
|
866
|
-
self.saved_motor_map_end.setData(pos=np.array(end_points))
|
867
|
-
print("plotted end")
|
868
|
-
if individual_points:
|
869
|
-
self.saved_motor_map_individual.setData(pos=np.array(individual_points))
|
870
|
-
print("plotted individual")
|
871
|
-
|
872
|
-
# TODO will be adapted with logic to handle start/end points
|
873
|
-
def draw_rectangles(self, start_points, end_points):
|
874
|
-
for start, end in zip(start_points, end_points):
|
875
|
-
self.draw_rectangle(start, end)
|
876
|
-
|
877
|
-
def draw_rectangle(self, start, end):
|
878
|
-
pass
|
879
|
-
|
880
|
-
def delete_selected_row(self):
|
881
|
-
selected_rows = self.tableWidget_coordinates.selectionModel().selectedRows()
|
882
|
-
rows_to_delete = [row.row() for row in selected_rows]
|
883
|
-
rows_to_delete.sort(reverse=True) # Sort in descending order
|
884
|
-
|
885
|
-
# Remove the row from the table
|
886
|
-
for row_index in rows_to_delete:
|
887
|
-
self.tableWidget_coordinates.removeRow(row_index)
|
888
|
-
|
889
|
-
# Replot the saved motor map
|
890
|
-
self.replot_based_on_table(self.tableWidget_coordinates)
|
891
|
-
|
892
|
-
def resizeTable(self, table):
|
893
|
-
table.resizeColumnsToContents()
|
894
|
-
|
895
|
-
def export_table_to_csv(self, table: QtWidgets.QTableWidget):
|
896
|
-
options = QFileDialog.Options()
|
897
|
-
filePath, _ = QFileDialog.getSaveFileName(
|
898
|
-
self, "Save File", "", "CSV Files (*.csv);;All Files (*)", options=options
|
899
|
-
)
|
900
|
-
|
901
|
-
if filePath:
|
902
|
-
if not filePath.endswith(".csv"):
|
903
|
-
filePath += ".csv"
|
904
|
-
|
905
|
-
with open(filePath, mode="w", newline="") as file:
|
906
|
-
writer = csv.writer(file)
|
907
|
-
|
908
|
-
col_offset = 2 if self.comboBox_mode.currentIndex() == 0 else 3
|
909
|
-
|
910
|
-
# Write the header
|
911
|
-
header = []
|
912
|
-
for col in range(col_offset, table.columnCount()):
|
913
|
-
header_item = table.horizontalHeaderItem(col)
|
914
|
-
header.append(header_item.text() if header_item else "")
|
915
|
-
writer.writerow(header)
|
916
|
-
|
917
|
-
# Write the content
|
918
|
-
for row in range(table.rowCount()):
|
919
|
-
row_data = []
|
920
|
-
for col in range(col_offset, table.columnCount()):
|
921
|
-
item = table.item(row, col)
|
922
|
-
row_data.append(item.text() if item else "")
|
923
|
-
writer.writerow(row_data)
|
924
|
-
|
925
|
-
def load_table_from_csv(self, table: QtWidgets.QTableWidget, precision: int = 0):
|
926
|
-
options = QFileDialog.Options()
|
927
|
-
filePath, _ = QFileDialog.getOpenFileName(
|
928
|
-
self, "Open File", "", "CSV Files (*.csv);;All Files (*)", options=options
|
929
|
-
)
|
930
|
-
|
931
|
-
if filePath:
|
932
|
-
with open(filePath, mode="r") as file:
|
933
|
-
reader = csv.reader(file)
|
934
|
-
header = next(reader)
|
935
|
-
|
936
|
-
# Wipe the current table
|
937
|
-
table.setRowCount(0)
|
938
|
-
|
939
|
-
# Populate data
|
940
|
-
for row_data in reader:
|
941
|
-
tag = row_data[0]
|
942
|
-
|
943
|
-
if self.comboBox_mode.currentIndex() == 0: # Individual mode
|
944
|
-
x = float(row_data[1])
|
945
|
-
y = float(row_data[2])
|
946
|
-
self.generate_table_coordinate(table, (x, y), tag, precision)
|
947
|
-
|
948
|
-
elif self.comboBox_mode.currentIndex() == 1: # Start/Stop mode
|
949
|
-
x_start = float(row_data[1])
|
950
|
-
y_start = float(row_data[2])
|
951
|
-
x_end = float(row_data[3])
|
952
|
-
y_end = float(row_data[4])
|
953
|
-
|
954
|
-
self.generate_table_coordinate(table, (x_start, y_start), tag, precision)
|
955
|
-
self.generate_table_coordinate(table, (x_end, y_end), tag, precision)
|
956
|
-
|
957
|
-
if self.checkBox_resize_auto.isChecked():
|
958
|
-
table.resizeColumnsToContents()
|
959
|
-
|
960
|
-
def save_absolute_coordinates(self):
|
961
|
-
self.generate_table_coordinate(
|
962
|
-
self.tableWidget_coordinates,
|
963
|
-
(self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()),
|
964
|
-
tag=f"Pos {self.tag_N}",
|
965
|
-
precision=self.precision,
|
966
|
-
)
|
967
|
-
|
968
|
-
self.tag_N += 1
|
969
|
-
|
970
|
-
def save_current_coordinates(self):
|
971
|
-
self.generate_table_coordinate(
|
972
|
-
self.tableWidget_coordinates,
|
973
|
-
self.motor_thread.retrieve_coordinates(),
|
974
|
-
tag=f"Cur {self.tag_N}",
|
975
|
-
precision=self.precision,
|
976
|
-
)
|
977
|
-
|
978
|
-
self.tag_N += 1
|
979
|
-
|
980
|
-
def update_precision(self, precision: int):
|
981
|
-
self.precision = precision
|
982
|
-
self.spinBox_step_x.setDecimals(self.precision)
|
983
|
-
self.spinBox_step_y.setDecimals(self.precision)
|
984
|
-
self.spinBox_absolute_x.setDecimals(self.precision)
|
985
|
-
self.spinBox_absolute_y.setDecimals(self.precision)
|
986
|
-
|
987
|
-
def change_step_size(self, spinBox: QtWidgets.QDoubleSpinBox, factor: float) -> None:
|
988
|
-
old_step = spinBox.value()
|
989
|
-
new_step = old_step * factor
|
990
|
-
spinBox.setValue(new_step)
|
991
|
-
|
992
|
-
# TODO generalize these functions
|
993
|
-
|
994
|
-
def sync_step_sizes(self):
|
995
|
-
"""Sync step sizes based on checkbox state."""
|
996
|
-
if self.checkBox_same_xy.isChecked():
|
997
|
-
value = self.spinBox_step_x.value()
|
998
|
-
self.spinBox_step_y.setValue(value)
|
999
|
-
|
1000
|
-
def update_step_size_x(self):
|
1001
|
-
"""Update step size for x if checkbox is checked."""
|
1002
|
-
if self.checkBox_same_xy.isChecked():
|
1003
|
-
value = self.spinBox_step_x.value()
|
1004
|
-
self.spinBox_step_y.setValue(value)
|
1005
|
-
|
1006
|
-
def update_step_size_y(self):
|
1007
|
-
"""Update step size for y if checkbox is checked."""
|
1008
|
-
if self.checkBox_same_xy.isChecked():
|
1009
|
-
value = self.spinBox_step_y.value()
|
1010
|
-
self.spinBox_step_x.setValue(value)
|
1011
|
-
|
1012
|
-
# def sync_step_sizes(self, spinBox1, spinBox2): #TODO move to more general solution like this
|
1013
|
-
# if self.checkBox_same_xy.isChecked():
|
1014
|
-
# value = spinBox1.value()
|
1015
|
-
# spinBox2.setValue(value)
|
1016
|
-
|
1017
|
-
def show_help_dialog(self):
|
1018
|
-
dialog = QDialog(self)
|
1019
|
-
dialog.setWindowTitle("Help")
|
1020
|
-
|
1021
|
-
layout = QVBoxLayout()
|
1022
|
-
|
1023
|
-
# Key bindings section
|
1024
|
-
layout.addWidget(QLabel("Keyboard Shortcuts:"))
|
1025
|
-
|
1026
|
-
key_bindings = [
|
1027
|
-
("Delete/Backspace", "Delete selected row"),
|
1028
|
-
("Ctrl+A", "Increase step size for X motor by factor of 2"),
|
1029
|
-
("Ctrl+Z", "Decrease step size for X motor by factor of 2"),
|
1030
|
-
("Alt+A", "Increase step size for Y motor by factor of 2"),
|
1031
|
-
("Alt+Z", "Decrease step size for Y motor by factor of 2"),
|
1032
|
-
("Ctrl+G", "Go absolute"),
|
1033
|
-
("Ctrl+D", "Set absolute coordinates"),
|
1034
|
-
("Ctrl+S", "Save Current coordinates"),
|
1035
|
-
("Ctrl+X", "Stop"),
|
1036
|
-
]
|
1037
|
-
|
1038
|
-
for keys, action in key_bindings:
|
1039
|
-
layout.addWidget(QLabel(f"{keys} - {action}"))
|
1040
|
-
|
1041
|
-
# Separator
|
1042
|
-
separator = QFrame()
|
1043
|
-
separator.setFrameShape(QFrame.HLine)
|
1044
|
-
separator.setFrameShadow(QFrame.Sunken)
|
1045
|
-
layout.addWidget(separator)
|
1046
|
-
|
1047
|
-
# Import/Export section
|
1048
|
-
layout.addWidget(QLabel("Import/Export of Table:"))
|
1049
|
-
layout.addWidget(
|
1050
|
-
QLabel(
|
1051
|
-
"Create additional table columns in config yaml file.\n"
|
1052
|
-
"Be sure to load the correct config file with console argument -c.\n"
|
1053
|
-
"When importing a table, the first three columns must be [Tag, X, Y] in the case of Individual mode \n"
|
1054
|
-
"and [Tag, X [start], Y [start], X [end], Y [end] in the case of Start/Stop mode.\n"
|
1055
|
-
"Failing to do so will break the table!"
|
1056
|
-
)
|
1057
|
-
)
|
1058
|
-
layout.addWidget(
|
1059
|
-
QLabel(
|
1060
|
-
"Note: Importing a table will overwrite the current table. Import in correct mode."
|
1061
|
-
)
|
1062
|
-
)
|
1063
|
-
|
1064
|
-
# Another Separator
|
1065
|
-
another_separator = QFrame()
|
1066
|
-
another_separator.setFrameShape(QFrame.HLine)
|
1067
|
-
another_separator.setFrameShadow(QFrame.Sunken)
|
1068
|
-
layout.addWidget(another_separator)
|
1069
|
-
|
1070
|
-
# PyQtGraph Controls
|
1071
|
-
layout.addWidget(QLabel("Graph Window Controls:"))
|
1072
|
-
graph_controls = [("Left Drag", "Pan the view"), ("Right Drag or Scroll", "Zoom in/out")]
|
1073
|
-
for action, description in graph_controls:
|
1074
|
-
layout.addWidget(QLabel(f"{action} - {description}"))
|
1075
|
-
|
1076
|
-
ok_button = QPushButton("OK")
|
1077
|
-
ok_button.clicked.connect(dialog.close)
|
1078
|
-
layout.addWidget(ok_button)
|
1079
|
-
|
1080
|
-
dialog.setLayout(layout)
|
1081
|
-
dialog.exec()
|
1082
|
-
|
1083
|
-
@staticmethod
|
1084
|
-
def param_changed(ui_element):
|
1085
|
-
ui_element.setStyleSheet("background-color: #FFA700;")
|
1086
|
-
|
1087
|
-
|
1088
|
-
class MotorActions(Enum):
|
1089
|
-
MOVE_TO_COORDINATES = "move_to_coordinates"
|
1090
|
-
MOVE_RELATIVE = "move_relative"
|
1091
|
-
|
1092
|
-
|
1093
|
-
class MotorControl(QThread):
|
1094
|
-
"""
|
1095
|
-
QThread subclass for controlling motor actions asynchronously.
|
1096
|
-
|
1097
|
-
Attributes:
|
1098
|
-
coordinates_updated (pyqtSignal): Signal to emit current coordinates.
|
1099
|
-
limits_retrieved (pyqtSignal): Signal to emit current limits.
|
1100
|
-
move_finished (pyqtSignal): Signal to emit when the move is finished.
|
1101
|
-
motors_loaded (pyqtSignal): Signal to emit when the motors are loaded.
|
1102
|
-
motors_selected (pyqtSignal): Signal to emit when the motors are selected.
|
1103
|
-
"""
|
1104
|
-
|
1105
|
-
coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates
|
1106
|
-
limits_retrieved = pyqtSignal(list, list) # Signal to emit current limits
|
1107
|
-
move_finished = pyqtSignal() # Signal to emit when the move is finished
|
1108
|
-
motors_loaded = pyqtSignal(list, list) # Signal to emit when the motors are loaded
|
1109
|
-
motors_selected = pyqtSignal(object, object) # Signal to emit when the motors are selected
|
1110
|
-
# progress_updated = pyqtSignal(int) #TODO Signal to emit progress percentage
|
1111
|
-
|
1112
|
-
def __init__(self, parent=None):
|
1113
|
-
super().__init__(parent)
|
1114
|
-
|
1115
|
-
self.action = None
|
1116
|
-
self._initialize_motor()
|
1117
|
-
|
1118
|
-
def connect_motors(self, motor_x_name: str, motor_y_name: str) -> None:
|
1119
|
-
"""
|
1120
|
-
Connect to the specified motors by their names.
|
1121
|
-
|
1122
|
-
Args:
|
1123
|
-
motor_x_name (str): The name of the motor for the x-axis.
|
1124
|
-
motor_y_name (str): The name of the motor for the y-axis.
|
1125
|
-
"""
|
1126
|
-
self.motor_x_name = motor_x_name
|
1127
|
-
self.motor_y_name = motor_y_name
|
1128
|
-
|
1129
|
-
self.motor_x, self.motor_y = (dev[self.motor_x_name], dev[self.motor_y_name])
|
1130
|
-
|
1131
|
-
(self.current_x, self.current_y) = self.get_coordinates()
|
1132
|
-
|
1133
|
-
if self.motors_consumer is not None:
|
1134
|
-
self.motors_consumer.shutdown()
|
1135
|
-
|
1136
|
-
self.motors_consumer = client.connector.consumer(
|
1137
|
-
topics=[
|
1138
|
-
MessageEndpoints.device_readback(self.motor_x.name),
|
1139
|
-
MessageEndpoints.device_readback(self.motor_y.name),
|
1140
|
-
],
|
1141
|
-
cb=self._device_status_callback_motors,
|
1142
|
-
parent=self,
|
1143
|
-
)
|
1144
|
-
|
1145
|
-
self.motors_consumer.start()
|
1146
|
-
|
1147
|
-
self.motors_selected.emit(self.motor_x, self.motor_y)
|
1148
|
-
|
1149
|
-
def get_all_motors(self) -> list:
|
1150
|
-
"""
|
1151
|
-
Retrieve a list of all available motors.
|
1152
|
-
|
1153
|
-
Returns:
|
1154
|
-
list: List of all available motors.
|
1155
|
-
"""
|
1156
|
-
all_motors = (
|
1157
|
-
client.device_manager.devices.enabled_devices
|
1158
|
-
) # .acquisition_group("motor") #TODO remove motor group?
|
1159
|
-
return all_motors
|
1160
|
-
|
1161
|
-
def get_all_motors_names(self) -> list:
|
1162
|
-
all_motors = client.device_manager.devices.enabled_devices # .acquisition_group("motor")
|
1163
|
-
all_motors_names = [motor.name for motor in all_motors]
|
1164
|
-
return all_motors_names
|
1165
|
-
|
1166
|
-
def retrieve_all_motors(self):
|
1167
|
-
self.all_motors = self.get_all_motors()
|
1168
|
-
self.all_motors_names = self.get_all_motors_names()
|
1169
|
-
self.motors_loaded.emit(self.all_motors_names, self.all_motors_names)
|
1170
|
-
|
1171
|
-
return self.all_motors, self.all_motors_names
|
1172
|
-
|
1173
|
-
def get_coordinates(self) -> tuple:
|
1174
|
-
"""Get current motor position"""
|
1175
|
-
x = self.motor_x.readback.get()
|
1176
|
-
y = self.motor_y.readback.get()
|
1177
|
-
return x, y
|
1178
|
-
|
1179
|
-
def retrieve_coordinates(self) -> tuple:
|
1180
|
-
"""Get current motor position for export to main app"""
|
1181
|
-
return self.current_x, self.current_y
|
1182
|
-
|
1183
|
-
def get_motor_limits(self, motor) -> list:
|
1184
|
-
"""
|
1185
|
-
Retrieve the limits for a specific motor.
|
1186
|
-
|
1187
|
-
Args:
|
1188
|
-
motor (object): Motor object.
|
1189
|
-
|
1190
|
-
Returns:
|
1191
|
-
tuple: Lower and upper limit for the motor.
|
1192
|
-
"""
|
1193
|
-
try:
|
1194
|
-
return motor.limits
|
1195
|
-
except AttributeError:
|
1196
|
-
# If the motor doesn't have a 'limits' attribute, return a default value or raise a custom exception
|
1197
|
-
print(f"The device {motor} does not have defined limits.")
|
1198
|
-
return None
|
1199
|
-
|
1200
|
-
def retrieve_motor_limits(self, motor_x, motor_y):
|
1201
|
-
limit_x = self.get_motor_limits(motor_x)
|
1202
|
-
limit_y = self.get_motor_limits(motor_y)
|
1203
|
-
self.limits_retrieved.emit(limit_x, limit_y)
|
1204
|
-
|
1205
|
-
def update_motor_limits(self, motor, low_limit=None, high_limit=None) -> None:
|
1206
|
-
current_low_limit, current_high_limit = self.get_motor_limits(motor)
|
1207
|
-
|
1208
|
-
# Check if the low limit has changed and is not None
|
1209
|
-
if low_limit is not None and low_limit != current_low_limit:
|
1210
|
-
motor.low_limit = low_limit
|
1211
|
-
|
1212
|
-
# Check if the high limit has changed and is not None
|
1213
|
-
if high_limit is not None and high_limit != current_high_limit:
|
1214
|
-
motor.high_limit = high_limit
|
1215
|
-
|
1216
|
-
def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None:
|
1217
|
-
current_position = self.get_coordinates()
|
1218
|
-
|
1219
|
-
if x_limit is not None:
|
1220
|
-
if current_position[0] < x_limit[0] or current_position[0] > x_limit[1]:
|
1221
|
-
raise ValueError("Current motor position is outside the new limits (X)")
|
1222
|
-
else:
|
1223
|
-
self.update_motor_limits(self.motor_x, low_limit=x_limit[0], high_limit=x_limit[1])
|
1224
|
-
|
1225
|
-
if y_limit is not None:
|
1226
|
-
if current_position[1] < y_limit[0] or current_position[1] > y_limit[1]:
|
1227
|
-
raise ValueError("Current motor position is outside the new limits (Y)")
|
1228
|
-
else:
|
1229
|
-
self.update_motor_limits(self.motor_y, low_limit=y_limit[0], high_limit=y_limit[1])
|
1230
|
-
|
1231
|
-
self.retrieve_motor_limits(self.motor_x, self.motor_y)
|
1232
|
-
|
1233
|
-
def move_to_coordinates(self, target_coordinates: tuple):
|
1234
|
-
self.action = MotorActions.MOVE_TO_COORDINATES
|
1235
|
-
self.target_coordinates = target_coordinates
|
1236
|
-
self.start()
|
1237
|
-
|
1238
|
-
def move_relative(self, motor, value: float):
|
1239
|
-
self.action = MotorActions.MOVE_RELATIVE
|
1240
|
-
self.motor = motor
|
1241
|
-
self.value = value
|
1242
|
-
self.start()
|
1243
|
-
|
1244
|
-
def run(self):
|
1245
|
-
if self.action == MotorActions.MOVE_TO_COORDINATES:
|
1246
|
-
self._move_motor_coordinate()
|
1247
|
-
elif self.action == MotorActions.MOVE_RELATIVE:
|
1248
|
-
self._move_motor_relative(self.motor, self.value)
|
1249
|
-
|
1250
|
-
def set_target_coordinates(self, target_coordinates: tuple) -> None:
|
1251
|
-
self.target_coordinates = target_coordinates
|
1252
|
-
|
1253
|
-
def _initialize_motor(self) -> None:
|
1254
|
-
self.motor_x, self.motor_y = None, None
|
1255
|
-
self.current_x, self.current_y = None, None
|
1256
|
-
|
1257
|
-
self.motors_consumer = None
|
1258
|
-
|
1259
|
-
# Get all available motors in the client
|
1260
|
-
self.all_motors = self.get_all_motors()
|
1261
|
-
self.all_motors_names = self.get_all_motors_names()
|
1262
|
-
self.retrieve_all_motors() # send motor list to GUI
|
1263
|
-
|
1264
|
-
self.target_coordinates = None
|
1265
|
-
|
1266
|
-
def _move_motor_coordinate(self) -> None:
|
1267
|
-
"""Move the motor to the specified coordinates"""
|
1268
|
-
status = scans.mv(
|
1269
|
-
self.motor_x,
|
1270
|
-
self.target_coordinates[0],
|
1271
|
-
self.motor_y,
|
1272
|
-
self.target_coordinates[1],
|
1273
|
-
relative=False,
|
1274
|
-
)
|
1275
|
-
|
1276
|
-
status.wait()
|
1277
|
-
self.move_finished.emit()
|
1278
|
-
|
1279
|
-
def _move_motor_relative(self, motor, value: float) -> None:
|
1280
|
-
status = scans.mv(motor, value, relative=True)
|
1281
|
-
|
1282
|
-
status.wait()
|
1283
|
-
self.move_finished.emit()
|
1284
|
-
|
1285
|
-
def stop_movement(self):
|
1286
|
-
queue.request_scan_abortion()
|
1287
|
-
queue.request_queue_reset()
|
1288
|
-
|
1289
|
-
@staticmethod
|
1290
|
-
def _device_status_callback_motors(msg, *, parent, **_kwargs) -> None:
|
1291
|
-
deviceMSG = msg.value
|
1292
|
-
if parent.motor_x.name in deviceMSG.content["signals"]:
|
1293
|
-
parent.current_x = deviceMSG.content["signals"][parent.motor_x.name]["value"]
|
1294
|
-
elif parent.motor_y.name in deviceMSG.content["signals"]:
|
1295
|
-
parent.current_y = deviceMSG.content["signals"][parent.motor_y.name]["value"]
|
1296
|
-
parent.coordinates_updated.emit(parent.current_x, parent.current_y)
|
1297
|
-
|
1298
|
-
|
1299
|
-
if __name__ == "__main__":
|
1300
|
-
import argparse
|
1301
|
-
|
1302
|
-
import yaml
|
1303
|
-
from bec_lib import BECClient, ServiceConfig
|
1304
|
-
|
1305
|
-
parser = argparse.ArgumentParser(description="Motor App")
|
1306
|
-
|
1307
|
-
parser.add_argument(
|
1308
|
-
"--config", "-c", help="Path to the .yaml configuration file", default="config_example.yaml"
|
1309
|
-
)
|
1310
|
-
parser.add_argument(
|
1311
|
-
"--bec-config", "-b", help="Path to the BEC .yaml configuration file", default=None
|
1312
|
-
)
|
1313
|
-
|
1314
|
-
args = parser.parse_args()
|
1315
|
-
|
1316
|
-
try:
|
1317
|
-
with open(args.config, "r") as file:
|
1318
|
-
config = yaml.safe_load(file)
|
1319
|
-
|
1320
|
-
selected_motors = config.get("selected_motors", {})
|
1321
|
-
plot_motors = config.get("plot_motors", {})
|
1322
|
-
|
1323
|
-
except FileNotFoundError:
|
1324
|
-
print(f"The file {args.config} was not found.")
|
1325
|
-
exit(1)
|
1326
|
-
except Exception as e:
|
1327
|
-
print(f"An error occurred while loading the config file: {e}")
|
1328
|
-
exit(1)
|
1329
|
-
|
1330
|
-
client = BECClient()
|
1331
|
-
|
1332
|
-
if args.bec_config:
|
1333
|
-
client.initialize(config=ServiceConfig(config_path=args.bec_config))
|
1334
|
-
|
1335
|
-
client.start()
|
1336
|
-
dev = client.device_manager.devices
|
1337
|
-
scans = client.scans
|
1338
|
-
queue = client.queue
|
1339
|
-
|
1340
|
-
app = QApplication([])
|
1341
|
-
MotorApp = MotorApp(selected_motors=selected_motors, plot_motors=plot_motors)
|
1342
|
-
window = MotorApp
|
1343
|
-
window.show()
|
1344
|
-
app.exec()
|