bec-widgets 0.82.2__py3-none-any.whl → 0.83.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 +3 -0
- CHANGELOG.md +42 -46
- PKG-INFO +2 -2
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +2 -2
- bec_widgets/qt_utils/toolbar.py +3 -1
- bec_widgets/utils/bec_connector.py +2 -0
- bec_widgets/utils/bec_widget.py +6 -0
- bec_widgets/utils/colors.py +27 -0
- bec_widgets/utils/generate_designer_plugin.py +6 -5
- bec_widgets/utils/reference_utils.py +92 -0
- bec_widgets/widgets/bec_status_box/bec_status_box.py +2 -2
- bec_widgets/widgets/console/console.py +1 -1
- bec_widgets/widgets/device_box/__init__.py +0 -0
- bec_widgets/widgets/device_box/device_box.py +197 -0
- bec_widgets/widgets/device_box/device_box.pyproject +1 -0
- bec_widgets/widgets/device_box/device_box.ui +179 -0
- bec_widgets/widgets/device_box/device_box_plugin.py +54 -0
- bec_widgets/widgets/device_box/register_device_box.py +15 -0
- bec_widgets/widgets/figure/figure.py +5 -6
- bec_widgets/widgets/figure/plots/axis_settings.py +2 -2
- bec_widgets/widgets/position_indicator/position_indicator.py +71 -0
- bec_widgets/widgets/position_indicator/position_indicator.pyproject +1 -0
- bec_widgets/widgets/position_indicator/position_indicator_plugin.py +54 -0
- bec_widgets/widgets/position_indicator/register_position_indicator.py +17 -0
- bec_widgets/widgets/scan_control/scan_control.py +2 -2
- bec_widgets/widgets/spinner/__init__.py +0 -0
- bec_widgets/widgets/spinner/register_spinner_widget.py +15 -0
- bec_widgets/widgets/spinner/spinner.py +86 -0
- bec_widgets/widgets/spinner/spinner_widget.pyproject +1 -0
- bec_widgets/widgets/spinner/spinner_widget_plugin.py +54 -0
- bec_widgets/widgets/vscode/vscode.py +0 -14
- bec_widgets/widgets/website/website.py +1 -1
- {bec_widgets-0.82.2.dist-info → bec_widgets-0.83.1.dist-info}/METADATA +2 -2
- {bec_widgets-0.82.2.dist-info → bec_widgets-0.83.1.dist-info}/RECORD +46 -24
- pyproject.toml +2 -2
- tests/references/SpinnerWidget/SpinnerWidget_darwin.png +0 -0
- tests/references/SpinnerWidget/SpinnerWidget_linux.png +0 -0
- tests/references/SpinnerWidget/SpinnerWidget_started_darwin.png +0 -0
- tests/references/SpinnerWidget/SpinnerWidget_started_linux.png +0 -0
- tests/unit_tests/client_mocks.py +9 -1
- tests/unit_tests/test_device_box.py +98 -0
- tests/unit_tests/test_spinner.py +30 -0
- tests/unit_tests/test_vscode_widget.py +27 -32
- {bec_widgets-0.82.2.dist-info → bec_widgets-0.83.1.dist-info}/WHEEL +0 -0
- {bec_widgets-0.82.2.dist-info → bec_widgets-0.83.1.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.82.2.dist-info → bec_widgets-0.83.1.dist-info}/licenses/LICENSE +0 -0
.gitlab-ci.yml
CHANGED
CHANGELOG.md
CHANGED
@@ -1,5 +1,47 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## v0.83.1 (2024-07-14)
|
4
|
+
|
5
|
+
### Fix
|
6
|
+
|
7
|
+
* fix(toolbar): default transparent background ([`eab7883`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/eab78839792f175b7ac127ca603385c6baa5ff15))
|
8
|
+
|
9
|
+
* fix: use apply_theme ([`2d4249e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2d4249e73a792fed1c2c7ab79bb8aec38c57466c))
|
10
|
+
|
11
|
+
* fix: spinner: update reference image for widget test, use apply_theme ([`63db135`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/63db1352ee883d35670b3a692dbe51d6d01872ae))
|
12
|
+
|
13
|
+
* fix: replace pyqtdarktheme by qdarkstyle, add 'apply_theme' function (in utils/colors.py) ([`8308115`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8308115f3646245d825fc47ab57297d3460bbcf5))
|
14
|
+
|
15
|
+
### Test
|
16
|
+
|
17
|
+
* test(toolbar): added reference pngs for spinner for Darwin ([`11a7204`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/11a7204c98e0bf211a8721d296b45d24a3102b97))
|
18
|
+
|
19
|
+
## v0.83.0 (2024-07-08)
|
20
|
+
|
21
|
+
### Feature
|
22
|
+
|
23
|
+
* feat: added reference utils to compare renderings of widgets ([`2988fd3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2988fd387e6b8076fffec1d57e3ccab89ddb2aeb))
|
24
|
+
|
25
|
+
* feat(widgets): added device box with spinner ([`1b017ed`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1b017edfad8e78fa079210486123976695b8915c))
|
26
|
+
|
27
|
+
* feat(designer): added option to skip the widget validation for DesignerPluginGenerator ([`41bcb80`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/41bcb801674ab6c4d6069bba34ffee09c9e665db))
|
28
|
+
|
29
|
+
### Fix
|
30
|
+
|
31
|
+
* fix(terminal): added default args to avoid designer crashes on startup ([`360d171`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/360d17135573e44b80ab517756da3c0b31daab0f))
|
32
|
+
|
33
|
+
* fix(widget): fixed widget cleanup routine ([`2b29e34`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2b29e34b52d056349647bb2fcf649b749a60d292))
|
34
|
+
|
35
|
+
* fix(bec_widget): added cleanup method to bec widget base class ([`fd8766e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fd8766ed87770661da6591aeb4df5abdaf38afc7))
|
36
|
+
|
37
|
+
* fix(website): fixed dummy input ([`903ce7d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/903ce7d46b5d37d40486d0fda92d3694d3faca62))
|
38
|
+
|
39
|
+
### Test
|
40
|
+
|
41
|
+
* test(vscode): fixed vscode tests for new cleanup routine ([`eb26e2a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/eb26e2a11b229a52efe2e6d4fb28d760d3740136))
|
42
|
+
|
43
|
+
* test(vscode): improved vscode test ([`5de8804`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5de8804da1e41eafad2472344904b3324438c13b))
|
44
|
+
|
3
45
|
## v0.82.2 (2024-07-08)
|
4
46
|
|
5
47
|
### Fix
|
@@ -99,49 +141,3 @@
|
|
99
141
|
* fix: overwrite closeEvent and call super class ([`bc0ef78`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/bc0ef7893ef100b71b62101c459655509b534a56))
|
100
142
|
|
101
143
|
## v0.79.1 (2024-07-03)
|
102
|
-
|
103
|
-
### Fix
|
104
|
-
|
105
|
-
* fix: use libdir env var to preload Python library, also for Linux platform ([`d7718d4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d7718d4dcb9728c050b6421388af4d484f3741f2))
|
106
|
-
|
107
|
-
## v0.79.0 (2024-07-03)
|
108
|
-
|
109
|
-
### Feature
|
110
|
-
|
111
|
-
* feat(motor_map_widget): standalone MotorMap Widget with toolbar + plugin ([`6e75642`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6e756420907d7093557e945bc92bc4cfc0138d07))
|
112
|
-
|
113
|
-
* feat(motor_map): method to reset history trace ([`5960918`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5960918137dd41cdeb94e50f8abc4f169cf45c11))
|
114
|
-
|
115
|
-
### Fix
|
116
|
-
|
117
|
-
* fix(toolbar): change default color to black to match BECFigure theme ([`b8774e0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b8774e0b0bc43dcd00f94f42539a778e507ca27d))
|
118
|
-
|
119
|
-
* fix(motor_map): fixed bug with residual trace after changing motors ([`aaa0d10`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/aaa0d1003d2e94b45bafe4f700852c2c05288aea))
|
120
|
-
|
121
|
-
* fix(widget_io): widget handler adjusted for spinboxes and comboboxes ([`3dc0532`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3dc0532df05b6ec0a2522107fa0b1e210ce7d91b))
|
122
|
-
|
123
|
-
### Refactor
|
124
|
-
|
125
|
-
* refactor(toolbar): cleanup and adjusted colors ([`96863ad`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/96863adf53c15112645d20eb6200733617801c6d))
|
126
|
-
|
127
|
-
## v0.78.1 (2024-07-02)
|
128
|
-
|
129
|
-
### Fix
|
130
|
-
|
131
|
-
* fix(ui_loader): ui loader is compatible with bec plugins ([`b787759`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b787759f44486dc7af2c03811efb156041e4b6cb))
|
132
|
-
|
133
|
-
## v0.78.0 (2024-07-02)
|
134
|
-
|
135
|
-
### Feature
|
136
|
-
|
137
|
-
* feat(color_button): patched ColorButton from pyqtgraph to be able to be opened in another QDialog ([`c36bb80`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c36bb80d6a4939802a4a1c8e5452c7b94bac185e))
|
138
|
-
|
139
|
-
## v0.77.0 (2024-07-02)
|
140
|
-
|
141
|
-
### Fix
|
142
|
-
|
143
|
-
* fix(waveform): scatter 2D brush error ([`215d59c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/215d59c8bfe7fda9aff8cec8353bef9e1ce2eca1))
|
144
|
-
|
145
|
-
* fix(figure): API cleanup ([`008a33a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/008a33a9b192473cc58e90cd6d98c5bcb5f7b8c0))
|
146
|
-
|
147
|
-
* fix(figure): if/else logic corrected in subplot_factory ([`3e78723`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3e787234c7274b0698423d7bf9a4c54ec46bad5f))
|
PKG-INFO
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: bec_widgets
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.83.1
|
4
4
|
Summary: BEC Widgets
|
5
5
|
Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
|
6
6
|
Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
|
@@ -14,9 +14,9 @@ Requires-Dist: bec-lib~=2.16
|
|
14
14
|
Requires-Dist: black~=24.0
|
15
15
|
Requires-Dist: isort>=5.13.2,~=5.13
|
16
16
|
Requires-Dist: pydantic~=2.0
|
17
|
-
Requires-Dist: pyqtdarktheme~=2.1
|
18
17
|
Requires-Dist: pyqtgraph~=0.13
|
19
18
|
Requires-Dist: pyte
|
19
|
+
Requires-Dist: qdarkstyle>=3.2.2
|
20
20
|
Requires-Dist: qtconsole>=5.5.1,~=5.5
|
21
21
|
Requires-Dist: qtpy~=2.4
|
22
22
|
Provides-Extra: dev
|
@@ -2,7 +2,6 @@ import os
|
|
2
2
|
|
3
3
|
import numpy as np
|
4
4
|
import pyqtgraph as pg
|
5
|
-
import qdarktheme
|
6
5
|
from qtconsole.inprocess import QtInProcessKernelManager
|
7
6
|
from qtconsole.rich_jupyter_widget import RichJupyterWidget
|
8
7
|
from qtpy.QtCore import QSize
|
@@ -10,6 +9,7 @@ from qtpy.QtGui import QIcon
|
|
10
9
|
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
11
10
|
|
12
11
|
from bec_widgets.utils import BECDispatcher, UILoader
|
12
|
+
from bec_widgets.utils.colors import apply_theme
|
13
13
|
from bec_widgets.widgets.dock.dock_area import BECDockArea
|
14
14
|
from bec_widgets.widgets.figure import BECFigure
|
15
15
|
from bec_widgets.widgets.jupyter_console.jupyter_console import BECJupyterConsole
|
@@ -147,7 +147,7 @@ if __name__ == "__main__": # pragma: no cover
|
|
147
147
|
app = QApplication(sys.argv)
|
148
148
|
app.setApplicationName("Jupyter Console")
|
149
149
|
app.setApplicationDisplayName("Jupyter Console")
|
150
|
-
|
150
|
+
apply_theme("dark")
|
151
151
|
icon = QIcon()
|
152
152
|
icon.addFile(os.path.join(module_path, "assets", "terminal_icon.png"), size=QSize(48, 48))
|
153
153
|
app.setWindowIcon(icon)
|
bec_widgets/qt_utils/toolbar.py
CHANGED
@@ -26,7 +26,9 @@ class ModularToolBar(QToolBar):
|
|
26
26
|
color (str, optional): The background color of the toolbar. Defaults to "black".
|
27
27
|
"""
|
28
28
|
|
29
|
-
def __init__(
|
29
|
+
def __init__(
|
30
|
+
self, parent=None, actions=None, target_widget=None, color: str = "rgba(255, 255, 255, 0)"
|
31
|
+
):
|
30
32
|
super().__init__(parent)
|
31
33
|
|
32
34
|
self.widgets = defaultdict(dict)
|
@@ -289,6 +289,8 @@ class BECConnector(BECWidget):
|
|
289
289
|
print("No more connections. Shutting down GUI BEC client.")
|
290
290
|
self.bec_dispatcher.disconnect_all()
|
291
291
|
self.client.shutdown()
|
292
|
+
if hasattr(super(), "cleanup"):
|
293
|
+
super().cleanup()
|
292
294
|
|
293
295
|
# def closeEvent(self, event):
|
294
296
|
# self.cleanup()
|
bec_widgets/utils/bec_widget.py
CHANGED
bec_widgets/utils/colors.py
CHANGED
@@ -1,10 +1,37 @@
|
|
1
|
+
import itertools
|
1
2
|
import re
|
2
3
|
from typing import Literal
|
3
4
|
|
4
5
|
import numpy as np
|
5
6
|
import pyqtgraph as pg
|
7
|
+
import qdarkstyle
|
6
8
|
from pydantic_core import PydanticCustomError
|
9
|
+
from qdarkstyle import DarkPalette, LightPalette
|
7
10
|
from qtpy.QtGui import QColor
|
11
|
+
from qtpy.QtWidgets import QApplication
|
12
|
+
|
13
|
+
CURRENT_THEME = "dark"
|
14
|
+
|
15
|
+
|
16
|
+
def get_theme_palette():
|
17
|
+
return DarkPalette if CURRENT_THEME == "dark" else LightPalette
|
18
|
+
|
19
|
+
|
20
|
+
def apply_theme(theme: Literal["dark", "light"]):
|
21
|
+
global CURRENT_THEME
|
22
|
+
CURRENT_THEME = theme
|
23
|
+
|
24
|
+
app = QApplication.instance()
|
25
|
+
# go through all pyqtgraph widgets and set background
|
26
|
+
children = itertools.chain.from_iterable(
|
27
|
+
top.findChildren(pg.GraphicsLayoutWidget) for top in app.topLevelWidgets()
|
28
|
+
)
|
29
|
+
for pg_widget in children:
|
30
|
+
pg_widget.setBackground("k" if theme == "dark" else "w")
|
31
|
+
|
32
|
+
# now define stylesheet according to theme and apply it
|
33
|
+
style = qdarkstyle.load_stylesheet(palette=get_theme_palette())
|
34
|
+
app.setStyleSheet(style)
|
8
35
|
|
9
36
|
|
10
37
|
class Colors:
|
@@ -58,11 +58,12 @@ class DesignerPluginGenerator:
|
|
58
58
|
os.path.dirname(os.path.abspath(__file__)), "plugin_templates"
|
59
59
|
)
|
60
60
|
|
61
|
-
def run(self):
|
61
|
+
def run(self, validate=True):
|
62
62
|
if self._excluded:
|
63
63
|
print(f"Plugin {self.widget.__name__} is excluded from generation.")
|
64
64
|
return
|
65
|
-
|
65
|
+
if validate:
|
66
|
+
self._check_class_validity()
|
66
67
|
self._load_templates()
|
67
68
|
self._write_templates()
|
68
69
|
|
@@ -142,7 +143,7 @@ class DesignerPluginGenerator:
|
|
142
143
|
|
143
144
|
if __name__ == "__main__": # pragma: no cover
|
144
145
|
# from bec_widgets.widgets.bec_queue.bec_queue import BECQueue
|
145
|
-
from bec_widgets.widgets.
|
146
|
+
from bec_widgets.widgets.spinner.spinner import SpinnerWidget
|
146
147
|
|
147
|
-
generator = DesignerPluginGenerator(
|
148
|
-
generator.run()
|
148
|
+
generator = DesignerPluginGenerator(SpinnerWidget)
|
149
|
+
generator.run(validate=False)
|
@@ -0,0 +1,92 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
|
4
|
+
from PIL import Image, ImageChops
|
5
|
+
from qtpy.QtGui import QPixmap
|
6
|
+
|
7
|
+
import bec_widgets
|
8
|
+
|
9
|
+
REFERENCE_DIR = os.path.join(
|
10
|
+
os.path.dirname(os.path.dirname(bec_widgets.__file__)), "tests/references"
|
11
|
+
)
|
12
|
+
REFERENCE_DIR_FAILURES = os.path.join(
|
13
|
+
os.path.dirname(os.path.dirname(bec_widgets.__file__)), "tests/reference_failures"
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
def compare_images(image1_path: str, reference_image_path: str):
|
18
|
+
"""
|
19
|
+
Load two images and compare them pixel by pixel
|
20
|
+
|
21
|
+
Args:
|
22
|
+
image1_path(str): The path to the first image
|
23
|
+
reference_image_path(str): The path to the reference image
|
24
|
+
|
25
|
+
Raises:
|
26
|
+
ValueError: If the images are different
|
27
|
+
"""
|
28
|
+
image1 = Image.open(image1_path)
|
29
|
+
image2 = Image.open(reference_image_path)
|
30
|
+
if image1.size != image2.size:
|
31
|
+
raise ValueError("Image size has changed")
|
32
|
+
diff = ImageChops.difference(image1, image2)
|
33
|
+
if diff.getbbox():
|
34
|
+
# copy image1 to the reference directory to upload as artifact
|
35
|
+
os.makedirs(REFERENCE_DIR_FAILURES, exist_ok=True)
|
36
|
+
image_name = os.path.join(REFERENCE_DIR_FAILURES, os.path.basename(image1_path))
|
37
|
+
image1.save(image_name)
|
38
|
+
print(f"Image saved to {image_name}")
|
39
|
+
|
40
|
+
raise ValueError("Images are different")
|
41
|
+
|
42
|
+
|
43
|
+
def snap_and_compare(widget: any, output_directory: str, suffix: str = ""):
|
44
|
+
"""
|
45
|
+
Save a rendering of a widget and compare it to a reference image
|
46
|
+
|
47
|
+
Args:
|
48
|
+
widget(any): The widget to render
|
49
|
+
output_directory(str): The directory to save the image to
|
50
|
+
suffix(str): A suffix to append to the image name
|
51
|
+
|
52
|
+
Raises:
|
53
|
+
ValueError: If the images are different
|
54
|
+
|
55
|
+
Examples:
|
56
|
+
snap_and_compare(widget, tmpdir, suffix="started")
|
57
|
+
|
58
|
+
"""
|
59
|
+
|
60
|
+
if not isinstance(output_directory, str):
|
61
|
+
output_directory = str(output_directory)
|
62
|
+
|
63
|
+
os_suffix = sys.platform
|
64
|
+
|
65
|
+
name = (
|
66
|
+
f"{widget.__class__.__name__}_{suffix}_{os_suffix}.png"
|
67
|
+
if suffix
|
68
|
+
else f"{widget.__class__.__name__}_{os_suffix}.png"
|
69
|
+
)
|
70
|
+
|
71
|
+
# Save the widget to a pixmap
|
72
|
+
test_image_path = os.path.join(output_directory, name)
|
73
|
+
pixmap = QPixmap(widget.size())
|
74
|
+
widget.render(pixmap)
|
75
|
+
pixmap.save(test_image_path)
|
76
|
+
|
77
|
+
try:
|
78
|
+
reference_path = os.path.join(REFERENCE_DIR, f"{widget.__class__.__name__}")
|
79
|
+
reference_image_path = os.path.join(reference_path, name)
|
80
|
+
|
81
|
+
if not os.path.exists(reference_image_path):
|
82
|
+
raise ValueError(f"Reference image not found: {reference_image_path}")
|
83
|
+
|
84
|
+
compare_images(test_image_path, reference_image_path)
|
85
|
+
|
86
|
+
except ValueError:
|
87
|
+
image = Image.open(test_image_path)
|
88
|
+
os.makedirs(REFERENCE_DIR_FAILURES, exist_ok=True)
|
89
|
+
image_name = os.path.join(REFERENCE_DIR_FAILURES, name)
|
90
|
+
image.save(image_name)
|
91
|
+
print(f"Image saved to {image_name}")
|
92
|
+
raise
|
@@ -9,12 +9,12 @@ from collections import defaultdict
|
|
9
9
|
from dataclasses import dataclass
|
10
10
|
from typing import TYPE_CHECKING
|
11
11
|
|
12
|
-
import qdarktheme
|
13
12
|
from bec_lib.utils.import_utils import lazy_import_from
|
14
13
|
from qtpy.QtCore import QObject, QTimer, Signal, Slot
|
15
14
|
from qtpy.QtWidgets import QHBoxLayout, QTreeWidget, QTreeWidgetItem, QWidget
|
16
15
|
|
17
16
|
from bec_widgets.utils.bec_connector import BECConnector
|
17
|
+
from bec_widgets.utils.colors import apply_theme
|
18
18
|
from bec_widgets.widgets.bec_status_box.status_item import StatusItem
|
19
19
|
|
20
20
|
if TYPE_CHECKING:
|
@@ -306,7 +306,7 @@ def main():
|
|
306
306
|
from qtpy.QtWidgets import QApplication
|
307
307
|
|
308
308
|
app = QApplication(sys.argv)
|
309
|
-
|
309
|
+
apply_theme("dark")
|
310
310
|
main_window = BECStatusBox()
|
311
311
|
main_window.show()
|
312
312
|
sys.exit(app.exec())
|
@@ -222,7 +222,7 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
|
|
222
222
|
Start ``Backend`` process and render Pyte output as text.
|
223
223
|
"""
|
224
224
|
|
225
|
-
def __init__(self, parent, numColumns, numLines, **kwargs):
|
225
|
+
def __init__(self, parent, numColumns=125, numLines=50, **kwargs):
|
226
226
|
super().__init__(parent)
|
227
227
|
|
228
228
|
# file descriptor to communicate with the subprocess
|
File without changes
|
@@ -0,0 +1,197 @@
|
|
1
|
+
import os
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
from bec_lib.endpoints import MessageEndpoints
|
5
|
+
from bec_lib.messages import ScanQueueMessage
|
6
|
+
from qtpy.QtCore import Property, Signal, Slot
|
7
|
+
from qtpy.QtGui import QDoubleValidator
|
8
|
+
from qtpy.QtWidgets import QDoubleSpinBox, QVBoxLayout, QWidget
|
9
|
+
|
10
|
+
from bec_widgets.utils import UILoader
|
11
|
+
from bec_widgets.utils.bec_connector import BECConnector
|
12
|
+
from bec_widgets.utils.colors import apply_theme
|
13
|
+
|
14
|
+
|
15
|
+
class DeviceBox(BECConnector, QWidget):
|
16
|
+
device_changed = Signal(str, str)
|
17
|
+
|
18
|
+
def __init__(self, parent=None, device=None, *args, **kwargs):
|
19
|
+
super().__init__(*args, **kwargs)
|
20
|
+
QWidget.__init__(self, parent=parent)
|
21
|
+
self.get_bec_shortcuts()
|
22
|
+
self._device = ""
|
23
|
+
self._limits = None
|
24
|
+
|
25
|
+
self.init_ui()
|
26
|
+
|
27
|
+
if device is not None:
|
28
|
+
self.device = device
|
29
|
+
self.init_device()
|
30
|
+
|
31
|
+
def init_ui(self):
|
32
|
+
self.device_changed.connect(self.on_device_change)
|
33
|
+
|
34
|
+
current_path = os.path.dirname(__file__)
|
35
|
+
self.ui = UILoader(self).loader(os.path.join(current_path, "device_box.ui"))
|
36
|
+
|
37
|
+
self.layout = QVBoxLayout(self)
|
38
|
+
self.layout.addWidget(self.ui)
|
39
|
+
self.layout.setSpacing(0)
|
40
|
+
self.layout.setContentsMargins(0, 0, 0, 0)
|
41
|
+
|
42
|
+
# fix the size of the device box
|
43
|
+
db = self.ui.device_box
|
44
|
+
db.setFixedHeight(234)
|
45
|
+
db.setFixedWidth(224)
|
46
|
+
|
47
|
+
self.ui.step_size.setStepType(QDoubleSpinBox.AdaptiveDecimalStepType)
|
48
|
+
self.ui.stop.clicked.connect(self.on_stop)
|
49
|
+
self.ui.tweak_right.clicked.connect(self.on_tweak_right)
|
50
|
+
self.ui.tweak_right.setToolTip("Tweak right")
|
51
|
+
self.ui.tweak_left.clicked.connect(self.on_tweak_left)
|
52
|
+
self.ui.tweak_left.setToolTip("Tweak left")
|
53
|
+
self.ui.setpoint.returnPressed.connect(self.on_setpoint_change)
|
54
|
+
|
55
|
+
self.setpoint_validator = QDoubleValidator()
|
56
|
+
self.ui.setpoint.setValidator(self.setpoint_validator)
|
57
|
+
self.ui.spinner_widget.start()
|
58
|
+
|
59
|
+
def init_device(self):
|
60
|
+
if self.device in self.dev:
|
61
|
+
data = self.dev[self.device].read()
|
62
|
+
self.on_device_readback({"signals": data}, {})
|
63
|
+
|
64
|
+
@Property(str)
|
65
|
+
def device(self):
|
66
|
+
return self._device
|
67
|
+
|
68
|
+
@device.setter
|
69
|
+
def device(self, value):
|
70
|
+
if not value or not isinstance(value, str):
|
71
|
+
return
|
72
|
+
old_device = self._device
|
73
|
+
self._device = value
|
74
|
+
self.device_changed.emit(old_device, value)
|
75
|
+
|
76
|
+
@Slot(str, str)
|
77
|
+
def on_device_change(self, old_device: str, new_device: str):
|
78
|
+
if new_device not in self.dev:
|
79
|
+
print(f"Device {new_device} not found in the device list")
|
80
|
+
return
|
81
|
+
print(f"Device changed from {old_device} to {new_device}")
|
82
|
+
self.init_device()
|
83
|
+
self.bec_dispatcher.disconnect_slot(
|
84
|
+
self.on_device_readback, MessageEndpoints.device_readback(old_device)
|
85
|
+
)
|
86
|
+
self.bec_dispatcher.connect_slot(
|
87
|
+
self.on_device_readback, MessageEndpoints.device_readback(new_device)
|
88
|
+
)
|
89
|
+
self.ui.device_box.setTitle(new_device)
|
90
|
+
self.ui.readback.setToolTip(f"{self.device} readback")
|
91
|
+
self.ui.setpoint.setToolTip(f"{self.device} setpoint")
|
92
|
+
self.ui.step_size.setToolTip(f"Step size for {new_device}")
|
93
|
+
|
94
|
+
precision = self.dev[new_device].precision
|
95
|
+
if precision is not None:
|
96
|
+
self.ui.step_size.setDecimals(precision)
|
97
|
+
self.ui.step_size.setValue(10**-precision * 10)
|
98
|
+
|
99
|
+
@Slot(dict, dict)
|
100
|
+
def on_device_readback(self, msg_content: dict, metadata: dict):
|
101
|
+
signals = msg_content.get("signals", {})
|
102
|
+
# pylint: disable=protected-access
|
103
|
+
hinted_signals = self.dev[self.device]._hints
|
104
|
+
precision = self.dev[self.device].precision
|
105
|
+
|
106
|
+
readback_val = None
|
107
|
+
setpoint_val = None
|
108
|
+
|
109
|
+
if len(hinted_signals) == 1:
|
110
|
+
signal = hinted_signals[0]
|
111
|
+
readback_val = signals.get(signal, {}).get("value")
|
112
|
+
|
113
|
+
if f"{self.device}_setpoint" in signals:
|
114
|
+
setpoint_val = signals.get(f"{self.device}_setpoint", {}).get("value")
|
115
|
+
|
116
|
+
if f"{self.device}_motor_is_moving" in signals:
|
117
|
+
is_moving = signals.get(f"{self.device}_motor_is_moving", {}).get("value")
|
118
|
+
if is_moving:
|
119
|
+
self.ui.spinner_widget.start()
|
120
|
+
self.ui.spinner_widget.setToolTip("Device is moving")
|
121
|
+
else:
|
122
|
+
self.ui.spinner_widget.stop()
|
123
|
+
self.ui.spinner_widget.setToolTip("Device is idle")
|
124
|
+
|
125
|
+
if readback_val is not None:
|
126
|
+
self.ui.readback.setText(f"{readback_val:.{precision}f}")
|
127
|
+
|
128
|
+
if setpoint_val is not None:
|
129
|
+
self.ui.setpoint.setText(f"{setpoint_val:.{precision}f}")
|
130
|
+
|
131
|
+
limits = self.dev[self.device].limits
|
132
|
+
self.update_limits(limits)
|
133
|
+
if limits is not None and readback_val is not None and limits[0] != limits[1]:
|
134
|
+
pos = (readback_val - limits[0]) / (limits[1] - limits[0])
|
135
|
+
self.ui.position_indicator.on_position_update(pos)
|
136
|
+
|
137
|
+
def update_limits(self, limits):
|
138
|
+
if limits == self._limits:
|
139
|
+
return
|
140
|
+
self._limits = limits
|
141
|
+
if limits is not None and limits[0] != limits[1]:
|
142
|
+
self.ui.position_indicator.setToolTip(f"Min: {limits[0]}, Max: {limits[1]}")
|
143
|
+
self.setpoint_validator.setRange(limits[0], limits[1])
|
144
|
+
else:
|
145
|
+
self.ui.position_indicator.setToolTip("No limits set")
|
146
|
+
self.setpoint_validator.setRange(float("-inf"), float("inf"))
|
147
|
+
|
148
|
+
@Slot()
|
149
|
+
def on_stop(self):
|
150
|
+
request_id = str(uuid.uuid4())
|
151
|
+
params = {
|
152
|
+
"device": self.device,
|
153
|
+
"rpc_id": request_id,
|
154
|
+
"func": "stop",
|
155
|
+
"args": [],
|
156
|
+
"kwargs": {},
|
157
|
+
}
|
158
|
+
msg = ScanQueueMessage(
|
159
|
+
scan_type="device_rpc",
|
160
|
+
parameter=params,
|
161
|
+
queue="emergency",
|
162
|
+
metadata={"RID": request_id, "response": False},
|
163
|
+
)
|
164
|
+
self.client.connector.send(MessageEndpoints.scan_queue_request(), msg)
|
165
|
+
|
166
|
+
@property
|
167
|
+
def step_size(self):
|
168
|
+
return self.ui.step_size.value()
|
169
|
+
|
170
|
+
@Slot()
|
171
|
+
def on_tweak_right(self):
|
172
|
+
self.dev[self.device].move(self.step_size, relative=True)
|
173
|
+
|
174
|
+
@Slot()
|
175
|
+
def on_tweak_left(self):
|
176
|
+
self.dev[self.device].move(-self.step_size, relative=True)
|
177
|
+
|
178
|
+
@Slot()
|
179
|
+
def on_setpoint_change(self):
|
180
|
+
self.ui.setpoint.clearFocus()
|
181
|
+
setpoint = self.ui.setpoint.text()
|
182
|
+
self.dev[self.device].move(float(setpoint), relative=False)
|
183
|
+
self.ui.tweak_left.setToolTip(f"Tweak left by {self.step_size}")
|
184
|
+
self.ui.tweak_right.setToolTip(f"Tweak right by {self.step_size}")
|
185
|
+
|
186
|
+
|
187
|
+
if __name__ == "__main__": # pragma: no cover
|
188
|
+
import sys
|
189
|
+
|
190
|
+
from qtpy.QtWidgets import QApplication
|
191
|
+
|
192
|
+
app = QApplication(sys.argv)
|
193
|
+
apply_theme("light")
|
194
|
+
widget = DeviceBox(device="samx")
|
195
|
+
|
196
|
+
widget.show()
|
197
|
+
sys.exit(app.exec_())
|
@@ -0,0 +1 @@
|
|
1
|
+
{'files': ['device_box.py']}
|