mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-04-08 09:47:52 +02:00
Compare commits
2 Commits
v1.5.1
...
fix/temp-d
| Author | SHA1 | Date | |
|---|---|---|---|
| 65dceead0c | |||
| 20125139f0 |
73
CHANGELOG.md
73
CHANGELOG.md
@@ -1,46 +1,6 @@
|
||||
# CHANGELOG
|
||||
|
||||
|
||||
## v1.5.1 (2024-11-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **plugin_utils**: Plugin utils are able to detect classes for plugin creation based on class
|
||||
attribute rather than if it is top level widget
|
||||
([`7a1b874`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7a1b8748a433f854671ac95f2eaf4604e6b8df20))
|
||||
|
||||
### Refactoring
|
||||
|
||||
- **widgets**: Widget module structure reorganised
|
||||
([`aab0229`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/aab0229a4067ad626de919e38a5c8a2e9e7b03c2))
|
||||
|
||||
|
||||
## v1.5.0 (2024-11-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **crosshair**: Crosshair adapted for multi waveform widget
|
||||
([`0cd85ed`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0cd85ed9fa5b67a6ecce89985cd4f54b7bbe3a4b))
|
||||
|
||||
### Documentation
|
||||
|
||||
- **multi_waveform**: Docs added
|
||||
([`42d4f18`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/42d4f182f790a97687ca3b6d0e72866070a89767))
|
||||
|
||||
### Features
|
||||
|
||||
- **multi-waveform**: New widget added
|
||||
([`f3a39a6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f3a39a69e29d490b3023a508ced18028c4205772))
|
||||
|
||||
|
||||
## v1.4.1 (2024-11-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **positioner_box**: Adjusted default signals
|
||||
([`8e5c0ad`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8e5c0ad8c8eff5a9308169bc663d2b7230f0ebb1))
|
||||
|
||||
|
||||
## v1.4.0 (2024-11-11)
|
||||
|
||||
### Bug Fixes
|
||||
@@ -196,6 +156,9 @@
|
||||
- Alignment 1D update, make app window a main window (in .ui file)
|
||||
([`0015f0e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0015f0e2d62adc02d3ef334e1f6dbb2d0288fec6))
|
||||
|
||||
- Set (Minimum, Fixed) size policy on Stop button
|
||||
([`523cc43`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/523cc435725b10b7d59a4477a1aaa24a1f3e37a2))
|
||||
|
||||
### Features
|
||||
|
||||
- New PositionerGroup widget
|
||||
@@ -207,6 +170,15 @@
|
||||
This property tells if expand should show a popup (by default), or if the widget should expand
|
||||
in-place
|
||||
|
||||
- Positionerbox with a popup view
|
||||
([`2615787`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/261578796f1de8ca9cab9b91659bc1484f7aa89d))
|
||||
|
||||
- Emit 'device_selected' and 'scan_axis' from scan control widget
|
||||
([`0b9b1a3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0b9b1a3c89a98505079f7d4078915b7bbfaa1e23))
|
||||
|
||||
- New 'device_selected' signals to ScanControl, ScanGroupBox, DeviceLineEdit
|
||||
([`9801d27`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9801d2769eb0ee95c94ec0c011e1dac1407142ae))
|
||||
|
||||
### Refactoring
|
||||
|
||||
- Redesign of scan selection and scan control boxes
|
||||
@@ -214,3 +186,24 @@ This property tells if expand should show a popup (by default), or if the widget
|
||||
|
||||
- Move add/remove bundle to scan group box
|
||||
([`e3d0a7b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e3d0a7bbf9918dc16eb7227a178c310256ce570d))
|
||||
|
||||
|
||||
## v0.118.0 (2024-10-13)
|
||||
|
||||
### Documentation
|
||||
|
||||
- **sphinx-build**: Adjusted pyside verion
|
||||
([`b236951`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b23695167ab969f754a058ffdccca2b40f00a008))
|
||||
|
||||
### Features
|
||||
|
||||
- **image**: Image widget can take data from monitor_1d endpoint
|
||||
([`9ef1d1c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9ef1d1c9ac2178d9fa2e655942208f8abbdf5c1b))
|
||||
|
||||
|
||||
## v0.117.1 (2024-10-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **FPS**: Qtimer cleanup leaking
|
||||
([`3a22392`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3a2239278075de7489ad10a58c31d7d89715e221))
|
||||
|
||||
@@ -5,23 +5,36 @@ It is a preliminary version of the GUI, which will be added to the main branch a
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from bec_lib.device import Positioner as BECPositioner
|
||||
from bec_lib.device import Signal as BECSignal
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import QSize
|
||||
from qtpy.QtCore import QSize, Signal
|
||||
from qtpy.QtGui import QIcon
|
||||
from qtpy.QtWidgets import QApplication
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QDoubleSpinBox,
|
||||
QMainWindow,
|
||||
QPushButton,
|
||||
QSpinBox,
|
||||
)
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
|
||||
from bec_widgets.qt_utils.toolbar import WidgetAction
|
||||
from bec_widgets.utils import UILoader
|
||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_widgets.widgets.control.buttons.stop_button.stop_button import StopButton
|
||||
from bec_widgets.widgets.control.device_control.positioner_group import PositionerGroup
|
||||
from bec_widgets.widgets.dap.lmfit_dialog import LMFitDialog
|
||||
from bec_widgets.widgets.plots.waveform import BECWaveformWidget
|
||||
from bec_widgets.widgets.progress.bec_progressbar.bec_progressbar import BECProgressBar
|
||||
from bec_widgets.widgets.bec_progressbar.bec_progressbar import BECProgressBar
|
||||
from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit
|
||||
from bec_widgets.widgets.lmfit_dialog.lmfit_dialog import LMFitDialog
|
||||
from bec_widgets.widgets.positioner_box.positioner_box import PositionerBox
|
||||
from bec_widgets.widgets.positioner_group.positioner_group import PositionerGroup
|
||||
from bec_widgets.widgets.stop_button.stop_button import StopButton
|
||||
from bec_widgets.widgets.toggle.toggle import ToggleSwitch
|
||||
from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
|
||||
|
||||
MODULE_PATH = os.path.dirname(bec_widgets.__file__)
|
||||
logger = bec_logger.logger
|
||||
|
||||
@@ -15,6 +15,38 @@ class Widgets(str, enum.Enum):
|
||||
Enum for the available widgets.
|
||||
"""
|
||||
|
||||
AbortButton = "AbortButton"
|
||||
BECColorMapWidget = "BECColorMapWidget"
|
||||
BECDock = "BECDock"
|
||||
BECDockArea = "BECDockArea"
|
||||
BECFigure = "BECFigure"
|
||||
BECImageWidget = "BECImageWidget"
|
||||
BECMotorMapWidget = "BECMotorMapWidget"
|
||||
BECProgressBar = "BECProgressBar"
|
||||
BECQueue = "BECQueue"
|
||||
BECStatusBox = "BECStatusBox"
|
||||
BECWaveformWidget = "BECWaveformWidget"
|
||||
DapComboBox = "DapComboBox"
|
||||
DarkModeButton = "DarkModeButton"
|
||||
DeviceBrowser = "DeviceBrowser"
|
||||
DeviceComboBox = "DeviceComboBox"
|
||||
DeviceLineEdit = "DeviceLineEdit"
|
||||
LMFitDialog = "LMFitDialog"
|
||||
PositionIndicator = "PositionIndicator"
|
||||
PositionerBox = "PositionerBox"
|
||||
PositionerControlLine = "PositionerControlLine"
|
||||
PositionerGroup = "PositionerGroup"
|
||||
ResetButton = "ResetButton"
|
||||
ResumeButton = "ResumeButton"
|
||||
RingProgressBar = "RingProgressBar"
|
||||
ScanControl = "ScanControl"
|
||||
SignalComboBox = "SignalComboBox"
|
||||
SignalLineEdit = "SignalLineEdit"
|
||||
StopButton = "StopButton"
|
||||
TextBox = "TextBox"
|
||||
VSCodeEditor = "VSCodeEditor"
|
||||
WebsiteWidget = "WebsiteWidget"
|
||||
|
||||
|
||||
class AbortButton(RPCBase):
|
||||
@property
|
||||
@@ -304,6 +336,13 @@ class BECDock(RPCBase):
|
||||
Detach the dock from the parent dock area.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def close(self):
|
||||
"""
|
||||
Close the dock area and cleanup.
|
||||
Has to be implemented to overwrite pyqtgraph event accept in Container close.
|
||||
"""
|
||||
|
||||
|
||||
class BECDockArea(RPCBase, BECGuiClientMixin):
|
||||
@property
|
||||
@@ -362,6 +401,7 @@ class BECDockArea(RPCBase, BECGuiClientMixin):
|
||||
name: "str" = None,
|
||||
position: "Literal['bottom', 'top', 'left', 'right', 'above', 'below']" = None,
|
||||
relative_to: "BECDock | None" = None,
|
||||
temporary: "bool" = False,
|
||||
closable: "bool" = True,
|
||||
floating: "bool" = False,
|
||||
prefix: "str" = "dock",
|
||||
@@ -378,6 +418,7 @@ class BECDockArea(RPCBase, BECGuiClientMixin):
|
||||
name(str): The name of the dock to be displayed and for further references. Has to be unique.
|
||||
position(Literal["bottom", "top", "left", "right", "above", "below"]): The position of the dock.
|
||||
relative_to(BECDock): The dock to which the new dock should be added relative to.
|
||||
temp(bool): Whether the dock is temporary. Upon closing the dock is not returned to the parent dock area.
|
||||
closable(bool): Whether the dock is closable.
|
||||
floating(bool): Whether the dock is detached after creating.
|
||||
prefix(str): The prefix for the dock name if no name is provided.
|
||||
@@ -1542,436 +1583,6 @@ class BECMotorMapWidget(RPCBase):
|
||||
"""
|
||||
|
||||
|
||||
class BECMultiWaveform(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def curves(self) -> collections.deque:
|
||||
"""
|
||||
Get the curves of the plot widget as a deque.
|
||||
Returns:
|
||||
deque: Deque of curves.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_monitor(self, monitor: str):
|
||||
"""
|
||||
Set the monitor for the plot widget.
|
||||
Args:
|
||||
monitor (str): The monitor to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_opacity(self, opacity: int):
|
||||
"""
|
||||
Set the opacity of the curve on the plot.
|
||||
|
||||
Args:
|
||||
opacity(int): The opacity of the curve. 0-100.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_curve_limit(self, max_trace: int, flush_buffer: bool = False):
|
||||
"""
|
||||
Set the maximum number of traces to display on the plot.
|
||||
|
||||
Args:
|
||||
max_trace (int): The maximum number of traces to display.
|
||||
flush_buffer (bool): Flush the buffer.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_curve_highlight(self, index: int):
|
||||
"""
|
||||
Set the curve highlight based on visible curves.
|
||||
|
||||
Args:
|
||||
index (int): The index of the curve to highlight among visible curves.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_colormap(self, colormap: str):
|
||||
"""
|
||||
Set the colormap for the curves.
|
||||
|
||||
Args:
|
||||
colormap(str): Colormap for the curves.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set(self, **kwargs) -> "None":
|
||||
"""
|
||||
Set the properties of the plot widget.
|
||||
|
||||
Args:
|
||||
**kwargs: Keyword arguments for the properties to be set.
|
||||
|
||||
Possible properties:
|
||||
- title: str
|
||||
- x_label: str
|
||||
- y_label: str
|
||||
- x_scale: Literal["linear", "log"]
|
||||
- y_scale: Literal["linear", "log"]
|
||||
- x_lim: tuple
|
||||
- y_lim: tuple
|
||||
- legend_label_size: int
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_title(self, title: "str", size: "int" = None):
|
||||
"""
|
||||
Set the title of the plot widget.
|
||||
|
||||
Args:
|
||||
title(str): Title of the plot widget.
|
||||
size(int): Font size of the title.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_x_label(self, label: "str", size: "int" = None):
|
||||
"""
|
||||
Set the label of the x-axis.
|
||||
|
||||
Args:
|
||||
label(str): Label of the x-axis.
|
||||
size(int): Font size of the label.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_y_label(self, label: "str", size: "int" = None):
|
||||
"""
|
||||
Set the label of the y-axis.
|
||||
|
||||
Args:
|
||||
label(str): Label of the y-axis.
|
||||
size(int): Font size of the label.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_x_scale(self, scale: "Literal['linear', 'log']" = "linear"):
|
||||
"""
|
||||
Set the scale of the x-axis.
|
||||
|
||||
Args:
|
||||
scale(Literal["linear", "log"]): Scale of the x-axis.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_y_scale(self, scale: "Literal['linear', 'log']" = "linear"):
|
||||
"""
|
||||
Set the scale of the y-axis.
|
||||
|
||||
Args:
|
||||
scale(Literal["linear", "log"]): Scale of the y-axis.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_x_lim(self, *args) -> "None":
|
||||
"""
|
||||
Set the limits of the x-axis. This method can accept either two separate arguments
|
||||
for the minimum and maximum x-axis values, or a single tuple containing both limits.
|
||||
|
||||
Usage:
|
||||
set_x_lim(x_min, x_max)
|
||||
set_x_lim((x_min, x_max))
|
||||
|
||||
Args:
|
||||
*args: A variable number of arguments. Can be two integers (x_min and x_max)
|
||||
or a single tuple with two integers.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_y_lim(self, *args) -> "None":
|
||||
"""
|
||||
Set the limits of the y-axis. This method can accept either two separate arguments
|
||||
for the minimum and maximum y-axis values, or a single tuple containing both limits.
|
||||
|
||||
Usage:
|
||||
set_y_lim(y_min, y_max)
|
||||
set_y_lim((y_min, y_max))
|
||||
|
||||
Args:
|
||||
*args: A variable number of arguments. Can be two integers (y_min and y_max)
|
||||
or a single tuple with two integers.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_grid(self, x: "bool" = False, y: "bool" = False):
|
||||
"""
|
||||
Set the grid of the plot widget.
|
||||
|
||||
Args:
|
||||
x(bool): Show grid on the x-axis.
|
||||
y(bool): Show grid on the y-axis.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_colormap(self, colormap: str):
|
||||
"""
|
||||
Set the colormap for the curves.
|
||||
|
||||
Args:
|
||||
colormap(str): Colormap for the curves.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def enable_fps_monitor(self, enable: "bool" = True):
|
||||
"""
|
||||
Enable the FPS monitor.
|
||||
|
||||
Args:
|
||||
enable(bool): True to enable, False to disable.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def lock_aspect_ratio(self, lock):
|
||||
"""
|
||||
Lock aspect ratio.
|
||||
|
||||
Args:
|
||||
lock(bool): True to lock, False to unlock.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def export(self):
|
||||
"""
|
||||
Show the Export Dialog of the plot widget.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def get_all_data(self, output: Literal["dict", "pandas"] = "dict") -> dict:
|
||||
"""
|
||||
Extract all curve data into a dictionary or a pandas DataFrame.
|
||||
|
||||
Args:
|
||||
output (Literal["dict", "pandas"]): Format of the output data.
|
||||
|
||||
Returns:
|
||||
dict | pd.DataFrame: Data of all curves in the specified format.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def remove(self):
|
||||
"""
|
||||
Remove the plot widget from the figure.
|
||||
"""
|
||||
|
||||
|
||||
class BECMultiWaveformWidget(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def curves(self) -> list[pyqtgraph.graphicsItems.PlotDataItem.PlotDataItem]:
|
||||
"""
|
||||
Get the curves of the plot widget as a list
|
||||
Returns:
|
||||
list: List of curves.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_monitor(self, monitor: str) -> None:
|
||||
"""
|
||||
Set the monitor of the plot widget.
|
||||
|
||||
Args:
|
||||
monitor(str): The monitor to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_curve_highlight(self, index: int) -> None:
|
||||
"""
|
||||
Set the curve highlight of the plot widget by index
|
||||
|
||||
Args:
|
||||
index(int): The index of the curve to highlight.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_opacity(self, opacity: int) -> None:
|
||||
"""
|
||||
Set the opacity of the plot widget.
|
||||
|
||||
Args:
|
||||
opacity(int): The opacity to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_curve_limit(self, curve_limit: int) -> None:
|
||||
"""
|
||||
Set the maximum number of traces to display on the plot widget.
|
||||
|
||||
Args:
|
||||
curve_limit(int): The maximum number of traces to display.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_buffer_flush(self, flush_buffer: bool) -> None:
|
||||
"""
|
||||
Set the buffer flush property of the plot widget.
|
||||
|
||||
Args:
|
||||
flush_buffer(bool): True to flush the buffer, False to not flush the buffer.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_highlight_last_curve(self, enable: bool) -> None:
|
||||
"""
|
||||
Enable or disable highlighting of the last curve.
|
||||
|
||||
Args:
|
||||
enable(bool): True to enable highlighting of the last curve, False to disable.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_colormap(self, colormap: str) -> None:
|
||||
"""
|
||||
Set the colormap of the plot widget.
|
||||
|
||||
Args:
|
||||
colormap(str): The colormap to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set(self, **kwargs):
|
||||
"""
|
||||
Set the properties of the plot widget.
|
||||
|
||||
Args:
|
||||
**kwargs: Keyword arguments for the properties to be set.
|
||||
|
||||
Possible properties:
|
||||
- title: str
|
||||
- x_label: str
|
||||
- y_label: str
|
||||
- x_scale: Literal["linear", "log"]
|
||||
- y_scale: Literal["linear", "log"]
|
||||
- x_lim: tuple
|
||||
- y_lim: tuple
|
||||
- legend_label_size: int
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_title(self, title: str):
|
||||
"""
|
||||
Set the title of the plot widget.
|
||||
|
||||
Args:
|
||||
title(str): The title to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_x_label(self, x_label: str):
|
||||
"""
|
||||
Set the x-axis label of the plot widget.
|
||||
|
||||
Args:
|
||||
x_label(str): The x-axis label to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_y_label(self, y_label: str):
|
||||
"""
|
||||
Set the y-axis label of the plot widget.
|
||||
|
||||
Args:
|
||||
y_label(str): The y-axis label to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_x_scale(self, x_scale: Literal["linear", "log"]):
|
||||
"""
|
||||
Set the x-axis scale of the plot widget.
|
||||
|
||||
Args:
|
||||
x_scale(str): The x-axis scale to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_y_scale(self, y_scale: Literal["linear", "log"]):
|
||||
"""
|
||||
Set the y-axis scale of the plot widget.
|
||||
|
||||
Args:
|
||||
y_scale(str): The y-axis scale to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_x_lim(self, x_lim: tuple):
|
||||
"""
|
||||
Set x-axis limits of the plot widget.
|
||||
|
||||
Args:
|
||||
x_lim(tuple): The x-axis limits to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_y_lim(self, y_lim: tuple):
|
||||
"""
|
||||
Set y-axis limits of the plot widget.
|
||||
|
||||
Args:
|
||||
y_lim(tuple): The y-axis limits to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_grid(self, x_grid: bool, y_grid: bool):
|
||||
"""
|
||||
Set the grid of the plot widget.
|
||||
|
||||
Args:
|
||||
x_grid(bool): True to enable the x-grid, False to disable.
|
||||
y_grid(bool): True to enable the y-grid, False to disable.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_colormap(self, colormap: str) -> None:
|
||||
"""
|
||||
Set the colormap of the plot widget.
|
||||
|
||||
Args:
|
||||
colormap(str): The colormap to set.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def enable_fps_monitor(self, enabled: bool):
|
||||
"""
|
||||
Enable or disable the FPS monitor
|
||||
|
||||
Args:
|
||||
enabled(bool): True to enable the FPS monitor, False to disable.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def lock_aspect_ratio(self, lock: bool):
|
||||
"""
|
||||
Lock the aspect ratio of the plot widget.
|
||||
|
||||
Args:
|
||||
lock(bool): True to lock the aspect ratio, False to unlock.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def export(self):
|
||||
"""
|
||||
Export the plot widget.
|
||||
"""
|
||||
|
||||
|
||||
class BECPlotBase(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
|
||||
@@ -11,7 +11,7 @@ import isort
|
||||
from qtpy.QtCore import Property as QtProperty
|
||||
|
||||
from bec_widgets.utils.generate_designer_plugin import DesignerPluginGenerator
|
||||
from bec_widgets.utils.plugin_utils import BECClassContainer, get_custom_classes
|
||||
from bec_widgets.utils.plugin_utils import BECClassContainer, get_rpc_classes
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from typing import get_overloads
|
||||
@@ -175,7 +175,7 @@ def main():
|
||||
current_path = os.path.dirname(__file__)
|
||||
client_path = os.path.join(current_path, "client.py")
|
||||
|
||||
rpc_classes = get_custom_classes("bec_widgets")
|
||||
rpc_classes = get_rpc_classes("bec_widgets")
|
||||
|
||||
generator = ClientGenerator()
|
||||
generator.generate_client(rpc_classes)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from threading import Lock
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from bec_widgets.utils import BECConnector
|
||||
|
||||
|
||||
@@ -28,10 +26,10 @@ class RPCWidgetHandler:
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
from bec_widgets.utils.plugin_utils import get_custom_classes
|
||||
from bec_widgets.utils.plugin_utils import get_rpc_classes
|
||||
|
||||
clss = get_custom_classes("bec_widgets")
|
||||
self._widget_classes = {cls.__name__: cls for cls in clss.widgets}
|
||||
clss = get_rpc_classes("bec_widgets")
|
||||
self._widget_classes = {cls.__name__: cls for cls in clss.top_level_classes}
|
||||
|
||||
def create_widget(self, widget_type, **kwargs) -> BECConnector:
|
||||
"""
|
||||
|
||||
@@ -16,8 +16,9 @@ from qtpy.QtCore import QTimer
|
||||
from bec_widgets.cli.rpc_register import RPCRegister
|
||||
from bec_widgets.utils import BECDispatcher
|
||||
from bec_widgets.utils.bec_connector import BECConnector
|
||||
from bec_widgets.widgets.containers.dock import BECDockArea
|
||||
from bec_widgets.widgets.containers.figure import BECFigure
|
||||
from bec_widgets.utils.bec_dispatcher import QtRedisConnector
|
||||
from bec_widgets.widgets.dock.dock_area import BECDockArea
|
||||
from bec_widgets.widgets.figure import BECFigure
|
||||
|
||||
messages = lazy_import("bec_lib.messages")
|
||||
logger = bec_logger.logger
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from bec_qthemes import material_icon
|
||||
from qtpy.QtCore import QSize
|
||||
from qtpy.QtGui import QIcon
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QGroupBox,
|
||||
@@ -15,9 +17,9 @@ from qtpy.QtWidgets import (
|
||||
|
||||
from bec_widgets.utils import BECDispatcher
|
||||
from bec_widgets.utils.colors import apply_theme
|
||||
from bec_widgets.widgets.containers.dock import BECDockArea
|
||||
from bec_widgets.widgets.containers.figure import BECFigure
|
||||
from bec_widgets.widgets.editors.jupyter_console.jupyter_console import BECJupyterConsole
|
||||
from bec_widgets.widgets.dock.dock_area import BECDockArea
|
||||
from bec_widgets.widgets.figure import BECFigure
|
||||
from bec_widgets.widgets.jupyter_console.jupyter_console import BECJupyterConsole
|
||||
|
||||
|
||||
class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
||||
@@ -54,7 +56,6 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
||||
# "cm": self.colormap,
|
||||
"im": self.im,
|
||||
"mm": self.mm,
|
||||
"mw": self.mw,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -166,11 +167,9 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
||||
self.im.image("waveform", "1d")
|
||||
|
||||
self.d2 = self.dock.add_dock(name="dock_2", position="bottom")
|
||||
self.wf = self.d2.add_widget("BECFigure", row=0, col=0)
|
||||
|
||||
self.mw = self.wf.multi_waveform(monitor="waveform") # , config=config)
|
||||
# self.wf.plot(x_name="samx", y_name="bpm3a")
|
||||
# self.wf.plot(x_name="samx", y_name="bpm4i", dap="GaussianModel")
|
||||
self.wf = self.d2.add_widget("BECWaveformWidget", row=0, col=0)
|
||||
self.wf.plot(x_name="samx", y_name="bpm3a")
|
||||
self.wf.plot(x_name="samx", y_name="bpm4i", dap="GaussianModel")
|
||||
# self.bar = self.d2.add_widget("RingProgressBar", row=0, col=1)
|
||||
# self.bar.set_diameter(200)
|
||||
|
||||
@@ -211,7 +210,6 @@ if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
win = JupyterConsoleWindow()
|
||||
win.show()
|
||||
win.resize(1200, 800)
|
||||
|
||||
app.aboutToQuit.connect(win.close)
|
||||
sys.exit(app.exec_())
|
||||
|
||||
@@ -13,7 +13,7 @@ from qtpy.QtWidgets import (
|
||||
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.colors import get_accent_colors, get_theme_palette
|
||||
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
|
||||
from bec_widgets.widgets.dark_mode_button.dark_mode_button import DarkModeButton
|
||||
|
||||
|
||||
class PaletteViewer(BECWidget, QWidget):
|
||||
|
||||
@@ -6,16 +6,13 @@ from qtpy.QtCore import QObject, Qt, Signal, Slot
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
|
||||
class CrosshairScatterItem(pg.ScatterPlotItem):
|
||||
class NonDownsamplingScatterPlotItem(pg.ScatterPlotItem):
|
||||
def setDownsampling(self, ds=None, auto=None, method=None):
|
||||
pass
|
||||
|
||||
def setClipToView(self, state):
|
||||
pass
|
||||
|
||||
def setAlpha(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class Crosshair(QObject):
|
||||
# QT Position of mouse cursor
|
||||
@@ -50,15 +47,9 @@ class Crosshair(QObject):
|
||||
self.v_line.skip_auto_range = True
|
||||
self.h_line = pg.InfiniteLine(angle=0, movable=False)
|
||||
self.h_line.skip_auto_range = True
|
||||
# Add custom attribute to identify crosshair lines
|
||||
self.v_line.is_crosshair = True
|
||||
self.h_line.is_crosshair = True
|
||||
self.plot_item.addItem(self.v_line, ignoreBounds=True)
|
||||
self.plot_item.addItem(self.h_line, ignoreBounds=True)
|
||||
|
||||
# Initialize highlighted curve in a case of multiple curves
|
||||
self.highlighted_curve_index = None
|
||||
|
||||
# Add TextItem to display coordinates
|
||||
self.coord_label = pg.TextItem("", anchor=(1, 1), fill=(0, 0, 0, 100))
|
||||
self.coord_label.setVisible(False) # Hide initially
|
||||
@@ -79,7 +70,6 @@ class Crosshair(QObject):
|
||||
self.plot_item.ctrl.downsampleSpin.valueChanged.connect(self.clear_markers)
|
||||
|
||||
# Initialize markers
|
||||
self.items = []
|
||||
self.marker_moved_1d = {}
|
||||
self.marker_clicked_1d = {}
|
||||
self.marker_2d = None
|
||||
@@ -123,74 +113,34 @@ class Crosshair(QObject):
|
||||
self.coord_label.fill = pg.mkBrush(label_bg_color)
|
||||
self.coord_label.border = pg.mkPen(None)
|
||||
|
||||
@Slot(int)
|
||||
def update_highlighted_curve(self, curve_index: int):
|
||||
"""
|
||||
Update the highlighted curve in the case of multiple curves in a plot item.
|
||||
|
||||
Args:
|
||||
curve_index(int): The index of curve to highlight
|
||||
"""
|
||||
self.highlighted_curve_index = curve_index
|
||||
self.clear_markers()
|
||||
self.update_markers()
|
||||
|
||||
def update_markers(self):
|
||||
"""Update the markers for the crosshair, creating new ones if necessary."""
|
||||
|
||||
if self.highlighted_curve_index is not None and hasattr(self.plot_item, "visible_curves"):
|
||||
# Focus on the highlighted curve only
|
||||
self.items = [self.plot_item.visible_curves[self.highlighted_curve_index]]
|
||||
else:
|
||||
# Handle all curves
|
||||
self.items = self.plot_item.items
|
||||
|
||||
# Create or update markers
|
||||
for item in self.items:
|
||||
# Create new markers
|
||||
for item in self.plot_item.items:
|
||||
if isinstance(item, pg.PlotDataItem): # 1D plot
|
||||
if item.name() in self.marker_moved_1d:
|
||||
continue
|
||||
pen = item.opts["pen"]
|
||||
color = pen.color() if hasattr(pen, "color") else pg.mkColor(pen)
|
||||
name = item.name() or str(id(item))
|
||||
if name in self.marker_moved_1d:
|
||||
# Update existing markers
|
||||
marker_moved = self.marker_moved_1d[name]
|
||||
marker_moved.setPen(pg.mkPen(color))
|
||||
# Update clicked markers' brushes
|
||||
for marker_clicked in self.marker_clicked_1d[name]:
|
||||
alpha = marker_clicked.opts["brush"].color().alpha()
|
||||
marker_clicked.setBrush(
|
||||
pg.mkBrush(color.red(), color.green(), color.blue(), alpha)
|
||||
)
|
||||
# Update z-values
|
||||
marker_moved.setZValue(item.zValue() + 1)
|
||||
for marker_clicked in self.marker_clicked_1d[name]:
|
||||
marker_clicked.setZValue(item.zValue() + 1)
|
||||
else:
|
||||
# Create new markers
|
||||
marker_moved = CrosshairScatterItem(
|
||||
size=10, pen=pg.mkPen(color), brush=pg.mkBrush(None)
|
||||
)
|
||||
marker_moved.skip_auto_range = True
|
||||
marker_moved.is_crosshair = True
|
||||
self.marker_moved_1d[name] = marker_moved
|
||||
self.plot_item.addItem(marker_moved)
|
||||
# Set marker z-value higher than the curve
|
||||
marker_moved.setZValue(item.zValue() + 1)
|
||||
marker_moved = NonDownsamplingScatterPlotItem(
|
||||
size=10, pen=pg.mkPen(color), brush=pg.mkBrush(None)
|
||||
)
|
||||
marker_moved.skip_auto_range = True
|
||||
self.marker_moved_1d[item.name()] = marker_moved
|
||||
self.plot_item.addItem(marker_moved)
|
||||
|
||||
# Create glowing effect markers for clicked events
|
||||
for size, alpha in [(18, 64), (14, 128), (10, 255)]:
|
||||
marker_clicked = NonDownsamplingScatterPlotItem(
|
||||
size=size,
|
||||
pen=pg.mkPen(None),
|
||||
brush=pg.mkBrush(color.red(), color.green(), color.blue(), alpha),
|
||||
)
|
||||
marker_clicked.skip_auto_range = True
|
||||
self.marker_clicked_1d[item.name()] = marker_clicked
|
||||
self.plot_item.addItem(marker_clicked)
|
||||
|
||||
# Create glowing effect markers for clicked events
|
||||
marker_clicked_list = []
|
||||
for size, alpha in [(18, 64), (14, 128), (10, 255)]:
|
||||
marker_clicked = CrosshairScatterItem(
|
||||
size=size,
|
||||
pen=pg.mkPen(None),
|
||||
brush=pg.mkBrush(color.red(), color.green(), color.blue(), alpha),
|
||||
)
|
||||
marker_clicked.skip_auto_range = True
|
||||
marker_clicked.is_crosshair = True
|
||||
self.plot_item.addItem(marker_clicked)
|
||||
marker_clicked.setZValue(item.zValue() + 1)
|
||||
marker_clicked_list.append(marker_clicked)
|
||||
self.marker_clicked_1d[name] = marker_clicked_list
|
||||
elif isinstance(item, pg.ImageItem): # 2D plot
|
||||
if self.marker_2d is not None:
|
||||
continue
|
||||
@@ -212,11 +162,12 @@ class Crosshair(QObject):
|
||||
"""
|
||||
y_values = defaultdict(list)
|
||||
x_values = defaultdict(list)
|
||||
image_2d = None
|
||||
|
||||
# Iterate through items in the plot
|
||||
for item in self.items:
|
||||
for item in self.plot_item.items:
|
||||
if isinstance(item, pg.PlotDataItem): # 1D plot
|
||||
name = item.name() or str(id(item))
|
||||
name = item.name()
|
||||
plot_data = item._getDisplayDataset()
|
||||
if plot_data is None:
|
||||
continue
|
||||
@@ -237,7 +188,7 @@ class Crosshair(QObject):
|
||||
elif isinstance(item, pg.ImageItem): # 2D plot
|
||||
name = item.config.monitor
|
||||
image_2d = item.image
|
||||
# Clip the x and y values to the image dimensions to avoid out of bounds errors
|
||||
# clip the x and y values to the image dimensions to avoid out of bounds errors
|
||||
y_values[name] = int(np.clip(y, 0, image_2d.shape[1] - 1))
|
||||
x_values[name] = int(np.clip(x, 0, image_2d.shape[0] - 1))
|
||||
|
||||
@@ -305,9 +256,9 @@ class Crosshair(QObject):
|
||||
# not sure how we got here, but just to be safe...
|
||||
return
|
||||
|
||||
for item in self.items:
|
||||
for item in self.plot_item.items:
|
||||
if isinstance(item, pg.PlotDataItem):
|
||||
name = item.name() or str(id(item))
|
||||
name = item.name()
|
||||
x, y = x_snap_values[name], y_snap_values[name]
|
||||
if x is None or y is None:
|
||||
continue
|
||||
@@ -358,14 +309,13 @@ class Crosshair(QObject):
|
||||
# not sure how we got here, but just to be safe...
|
||||
return
|
||||
|
||||
for item in self.items:
|
||||
for item in self.plot_item.items:
|
||||
if isinstance(item, pg.PlotDataItem):
|
||||
name = item.name() or str(id(item))
|
||||
name = item.name()
|
||||
x, y = x_snap_values[name], y_snap_values[name]
|
||||
if x is None or y is None:
|
||||
continue
|
||||
for marker_clicked in self.marker_clicked_1d[name]:
|
||||
marker_clicked.setData([x], [y])
|
||||
self.marker_clicked_1d[name].setData([x], [y])
|
||||
x_snapped_scaled, y_snapped_scaled = self.scale_emitted_coordinates(x, y)
|
||||
coordinate_to_emit = (
|
||||
name,
|
||||
@@ -387,12 +337,9 @@ class Crosshair(QObject):
|
||||
def clear_markers(self):
|
||||
"""Clears the markers from the plot."""
|
||||
for marker in self.marker_moved_1d.values():
|
||||
self.plot_item.removeItem(marker)
|
||||
for markers in self.marker_clicked_1d.values():
|
||||
for marker in markers:
|
||||
self.plot_item.removeItem(marker)
|
||||
self.marker_moved_1d.clear()
|
||||
self.marker_clicked_1d.clear()
|
||||
marker.clear()
|
||||
for marker in self.marker_clicked_1d.values():
|
||||
marker.clear()
|
||||
|
||||
def scale_emitted_coordinates(self, x, y):
|
||||
"""Scales the emitted coordinates if the axes are in log scale.
|
||||
@@ -419,7 +366,7 @@ class Crosshair(QObject):
|
||||
x, y = pos
|
||||
x_scaled, y_scaled = self.scale_emitted_coordinates(x, y)
|
||||
|
||||
# Update coordinate label
|
||||
# # Update coordinate label
|
||||
self.coord_label.setText(f"({x_scaled:.{self.precision}g}, {y_scaled:.{self.precision}g})")
|
||||
self.coord_label.setPos(x, y)
|
||||
self.coord_label.setVisible(True)
|
||||
|
||||
@@ -143,7 +143,7 @@ class DesignerPluginGenerator:
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
# from bec_widgets.widgets.bec_queue.bec_queue import BECQueue
|
||||
from bec_widgets.widgets.utility.spinner import SpinnerWidget
|
||||
from bec_widgets.widgets.spinner.spinner import SpinnerWidget
|
||||
|
||||
generator = DesignerPluginGenerator(SpinnerWidget)
|
||||
generator.run(validate=False)
|
||||
|
||||
@@ -53,7 +53,7 @@ class BECClassInfo:
|
||||
obj: type
|
||||
is_connector: bool = False
|
||||
is_widget: bool = False
|
||||
is_plugin: bool = False
|
||||
is_top_level: bool = False
|
||||
|
||||
|
||||
class BECClassContainer:
|
||||
@@ -88,14 +88,14 @@ class BECClassContainer:
|
||||
"""
|
||||
Get all top-level classes.
|
||||
"""
|
||||
return [info.obj for info in self.collection if info.is_plugin]
|
||||
return [info.obj for info in self.collection if info.is_top_level]
|
||||
|
||||
@property
|
||||
def plugins(self):
|
||||
"""
|
||||
Get all plugins. These are all classes that are on the top level and are widgets.
|
||||
"""
|
||||
return [info.obj for info in self.collection if info.is_widget and info.is_plugin]
|
||||
return [info.obj for info in self.collection if info.is_widget and info.is_top_level]
|
||||
|
||||
@property
|
||||
def widgets(self):
|
||||
@@ -109,17 +109,10 @@ class BECClassContainer:
|
||||
"""
|
||||
Get all top-level classes that are RPC-enabled. These are all classes that users can choose from.
|
||||
"""
|
||||
return [info.obj for info in self.collection if info.is_plugin and info.is_connector]
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
"""
|
||||
Get all classes.
|
||||
"""
|
||||
return [info.obj for info in self.collection]
|
||||
return [info.obj for info in self.collection if info.is_top_level and info.is_connector]
|
||||
|
||||
|
||||
def get_custom_classes(repo_name: str) -> BECClassContainer:
|
||||
def get_rpc_classes(repo_name: str) -> BECClassContainer:
|
||||
"""
|
||||
Get all RPC-enabled classes in the specified repository.
|
||||
|
||||
@@ -160,8 +153,6 @@ def get_custom_classes(repo_name: str) -> BECClassContainer:
|
||||
issubclass(obj, QWidget) or issubclass(obj, QGraphicsWidget)
|
||||
):
|
||||
class_info.is_top_level = True
|
||||
if hasattr(obj, "PLUGIN") and obj.PLUGIN:
|
||||
class_info.is_plugin = True
|
||||
collection.add_class(class_info)
|
||||
|
||||
return collection
|
||||
|
||||
@@ -4,7 +4,7 @@ from qtpy import PYQT6, PYSIDE6, QT_VERSION
|
||||
from qtpy.QtCore import QFile, QIODevice
|
||||
|
||||
from bec_widgets.utils.generate_designer_plugin import DesignerPluginInfo
|
||||
from bec_widgets.utils.plugin_utils import get_custom_classes
|
||||
from bec_widgets.utils.plugin_utils import get_rpc_classes
|
||||
|
||||
if PYSIDE6:
|
||||
from PySide6.QtUiTools import QUiLoader
|
||||
@@ -30,7 +30,7 @@ class UILoader:
|
||||
def __init__(self, parent=None):
|
||||
self.parent = parent
|
||||
|
||||
widgets = get_custom_classes("bec_widgets").classes
|
||||
widgets = get_rpc_classes("bec_widgets").top_level_classes
|
||||
|
||||
self.custom_widgets = {widget.__name__: widget for widget in widgets}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.progress.bec_progressbar.bec_progressbar import BECProgressBar
|
||||
from bec_widgets.widgets.bec_progressbar.bec_progressbar import BECProgressBar
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -14,7 +14,6 @@ class BECProgressBar(BECWidget, QWidget):
|
||||
A custom progress bar with smooth transitions. The displayed text can be customized using a template.
|
||||
"""
|
||||
|
||||
PLUGIN = True
|
||||
USER_ACCESS = [
|
||||
"set_value",
|
||||
"set_maximum",
|
||||
@@ -6,9 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.progress.bec_progressbar.bec_progress_bar_plugin import (
|
||||
BECProgressBarPlugin,
|
||||
)
|
||||
from bec_widgets.widgets.bec_progressbar.bec_progress_bar_plugin import BECProgressBarPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(BECProgressBarPlugin())
|
||||
|
||||
@@ -10,10 +10,10 @@ from bec_widgets.qt_utils.compact_popup import CompactPopupWidget
|
||||
from bec_widgets.qt_utils.toolbar import ModularToolBar, SeparatorAction, WidgetAction
|
||||
from bec_widgets.utils.bec_connector import ConnectionConfig
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.widgets.control.buttons.button_abort.button_abort import AbortButton
|
||||
from bec_widgets.widgets.control.buttons.button_reset.button_reset import ResetButton
|
||||
from bec_widgets.widgets.control.buttons.button_resume.button_resume import ResumeButton
|
||||
from bec_widgets.widgets.control.buttons.stop_button.stop_button import StopButton
|
||||
from bec_widgets.widgets.button_abort.button_abort import AbortButton
|
||||
from bec_widgets.widgets.button_reset.button_reset import ResetButton
|
||||
from bec_widgets.widgets.button_resume.button_resume import ResumeButton
|
||||
from bec_widgets.widgets.stop_button.stop_button import StopButton
|
||||
|
||||
|
||||
class BECQueue(BECWidget, CompactPopupWidget):
|
||||
@@ -21,7 +21,6 @@ class BECQueue(BECWidget, CompactPopupWidget):
|
||||
Widget to display the BEC queue.
|
||||
"""
|
||||
|
||||
PLUGIN = True
|
||||
ICON_NAME = "edit_note"
|
||||
status_colors = {
|
||||
"STOPPED": "red",
|
||||
@@ -6,7 +6,7 @@ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.services.bec_queue.bec_queue import BECQueue
|
||||
from bec_widgets.widgets.bec_queue.bec_queue import BECQueue
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -6,7 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.services.bec_queue.bec_queue_plugin import BECQueuePlugin
|
||||
from bec_widgets.widgets.bec_queue.bec_queue_plugin import BECQueuePlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(BECQueuePlugin())
|
||||
|
||||
@@ -4,17 +4,18 @@ The widget automatically updates the status of all running BEC services, and dis
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bec_lib.utils.import_utils import lazy_import_from
|
||||
from qtpy.QtCore import QObject, QTimer, Signal, Slot
|
||||
from qtpy.QtWidgets import QHBoxLayout, QTreeWidget, QTreeWidgetItem
|
||||
from qtpy.QtWidgets import QHBoxLayout, QTreeWidget, QTreeWidgetItem, QWidget
|
||||
|
||||
from bec_widgets.qt_utils.compact_popup import CompactPopupWidget
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.widgets.services.bec_status_box.status_item import StatusItem
|
||||
from bec_widgets.widgets.bec_status_box.status_item import StatusItem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_lib.client import BECClient
|
||||
@@ -74,7 +75,6 @@ class BECStatusBox(BECWidget, CompactPopupWidget):
|
||||
gui_id Optional(str): The unique id for the widget. Defaults to None.
|
||||
"""
|
||||
|
||||
PLUGIN = True
|
||||
CORE_SERVICES = ["DeviceServer", "ScanServer", "SciHub", "ScanBundler", "FileWriterManager"]
|
||||
|
||||
service_update = Signal(BECServiceInfoContainer)
|
||||
@@ -6,7 +6,7 @@ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.services.bec_status_box.bec_status_box import BECStatusBox
|
||||
from bec_widgets.widgets.bec_status_box.bec_status_box import BECStatusBox
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -6,7 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.services.bec_status_box.bec_status_box_plugin import BECStatusBoxPlugin
|
||||
from bec_widgets.widgets.bec_status_box.bec_status_box_plugin import BECStatusBoxPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(BECStatusBoxPlugin())
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.control.buttons.button_abort.button_abort import AbortButton
|
||||
from bec_widgets.widgets.button_abort.button_abort import AbortButton
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -9,7 +9,6 @@ from bec_widgets.utils.bec_widget import BECWidget
|
||||
class AbortButton(BECWidget, QWidget):
|
||||
"""A button that abort the scan."""
|
||||
|
||||
PLUGIN = True
|
||||
ICON_NAME = "cancel"
|
||||
|
||||
def __init__(
|
||||
@@ -6,9 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.control.buttons.button_abort.abort_button_plugin import (
|
||||
AbortButtonPlugin,
|
||||
)
|
||||
from bec_widgets.widgets.button_abort.abort_button_plugin import AbortButtonPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(AbortButtonPlugin())
|
||||
|
||||
@@ -9,7 +9,6 @@ from bec_widgets.utils.bec_widget import BECWidget
|
||||
class ResetButton(BECWidget, QWidget):
|
||||
"""A button that resets the scan queue."""
|
||||
|
||||
PLUGIN = True
|
||||
ICON_NAME = "restart_alt"
|
||||
|
||||
def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False):
|
||||
@@ -6,9 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.control.buttons.button_reset.reset_button_plugin import (
|
||||
ResetButtonPlugin,
|
||||
)
|
||||
from bec_widgets.widgets.button_reset.reset_button_plugin import ResetButtonPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(ResetButtonPlugin())
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.control.buttons.button_reset.button_reset import ResetButton
|
||||
from bec_widgets.widgets.button_reset.button_reset import ResetButton
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -9,7 +9,6 @@ from bec_widgets.utils.bec_widget import BECWidget
|
||||
class ResumeButton(BECWidget, QWidget):
|
||||
"""A button that continue scan queue."""
|
||||
|
||||
PLUGIN = True
|
||||
ICON_NAME = "resume"
|
||||
|
||||
def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False):
|
||||
@@ -6,9 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.control.buttons.button_resume.resume_button_plugin import (
|
||||
ResumeButtonPlugin,
|
||||
)
|
||||
from bec_widgets.widgets.button_resume.resume_button_plugin import ResumeButtonPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(ResumeButtonPlugin())
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.control.buttons.button_resume.button_resume import ResumeButton
|
||||
from bec_widgets.widgets.button_resume.button_resume import ResumeButton
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -17,7 +17,6 @@ class ColorButton(QWidget):
|
||||
|
||||
color_selected = Signal(str)
|
||||
|
||||
PLUGIN = True
|
||||
ICON_NAME = "colors"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -4,7 +4,7 @@ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.utility.visual.color_button.color_button import ColorButton
|
||||
from bec_widgets.widgets.color_button.color_button import ColorButton
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -6,9 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.utility.visual.color_button.color_button_plugin import (
|
||||
ColorButtonPlugin,
|
||||
)
|
||||
from bec_widgets.widgets.color_button.color_button_plugin import ColorButtonPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(ColorButtonPlugin())
|
||||
|
||||
@@ -47,7 +47,6 @@ class ColormapSelector(QWidget):
|
||||
|
||||
colormap_changed_signal = Signal(str)
|
||||
ICON_NAME = "palette"
|
||||
PLUGIN = True
|
||||
|
||||
def __init__(self, parent=None, default_colormaps=None):
|
||||
super().__init__(parent=parent)
|
||||
@@ -6,7 +6,7 @@ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.utility.visual.colormap_selector.colormap_selector import ColormapSelector
|
||||
from bec_widgets.widgets.colormap_selector.colormap_selector import ColormapSelector
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -6,7 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.utility.visual.colormap_selector.colormap_selector_plugin import (
|
||||
from bec_widgets.widgets.colormap_selector.colormap_selector_plugin import (
|
||||
ColormapSelectorPlugin,
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.utility.visual.colormap_widget.colormap_widget import BECColorMapWidget
|
||||
from bec_widgets.widgets.colormap_widget.colormap_widget import BECColorMapWidget
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -10,7 +10,6 @@ class BECColorMapWidget(BECWidget, QWidget):
|
||||
colormap_changed_signal = Signal(str)
|
||||
ICON_NAME = "palette"
|
||||
USER_ACCESS = ["colormap"]
|
||||
PLUGIN = True
|
||||
|
||||
def __init__(self, parent=None, cmap: str = "magma"):
|
||||
super().__init__()
|
||||
@@ -6,7 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.utility.visual.colormap_widget.bec_color_map_widget_plugin import (
|
||||
from bec_widgets.widgets.colormap_widget.bec_color_map_widget_plugin import (
|
||||
BECColorMapWidgetPlugin,
|
||||
)
|
||||
|
||||
@@ -232,7 +232,6 @@ class Backend(QtCore.QObject):
|
||||
class BECConsole(QtWidgets.QWidget):
|
||||
"""Container widget for the terminal text area"""
|
||||
|
||||
PLUGIN = True
|
||||
ICON_NAME = "terminal"
|
||||
|
||||
def __init__(self, parent=None, cols=132):
|
||||
@@ -6,7 +6,7 @@ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.editors.console.console import BECConsole
|
||||
from bec_widgets.widgets.console.console import BECConsole
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -6,7 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.editors.console.console_plugin import BECConsolePlugin
|
||||
from bec_widgets.widgets.console.console_plugin import BECConsolePlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(BECConsolePlugin())
|
||||
|
||||
@@ -1,353 +0,0 @@
|
||||
from collections import deque
|
||||
from typing import Literal, Optional
|
||||
|
||||
import pyqtgraph as pg
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.logger import bec_logger
|
||||
from pydantic import Field, field_validator
|
||||
from pyqtgraph.exporters import MatplotlibExporter
|
||||
from qtpy.QtCore import Signal, Slot
|
||||
from qtpy.QtWidgets import QWidget
|
||||
|
||||
from bec_widgets.utils import Colors
|
||||
from bec_widgets.widgets.containers.figure.plots.plot_base import BECPlotBase, SubplotConfig
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class BECMultiWaveformConfig(SubplotConfig):
|
||||
color_palette: Optional[str] = Field(
|
||||
"magma", description="The color palette of the figure widget.", validate_default=True
|
||||
)
|
||||
curve_limit: Optional[int] = Field(
|
||||
200, description="The maximum number of curves to display on the plot."
|
||||
)
|
||||
flush_buffer: Optional[bool] = Field(
|
||||
False, description="Flush the buffer of the plot widget when the curve limit is reached."
|
||||
)
|
||||
monitor: Optional[str] = Field(
|
||||
None, description="The monitor to set for the plot widget."
|
||||
) # TODO validate monitor in bec -> maybe make it as SignalData class for validation purpose
|
||||
curve_width: Optional[int] = Field(1, description="The width of the curve on the plot.")
|
||||
opacity: Optional[int] = Field(50, description="The opacity of the curve on the plot.")
|
||||
highlight_last_curve: Optional[bool] = Field(
|
||||
True, description="Highlight the last curve on the plot."
|
||||
)
|
||||
|
||||
model_config: dict = {"validate_assignment": True}
|
||||
_validate_color_map_z = field_validator("color_palette")(Colors.validate_color_map)
|
||||
|
||||
|
||||
class BECMultiWaveform(BECPlotBase):
|
||||
monitor_signal_updated = Signal()
|
||||
highlighted_curve_index_changed = Signal(int)
|
||||
USER_ACCESS = [
|
||||
"_rpc_id",
|
||||
"_config_dict",
|
||||
"curves",
|
||||
"set_monitor",
|
||||
"set_opacity",
|
||||
"set_curve_limit",
|
||||
"set_curve_highlight",
|
||||
"set_colormap",
|
||||
"set",
|
||||
"set_title",
|
||||
"set_x_label",
|
||||
"set_y_label",
|
||||
"set_x_scale",
|
||||
"set_y_scale",
|
||||
"set_x_lim",
|
||||
"set_y_lim",
|
||||
"set_grid",
|
||||
"set_colormap",
|
||||
"enable_fps_monitor",
|
||||
"lock_aspect_ratio",
|
||||
"export",
|
||||
"get_all_data",
|
||||
"remove",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: Optional[QWidget] = None,
|
||||
parent_figure=None,
|
||||
config: Optional[BECMultiWaveformConfig] = None,
|
||||
client=None,
|
||||
gui_id: Optional[str] = None,
|
||||
):
|
||||
if config is None:
|
||||
config = BECMultiWaveformConfig(widget_class=self.__class__.__name__)
|
||||
super().__init__(
|
||||
parent=parent, parent_figure=parent_figure, config=config, client=client, gui_id=gui_id
|
||||
)
|
||||
self.old_scan_id = None
|
||||
self.scan_id = None
|
||||
self.monitor = None
|
||||
self.connected = False
|
||||
self.current_highlight_index = 0
|
||||
self._curves = deque()
|
||||
self.visible_curves = []
|
||||
self.number_of_visible_curves = 0
|
||||
|
||||
# Get bec shortcuts dev, scans, queue, scan_storage, dap
|
||||
self.get_bec_shortcuts()
|
||||
|
||||
@property
|
||||
def curves(self) -> deque:
|
||||
"""
|
||||
Get the curves of the plot widget as a deque.
|
||||
Returns:
|
||||
deque: Deque of curves.
|
||||
"""
|
||||
return self._curves
|
||||
|
||||
@curves.setter
|
||||
def curves(self, value: deque):
|
||||
self._curves = value
|
||||
|
||||
@property
|
||||
def highlight_last_curve(self) -> bool:
|
||||
"""
|
||||
Get the highlight_last_curve property.
|
||||
Returns:
|
||||
bool: The highlight_last_curve property.
|
||||
"""
|
||||
return self.config.highlight_last_curve
|
||||
|
||||
@highlight_last_curve.setter
|
||||
def highlight_last_curve(self, value: bool):
|
||||
self.config.highlight_last_curve = value
|
||||
|
||||
def set_monitor(self, monitor: str):
|
||||
"""
|
||||
Set the monitor for the plot widget.
|
||||
Args:
|
||||
monitor (str): The monitor to set.
|
||||
"""
|
||||
self.config.monitor = monitor
|
||||
self._connect_monitor()
|
||||
|
||||
def _connect_monitor(self):
|
||||
"""
|
||||
Connect the monitor to the plot widget.
|
||||
"""
|
||||
try:
|
||||
previous_monitor = self.monitor
|
||||
except AttributeError:
|
||||
previous_monitor = None
|
||||
|
||||
if previous_monitor and self.connected is True:
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_monitor_1d_update, MessageEndpoints.device_monitor_1d(previous_monitor)
|
||||
)
|
||||
if self.config.monitor and self.connected is False:
|
||||
self.bec_dispatcher.connect_slot(
|
||||
self.on_monitor_1d_update, MessageEndpoints.device_monitor_1d(self.config.monitor)
|
||||
)
|
||||
self.connected = True
|
||||
self.monitor = self.config.monitor
|
||||
|
||||
@Slot(dict, dict)
|
||||
def on_monitor_1d_update(self, msg: dict, metadata: dict):
|
||||
"""
|
||||
Update the plot widget with the monitor data.
|
||||
|
||||
Args:
|
||||
msg(dict): The message data.
|
||||
metadata(dict): The metadata of the message.
|
||||
"""
|
||||
data = msg.get("data", None)
|
||||
current_scan_id = metadata.get("scan_id", None)
|
||||
|
||||
if current_scan_id != self.scan_id:
|
||||
self.scan_id = current_scan_id
|
||||
self.clear_curves()
|
||||
self.curves.clear()
|
||||
if self.crosshair:
|
||||
self.crosshair.clear_markers()
|
||||
|
||||
# Always create a new curve and add it
|
||||
curve = pg.PlotDataItem()
|
||||
curve.setData(data)
|
||||
self.plot_item.addItem(curve)
|
||||
self.curves.append(curve)
|
||||
|
||||
# Max Trace and scale colors
|
||||
self.set_curve_limit(self.config.curve_limit, self.config.flush_buffer)
|
||||
|
||||
self.monitor_signal_updated.emit()
|
||||
|
||||
@Slot(int)
|
||||
def set_curve_highlight(self, index: int):
|
||||
"""
|
||||
Set the curve highlight based on visible curves.
|
||||
|
||||
Args:
|
||||
index (int): The index of the curve to highlight among visible curves.
|
||||
"""
|
||||
self.plot_item.visible_curves = [curve for curve in self.curves if curve.isVisible()]
|
||||
num_visible_curves = len(self.plot_item.visible_curves)
|
||||
self.number_of_visible_curves = num_visible_curves
|
||||
|
||||
if num_visible_curves == 0:
|
||||
return # No curves to highlight
|
||||
|
||||
if index >= num_visible_curves:
|
||||
index = num_visible_curves - 1
|
||||
elif index < 0:
|
||||
index = num_visible_curves + index
|
||||
self.current_highlight_index = index
|
||||
num_colors = num_visible_curves
|
||||
colors = Colors.evenly_spaced_colors(
|
||||
colormap=self.config.color_palette, num=num_colors, format="HEX"
|
||||
)
|
||||
for i, curve in enumerate(self.plot_item.visible_curves):
|
||||
curve.setPen()
|
||||
if i == self.current_highlight_index:
|
||||
curve.setPen(pg.mkPen(color=colors[i], width=5))
|
||||
curve.setAlpha(alpha=1, auto=False)
|
||||
curve.setZValue(1)
|
||||
else:
|
||||
curve.setPen(pg.mkPen(color=colors[i], width=1))
|
||||
curve.setAlpha(alpha=self.config.opacity / 100, auto=False)
|
||||
curve.setZValue(0)
|
||||
|
||||
self.highlighted_curve_index_changed.emit(self.current_highlight_index)
|
||||
|
||||
@Slot(int)
|
||||
def set_opacity(self, opacity: int):
|
||||
"""
|
||||
Set the opacity of the curve on the plot.
|
||||
|
||||
Args:
|
||||
opacity(int): The opacity of the curve. 0-100.
|
||||
"""
|
||||
self.config.opacity = max(0, min(100, opacity))
|
||||
self.set_curve_highlight(self.current_highlight_index)
|
||||
|
||||
@Slot(int, bool)
|
||||
def set_curve_limit(self, max_trace: int, flush_buffer: bool = False):
|
||||
"""
|
||||
Set the maximum number of traces to display on the plot.
|
||||
|
||||
Args:
|
||||
max_trace (int): The maximum number of traces to display.
|
||||
flush_buffer (bool): Flush the buffer.
|
||||
"""
|
||||
self.config.curve_limit = max_trace
|
||||
self.config.flush_buffer = flush_buffer
|
||||
|
||||
if self.config.curve_limit is None:
|
||||
self.scale_colors()
|
||||
return
|
||||
|
||||
if self.config.flush_buffer:
|
||||
# Remove excess curves from the plot and the deque
|
||||
while len(self.curves) > self.config.curve_limit:
|
||||
curve = self.curves.popleft()
|
||||
self.plot_item.removeItem(curve)
|
||||
else:
|
||||
# Hide or show curves based on the new max_trace
|
||||
num_curves_to_show = min(self.config.curve_limit, len(self.curves))
|
||||
for i, curve in enumerate(self.curves):
|
||||
if i < len(self.curves) - num_curves_to_show:
|
||||
curve.hide()
|
||||
else:
|
||||
curve.show()
|
||||
self.scale_colors()
|
||||
|
||||
def scale_colors(self):
|
||||
"""
|
||||
Scale the colors of the curves based on the current colormap.
|
||||
"""
|
||||
if self.config.highlight_last_curve:
|
||||
self.set_curve_highlight(-1) # Use -1 to highlight the last visible curve
|
||||
else:
|
||||
self.set_curve_highlight(self.current_highlight_index)
|
||||
|
||||
def set_colormap(self, colormap: str):
|
||||
"""
|
||||
Set the colormap for the curves.
|
||||
|
||||
Args:
|
||||
colormap(str): Colormap for the curves.
|
||||
"""
|
||||
self.config.color_palette = colormap
|
||||
self.set_curve_highlight(self.current_highlight_index)
|
||||
|
||||
def hook_crosshair(self) -> None:
|
||||
super().hook_crosshair()
|
||||
if self.crosshair:
|
||||
self.highlighted_curve_index_changed.connect(self.crosshair.update_highlighted_curve)
|
||||
if self.curves:
|
||||
self.crosshair.update_highlighted_curve(self.current_highlight_index)
|
||||
|
||||
def get_all_data(self, output: Literal["dict", "pandas"] = "dict") -> dict:
|
||||
"""
|
||||
Extract all curve data into a dictionary or a pandas DataFrame.
|
||||
|
||||
Args:
|
||||
output (Literal["dict", "pandas"]): Format of the output data.
|
||||
|
||||
Returns:
|
||||
dict | pd.DataFrame: Data of all curves in the specified format.
|
||||
"""
|
||||
data = {}
|
||||
try:
|
||||
import pandas as pd
|
||||
except ImportError:
|
||||
pd = None
|
||||
if output == "pandas":
|
||||
logger.warning(
|
||||
"Pandas is not installed. "
|
||||
"Please install pandas using 'pip install pandas'."
|
||||
"Output will be dictionary instead."
|
||||
)
|
||||
output = "dict"
|
||||
|
||||
curve_keys = []
|
||||
curves_list = list(self.curves)
|
||||
for i, curve in enumerate(curves_list):
|
||||
x_data, y_data = curve.getData()
|
||||
if x_data is not None or y_data is not None:
|
||||
key = f"curve_{i}"
|
||||
curve_keys.append(key)
|
||||
if output == "dict":
|
||||
data[key] = {"x": x_data.tolist(), "y": y_data.tolist()}
|
||||
elif output == "pandas" and pd is not None:
|
||||
data[key] = pd.DataFrame({"x": x_data, "y": y_data})
|
||||
|
||||
if output == "pandas" and pd is not None:
|
||||
combined_data = pd.concat([data[key] for key in curve_keys], axis=1, keys=curve_keys)
|
||||
return combined_data
|
||||
return data
|
||||
|
||||
def clear_curves(self):
|
||||
"""
|
||||
Remove all curves from the plot, excluding crosshair items.
|
||||
"""
|
||||
items_to_remove = []
|
||||
for item in self.plot_item.items:
|
||||
if not getattr(item, "is_crosshair", False) and isinstance(item, pg.PlotDataItem):
|
||||
items_to_remove.append(item)
|
||||
for item in items_to_remove:
|
||||
self.plot_item.removeItem(item)
|
||||
|
||||
def export_to_matplotlib(self):
|
||||
"""
|
||||
Export current waveform to matplotlib GUI. Available only if matplotlib is installed in the environment.
|
||||
"""
|
||||
MatplotlibExporter(self.plot_item).export()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
from bec_widgets.widgets.containers.figure import BECFigure
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
widget = BECFigure()
|
||||
widget.show()
|
||||
sys.exit(app.exec_())
|
||||
@@ -21,7 +21,7 @@ class DapComboBox(BECWidget, QWidget):
|
||||
"""
|
||||
|
||||
ICON_NAME = "data_exploration"
|
||||
PLUGIN = True
|
||||
|
||||
USER_ACCESS = ["select_y_axis", "select_x_axis", "select_fit_model"]
|
||||
|
||||
### Signals ###
|
||||
@@ -4,7 +4,7 @@
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.dap.dap_combo_box.dap_combo_box import DapComboBox
|
||||
from bec_widgets.widgets.dap_combo_box.dap_combo_box import DapComboBox
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -6,7 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.dap.dap_combo_box.dap_combo_box_plugin import DapComboBoxPlugin
|
||||
from bec_widgets.widgets.dap_combo_box.dap_combo_box_plugin import DapComboBoxPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(DapComboBoxPlugin())
|
||||
|
||||
@@ -12,7 +12,6 @@ class DarkModeButton(BECWidget, QWidget):
|
||||
USER_ACCESS = ["toggle_dark_mode"]
|
||||
|
||||
ICON_NAME = "dark_mode"
|
||||
PLUGIN = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -4,7 +4,7 @@
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
|
||||
from bec_widgets.widgets.dark_mode_button.dark_mode_button import DarkModeButton
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -6,9 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button_plugin import (
|
||||
DarkModeButtonPlugin,
|
||||
)
|
||||
from bec_widgets.widgets.dark_mode_button.dark_mode_button_plugin import DarkModeButtonPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(DarkModeButtonPlugin())
|
||||
|
||||
@@ -5,16 +5,16 @@ from typing import Optional
|
||||
from bec_lib.callback_handler import EventType
|
||||
from pyqtgraph import SignalProxy
|
||||
from qtpy.QtCore import Signal, Slot
|
||||
from qtpy.QtWidgets import QListWidgetItem, QVBoxLayout, QWidget
|
||||
from qtpy.QtWidgets import QLineEdit, QListWidgetItem, QVBoxLayout, QWidget
|
||||
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.ui_loader import UILoader
|
||||
from bec_widgets.widgets.services.device_browser.device_item import DeviceItem
|
||||
from bec_widgets.widgets.device_browser.device_item import DeviceItem
|
||||
|
||||
|
||||
class DeviceBrowser(BECWidget, QWidget):
|
||||
device_update: Signal = Signal()
|
||||
PLUGIN = True
|
||||
|
||||
ICON_NAME = "lists"
|
||||
|
||||
def __init__(
|
||||
@@ -4,7 +4,7 @@
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.services.device_browser.device_browser import DeviceBrowser
|
||||
from bec_widgets.widgets.device_browser.device_browser import DeviceBrowser
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -6,9 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.services.device_browser.device_browser_plugin import (
|
||||
DeviceBrowserPlugin,
|
||||
)
|
||||
from bec_widgets.widgets.device_browser.device_browser_plugin import DeviceBrowserPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(DeviceBrowserPlugin())
|
||||
|
||||
@@ -6,7 +6,7 @@ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.control.device_input.device_combobox.device_combobox import DeviceComboBox
|
||||
from bec_widgets.widgets.device_combobox.device_combobox import DeviceComboBox
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -5,7 +5,7 @@ from qtpy.QtGui import QPainter, QPaintEvent, QPen
|
||||
from qtpy.QtWidgets import QComboBox, QSizePolicy
|
||||
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_widgets.widgets.control.device_input.base_classes.device_input_base import (
|
||||
from bec_widgets.widgets.base_classes.device_input_base import (
|
||||
BECDeviceFilter,
|
||||
DeviceInputBase,
|
||||
DeviceInputConfig,
|
||||
@@ -27,7 +27,6 @@ class DeviceComboBox(DeviceInputBase, QComboBox):
|
||||
"""
|
||||
|
||||
ICON_NAME = "list_alt"
|
||||
PLUGIN = True
|
||||
|
||||
device_selected = Signal(str)
|
||||
device_config_update = Signal()
|
||||
@@ -6,9 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.control.device_input.device_combobox.device_combo_box_plugin import (
|
||||
DeviceComboBoxPlugin,
|
||||
)
|
||||
from bec_widgets.widgets.device_combobox.device_combo_box_plugin import DeviceComboBoxPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(DeviceComboBoxPlugin())
|
||||
|
||||
@@ -6,7 +6,7 @@ from qtpy.QtGui import QPainter, QPaintEvent, QPen
|
||||
from qtpy.QtWidgets import QApplication, QCompleter, QLineEdit, QSizePolicy
|
||||
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_widgets.widgets.control.device_input.base_classes.device_input_base import (
|
||||
from bec_widgets.widgets.base_classes.device_input_base import (
|
||||
BECDeviceFilter,
|
||||
DeviceInputBase,
|
||||
DeviceInputConfig,
|
||||
@@ -32,7 +32,6 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit):
|
||||
device_selected = Signal(str)
|
||||
device_config_update = Signal()
|
||||
|
||||
PLUGIN = True
|
||||
ICON_NAME = "edit_note"
|
||||
|
||||
def __init__(
|
||||
@@ -157,9 +156,7 @@ if __name__ == "__main__": # pragma: no cover
|
||||
from qtpy.QtWidgets import QVBoxLayout, QWidget
|
||||
|
||||
from bec_widgets.utils.colors import set_theme
|
||||
from bec_widgets.widgets.control.device_input.signal_combobox.signal_combobox import (
|
||||
SignalComboBox,
|
||||
)
|
||||
from bec_widgets.widgets.signal_combobox.signal_combobox import SignalComboBox
|
||||
|
||||
app = QApplication([])
|
||||
set_theme("dark")
|
||||
@@ -6,9 +6,7 @@ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.control.device_input.device_line_edit.device_line_edit import (
|
||||
DeviceLineEdit,
|
||||
)
|
||||
from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
@@ -6,9 +6,7 @@ def main(): # pragma: no cover
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.control.device_input.device_line_edit.device_line_edit_plugin import (
|
||||
DeviceLineEditPlugin,
|
||||
)
|
||||
from bec_widgets.widgets.device_line_edit.device_line_edit_plugin import DeviceLineEditPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(DeviceLineEditPlugin())
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional
|
||||
|
||||
from qtpy.QtCore import QSize
|
||||
from pydantic import Field
|
||||
from pyqtgraph.dockarea import Dock, DockLabel
|
||||
from qtpy import QtCore, QtGui
|
||||
@@ -116,6 +117,7 @@ class BECDock(BECWidget, Dock):
|
||||
"remove",
|
||||
"attach",
|
||||
"detach",
|
||||
"close",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
@@ -126,6 +128,7 @@ class BECDock(BECWidget, Dock):
|
||||
name: str | None = None,
|
||||
client=None,
|
||||
gui_id: str | None = None,
|
||||
temp: bool = False,
|
||||
closable: bool = True,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
@@ -147,6 +150,8 @@ class BECDock(BECWidget, Dock):
|
||||
# Layout Manager
|
||||
self.layout_manager = GridLayoutManager(self.layout)
|
||||
|
||||
self.temp = temp
|
||||
|
||||
def dropEvent(self, event):
|
||||
source = event.source()
|
||||
old_area = source.area
|
||||
@@ -172,6 +177,24 @@ class BECDock(BECWidget, Dock):
|
||||
else:
|
||||
super().float()
|
||||
|
||||
if self.temp:
|
||||
self.make_dock_temporary()
|
||||
|
||||
def make_dock_temporary(self):
|
||||
"""
|
||||
Make the dock temporary.
|
||||
"""
|
||||
|
||||
from bec_widgets.widgets.dock import BECDockArea
|
||||
|
||||
self.orig_area.docks.pop(self.name(), None)
|
||||
self.orig_area = BECDockArea()
|
||||
self.area = self.orig_area
|
||||
self.area.panels[self.name()] = self
|
||||
self.config.parent_dock_area = self.area.gui_id
|
||||
self.area.temporary = False
|
||||
self.hide_title_bar()
|
||||
|
||||
@property
|
||||
def widget_list(self) -> list[BECWidget]:
|
||||
"""
|
||||
@@ -333,3 +356,7 @@ class BECDock(BECWidget, Dock):
|
||||
self.cleanup()
|
||||
super().close()
|
||||
self.parent_dock_area.dock_area.docks.pop(self.name(), None)
|
||||
|
||||
if self.temp:
|
||||
self.area.deleteLater()
|
||||
self.deleteLater()
|
||||
@@ -18,18 +18,17 @@ from bec_widgets.qt_utils.toolbar import (
|
||||
)
|
||||
from bec_widgets.utils import ConnectionConfig, WidgetContainerUtils
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.widgets.containers.dock.dock import BECDock, DockConfig
|
||||
from bec_widgets.widgets.control.device_control.positioner_box.positioner_box import PositionerBox
|
||||
from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
|
||||
from bec_widgets.widgets.editors.vscode.vscode import VSCodeEditor
|
||||
from bec_widgets.widgets.plots.image.image_widget import BECImageWidget
|
||||
from bec_widgets.widgets.plots.motor_map.motor_map_widget import BECMotorMapWidget
|
||||
from bec_widgets.widgets.plots.multi_waveform.multi_waveform_widget import BECMultiWaveformWidget
|
||||
from bec_widgets.widgets.plots.waveform.waveform_widget import BECWaveformWidget
|
||||
from bec_widgets.widgets.progress.ring_progress_bar.ring_progress_bar import RingProgressBar
|
||||
from bec_widgets.widgets.services.bec_queue.bec_queue import BECQueue
|
||||
from bec_widgets.widgets.services.bec_status_box.bec_status_box import BECStatusBox
|
||||
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
|
||||
from bec_widgets.widgets.bec_queue.bec_queue import BECQueue
|
||||
from bec_widgets.widgets.bec_status_box.bec_status_box import BECStatusBox
|
||||
from bec_widgets.widgets.dark_mode_button.dark_mode_button import DarkModeButton
|
||||
from bec_widgets.widgets.dock.dock import BECDock, DockConfig
|
||||
from bec_widgets.widgets.image.image_widget import BECImageWidget
|
||||
from bec_widgets.widgets.motor_map.motor_map_widget import BECMotorMapWidget
|
||||
from bec_widgets.widgets.positioner_box.positioner_box import PositionerBox
|
||||
from bec_widgets.widgets.ring_progress_bar.ring_progress_bar import RingProgressBar
|
||||
from bec_widgets.widgets.scan_control.scan_control import ScanControl
|
||||
from bec_widgets.widgets.vscode.vscode import VSCodeEditor
|
||||
from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
|
||||
|
||||
|
||||
class DockAreaConfig(ConnectionConfig):
|
||||
@@ -40,7 +39,6 @@ class DockAreaConfig(ConnectionConfig):
|
||||
|
||||
|
||||
class BECDockArea(BECWidget, QWidget):
|
||||
PLUGIN = True
|
||||
USER_ACCESS = [
|
||||
"_config_dict",
|
||||
"panels",
|
||||
@@ -87,11 +85,6 @@ class BECDockArea(BECWidget, QWidget):
|
||||
tooltip="Add Waveform",
|
||||
filled=True,
|
||||
),
|
||||
"multi_waveform": MaterialIconAction(
|
||||
icon_name=BECMultiWaveformWidget.ICON_NAME,
|
||||
tooltip="Add Multi Waveform",
|
||||
filled=True,
|
||||
),
|
||||
"image": MaterialIconAction(
|
||||
icon_name=BECImageWidget.ICON_NAME, tooltip="Add Image", filled=True
|
||||
),
|
||||
@@ -161,9 +154,6 @@ class BECDockArea(BECWidget, QWidget):
|
||||
self.toolbar.widgets["menu_plots"].widgets["waveform"].triggered.connect(
|
||||
lambda: self.add_dock(widget="BECWaveformWidget", prefix="waveform")
|
||||
)
|
||||
self.toolbar.widgets["menu_plots"].widgets["multi_waveform"].triggered.connect(
|
||||
lambda: self.add_dock(widget="BECMultiWaveformWidget", prefix="multi_waveform")
|
||||
)
|
||||
self.toolbar.widgets["menu_plots"].widgets["image"].triggered.connect(
|
||||
lambda: self.add_dock(widget="BECImageWidget", prefix="image")
|
||||
)
|
||||
@@ -288,6 +278,7 @@ class BECDockArea(BECWidget, QWidget):
|
||||
name: str = None,
|
||||
position: Literal["bottom", "top", "left", "right", "above", "below"] = None,
|
||||
relative_to: BECDock | None = None,
|
||||
temporary: bool = False,
|
||||
closable: bool = True,
|
||||
floating: bool = False,
|
||||
prefix: str = "dock",
|
||||
@@ -304,6 +295,7 @@ class BECDockArea(BECWidget, QWidget):
|
||||
name(str): The name of the dock to be displayed and for further references. Has to be unique.
|
||||
position(Literal["bottom", "top", "left", "right", "above", "below"]): The position of the dock.
|
||||
relative_to(BECDock): The dock to which the new dock should be added relative to.
|
||||
temp(bool): Whether the dock is temporary. Upon closing the dock is not returned to the parent dock area.
|
||||
closable(bool): Whether the dock is closable.
|
||||
floating(bool): Whether the dock is detached after creating.
|
||||
prefix(str): The prefix for the dock name if no name is provided.
|
||||
@@ -327,7 +319,7 @@ class BECDockArea(BECWidget, QWidget):
|
||||
if position is None:
|
||||
position = "bottom"
|
||||
|
||||
dock = BECDock(name=name, parent_dock_area=self, closable=closable)
|
||||
dock = BECDock(name=name, parent_dock_area=self, closable=closable, temp=temporary)
|
||||
dock.config.position = position
|
||||
self.config.docks[name] = dock.config
|
||||
|
||||
@@ -348,10 +340,19 @@ class BECDockArea(BECWidget, QWidget):
|
||||
): # TODO still decide how initial instructions should be handled
|
||||
self._instructions_visible = False
|
||||
self.update()
|
||||
if floating:
|
||||
if floating or temporary:
|
||||
dock.detach()
|
||||
print("dock added")
|
||||
return dock
|
||||
|
||||
# def add_temp_dock(self):
|
||||
# area = BECDockArea()
|
||||
# area.show()
|
||||
# area.add_dock("dock1", widget="BECFigure")
|
||||
|
||||
def addDock(self, *args, **kwargs):
|
||||
return self.add_dock(*args, **kwargs)
|
||||
|
||||
def detach_dock(self, dock_name: str) -> BECDock:
|
||||
"""
|
||||
Undock a dock from the dock area.
|
||||
@@ -412,6 +413,11 @@ class BECDockArea(BECWidget, QWidget):
|
||||
self.cleanup()
|
||||
super().close()
|
||||
|
||||
def closeEvent(self, event):
|
||||
print("close event called")
|
||||
self.cleanup()
|
||||
super().closeEvent(event)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from qtpy.QtWidgets import QApplication
|
||||
@@ -422,4 +428,5 @@ if __name__ == "__main__":
|
||||
set_theme("auto")
|
||||
dock_area = BECDockArea()
|
||||
dock_area.show()
|
||||
dock_area.add_dock("dock1", widget="BECFigure", temporary=True)
|
||||
app.exec_()
|
||||
@@ -6,7 +6,7 @@ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.containers.dock import BECDockArea
|
||||
from bec_widgets.widgets.dock import BECDockArea
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user