0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-13 19:21:50 +02:00

refactor(rpc/server): rpc server can accept BEC container class as a argument; the widget container of BECFigure container property s added to BECFigure; isort applied

This commit is contained in:
2024-04-22 21:31:12 +02:00
parent 4fa9f3f1d1
commit c0356b94db
9 changed files with 117 additions and 69 deletions

View File

@ -248,11 +248,11 @@ class BECWaveform(RPCBase):
""" """
@rpc_call @rpc_call
def apply_config(self, config: "dict | WidgetConfig", replot_last_scan: "bool" = False): def apply_config(self, config: "dict | SubplotConfig", replot_last_scan: "bool" = False):
""" """
Apply the configuration to the 1D waveform widget. Apply the configuration to the 1D waveform widget.
Args: Args:
config(dict|WidgetConfig): Configuration settings. config(dict|SubplotConfig): Configuration settings.
replot_last_scan(bool, optional): If True, replot the last scan. Defaults to False. replot_last_scan(bool, optional): If True, replot the last scan. Defaults to False.
""" """
@ -614,6 +614,13 @@ class BECFigure(RPCBase, BECFigureClientMixin):
Clear all widgets from the figure and reset to default state Clear all widgets from the figure and reset to default state
""" """
@property
@rpc_call
def containers(self) -> "dict":
"""
None
"""
class BECCurve(RPCBase): class BECCurve(RPCBase):
@property @property

View File

@ -1,10 +1,12 @@
import inspect import inspect
from typing import Literal
from bec_lib import MessageEndpoints, messages from bec_lib import MessageEndpoints, messages
from qtpy.QtCore import QTimer from qtpy.QtCore import QTimer
from bec_widgets.utils import BECDispatcher from bec_widgets.utils import BECDispatcher
from bec_widgets.utils.bec_connector import BECConnector from bec_widgets.utils.bec_connector import BECConnector
from bec_widgets.widgets.dock.dock_area import BECDockArea
from bec_widgets.widgets.figure import BECFigure from bec_widgets.widgets.figure import BECFigure
from bec_widgets.widgets.plots import BECCurve, BECImageShow, BECWaveform from bec_widgets.widgets.plots import BECCurve, BECImageShow, BECWaveform
@ -12,12 +14,18 @@ from bec_widgets.widgets.plots import BECCurve, BECImageShow, BECWaveform
class BECWidgetsCLIServer: class BECWidgetsCLIServer:
WIDGETS = [BECWaveform, BECFigure, BECCurve, BECImageShow] WIDGETS = [BECWaveform, BECFigure, BECCurve, BECImageShow]
def __init__(self, gui_id: str = None, dispatcher: BECDispatcher = None, client=None) -> None: def __init__(
self,
gui_id: str = None,
dispatcher: BECDispatcher = None,
client=None,
gui_class: BECFigure | BECDockArea = BECFigure,
) -> None:
self.dispatcher = BECDispatcher() if dispatcher is None else dispatcher self.dispatcher = BECDispatcher() if dispatcher is None else dispatcher
self.client = self.dispatcher.client if client is None else client self.client = self.dispatcher.client if client is None else client
self.client.start() self.client.start()
self.gui_id = gui_id self.gui_id = gui_id
self.fig = BECFigure(gui_id=self.gui_id) self.gui = gui_class(gui_id=self.gui_id)
self.dispatcher.connect_slot( self.dispatcher.connect_slot(
self.on_rpc_update, MessageEndpoints.gui_instructions(self.gui_id) self.on_rpc_update, MessageEndpoints.gui_instructions(self.gui_id)
@ -53,14 +61,14 @@ class BECWidgetsCLIServer:
def get_object_from_config(self, config: dict): def get_object_from_config(self, config: dict):
gui_id = config.get("gui_id") gui_id = config.get("gui_id")
# check if the object is the figure # check if the object is the figure
if gui_id == self.fig.gui_id: if gui_id == self.gui.gui_id:
return self.fig return self.gui
# check if the object is a widget # check if the object is a widget
if gui_id in self.fig._widgets: if gui_id in self.gui.containers:
obj = self.fig._widgets[config["gui_id"]] obj = self.gui.containers[config["gui_id"]]
return obj return obj
if self.fig._widgets: if self.gui.containers:
for widget in self.fig._widgets.values(): for widget in self.gui.containers.values():
item = widget.find_widget_by_id(gui_id) item = widget.find_widget_by_id(gui_id)
if item: if item:
return item return item
@ -123,13 +131,29 @@ if __name__ == "__main__": # pragma: no cover
parser = argparse.ArgumentParser(description="BEC Widgets CLI Server") parser = argparse.ArgumentParser(description="BEC Widgets CLI Server")
parser.add_argument("--id", type=str, help="The id of the server") parser.add_argument("--id", type=str, help="The id of the server")
parser.add_argument(
"--gui_class",
type=str,
help="Name of the gui class to be rendered. Possible values: \n- BECFigure\n- BECDockArea",
)
args = parser.parse_args() args = parser.parse_args()
server = BECWidgetsCLIServer(gui_id=args.id) if args.gui_class == "BECFigure":
# server = BECWidgetsCLIServer(gui_id="test") gui_class = BECFigure
elif args.gui_class == "BECDockArea":
gui_class = BECDockArea
else:
print(
"Please specify a valid gui_class to run. Use -h for help."
"\n Starting with default gui_class BECFigure."
)
gui_class = BECFigure
fig = server.fig # server = BECWidgetsCLIServer(gui_id=args.id, gui_class=gui_class)
server = BECWidgetsCLIServer(gui_id="test", gui_class=gui_class)
fig = server.gui
win.setCentralWidget(fig) win.setCentralWidget(fig)
win.show() win.show()

View File

@ -5,13 +5,13 @@ import pyqtgraph as pg
from PyQt6.QtCore import QSize from PyQt6.QtCore import QSize
from PyQt6.QtGui import QIcon from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QMainWindow from PyQt6.QtWidgets import QMainWindow
from pyqtgraph.Qt import uic, QtWidgets from pyqtgraph.Qt import QtWidgets, uic
from qtconsole.inprocess import QtInProcessKernelManager from qtconsole.inprocess import QtInProcessKernelManager
from qtconsole.rich_jupyter_widget import RichJupyterWidget from qtconsole.rich_jupyter_widget import RichJupyterWidget
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
from bec_widgets.utils import BECDispatcher from bec_widgets.utils import BECDispatcher
from bec_widgets.widgets import BECFigure, BECDockArea from bec_widgets.widgets import BECDockArea, BECFigure
class JupyterConsoleWidget(RichJupyterWidget): # pragma: no cover: class JupyterConsoleWidget(RichJupyterWidget): # pragma: no cover:

View File

@ -11,4 +11,3 @@ from .motor_control import (
from .motor_map import MotorMap from .motor_map import MotorMap
from .plots import BECCurve, BECMotorMap, BECWaveform from .plots import BECCurve, BECMotorMap, BECWaveform
from .scan_control import ScanControl from .scan_control import ScanControl
from .dock import BECDockArea, BECDock

View File

@ -1 +0,0 @@
from .dock_area import BECDockArea, BECDock

View File

@ -0,0 +1,50 @@
from typing import Literal, Optional
from pydantic import Field
from pyqtgraph.dockarea import Dock
from qtpy.QtWidgets import QWidget
from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils
class DockConfig(ConnectionConfig):
widgets: dict[str, ConnectionConfig] = Field({}, description="The widgets in the dock.")
position: Literal["bottom", "top", "left", "right", "above", "below"] = Field(
"bottom", description="The position of the dock."
)
parent_dock_area: Optional[str] = Field(
None, description="The GUI ID of parent dock area of the dock."
)
class BECDock(BECConnector, Dock):
def __init__(
self,
parent: Optional[QWidget] = None,
parent_dock_area: Optional["BECDockArea"] = None,
config: Optional[
DockConfig
] = None, # TODO ATM connection config -> will be changed when I will know what I want to use there
name: Optional[str] = None,
client=None,
gui_id: Optional[str] = None,
**kwargs,
) -> None:
if config is None:
config = DockConfig(
widget_class=self.__class__.__name__, parent_dock_area=parent_dock_area.gui_id
)
else:
if isinstance(config, dict):
config = DockConfig(**config)
self.config = config
super().__init__(client=client, config=config, gui_id=gui_id)
Dock.__init__(self, name=name, **kwargs)
self.parent_dock_area = parent_dock_area
self.sigClosed.connect(self._remove_from_dock_area)
def _remove_from_dock_area(self):
"""Remove this dock from the DockArea it lives inside."""
self.parent_dock_area.docks.pop(self.name())

View File

@ -1,62 +1,22 @@
from collections import defaultdict from collections import defaultdict
from typing import Optional, Literal from typing import Literal, Optional
import pyqtgraph as pg import pyqtgraph as pg
from qtpy.QtWidgets import QWidget
from pydantic import Field from pydantic import Field
from pyqtgraph.dockarea.DockArea import DockArea, Dock from pyqtgraph.dockarea.DockArea import Dock, DockArea
from qtpy.QtWidgets import QWidget
from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils
from bec_widgets.widgets import BECWaveform, BECFigure, BECMotorMap from bec_widgets.widgets import BECFigure, BECMotorMap, BECWaveform
from bec_widgets.widgets.plots import BECImageShow from bec_widgets.widgets.plots import BECImageShow
from .dock import BECDock, DockConfig
class DockConfig(ConnectionConfig):
widgets: dict[str, ConnectionConfig] = Field({}, description="The widgets in the dock.")
position: Literal["bottom", "top", "left", "right", "above", "below"] = Field(
"bottom", description="The position of the dock."
)
parent_dock_area: Optional[str] = Field(
None, description="The GUI ID of parent dock area of the dock."
)
class DockAreaConfig(ConnectionConfig): class DockAreaConfig(ConnectionConfig):
docks: dict[str, DockConfig] = Field({}, description="The docks in the dock area.") docks: dict[str, DockConfig] = Field({}, description="The docks in the dock area.")
class BECDock(BECConnector, Dock):
def __init__(
self,
parent: Optional[QWidget] = None,
parent_dock_area: Optional["BECDockArea"] = None,
config: Optional[
DockConfig
] = None, # TODO ATM connection config -> will be changed when I will know what I want to use there
name: Optional[str] = None,
client=None,
gui_id: Optional[str] = None,
**kwargs,
) -> None:
if config is None:
config = DockConfig(
widget_class=self.__class__.__name__, parent_dock_area=parent_dock_area.gui_id
)
else:
if isinstance(config, dict):
config = DockConfig(**config)
self.config = config
super().__init__(client=client, config=config, gui_id=gui_id)
Dock.__init__(self, name=name, **kwargs)
self.parent_dock_area = parent_dock_area
self.sigClosed.connect(self._remove_from_dock_area)
def _remove_from_dock_area(self):
"""Remove this dock from the DockArea it lives inside."""
self.parent_dock_area.docks.pop(self.name())
class BECDockArea(BECConnector, DockArea): class BECDockArea(BECConnector, DockArea):
USER_ACCESS = ["figure", "plot", "image", "motor_map", "add_dock", "remove_dock_by_id", "clear"] USER_ACCESS = ["figure", "plot", "image", "motor_map", "add_dock", "remove_dock_by_id", "clear"]

View File

@ -20,8 +20,8 @@ from bec_widgets.widgets.plots import (
BECMotorMap, BECMotorMap,
BECPlotBase, BECPlotBase,
BECWaveform, BECWaveform,
Waveform1DConfig,
SubplotConfig, SubplotConfig,
Waveform1DConfig,
) )
from bec_widgets.widgets.plots.image import ImageConfig from bec_widgets.widgets.plots.image import ImageConfig
from bec_widgets.widgets.plots.motor_map import MotorMapConfig from bec_widgets.widgets.plots.motor_map import MotorMapConfig
@ -110,6 +110,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
"change_layout", "change_layout",
"change_theme", "change_theme",
"clear_all", "clear_all",
"containers",
] ]
clean_signal = pyqtSignal() clean_signal = pyqtSignal()
@ -160,6 +161,14 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
def widgets(self, value: dict): def widgets(self, value: dict):
self._widgets = value self._widgets = value
@property
def containers(self) -> dict:
return self._widgets
@containers.setter
def containers(self, value: dict):
self._widgets = value
def add_plot( def add_plot(
self, self,
x_name: str = None, x_name: str = None,

View File

@ -11,8 +11,8 @@ from bec_widgets.utils import BECDispatcher
def rpc_server(qtbot, bec_client_lib, threads_check): def rpc_server(qtbot, bec_client_lib, threads_check):
dispatcher = BECDispatcher(client=bec_client_lib) # Has to init singleton with fixture client dispatcher = BECDispatcher(client=bec_client_lib) # Has to init singleton with fixture client
server = BECWidgetsCLIServer(gui_id="id_test") server = BECWidgetsCLIServer(gui_id="id_test")
qtbot.addWidget(server.fig) qtbot.addWidget(server.gui)
qtbot.waitExposed(server.fig) qtbot.waitExposed(server.gui)
qtbot.wait(1000) # 1s long to wait until gui is ready qtbot.wait(1000) # 1s long to wait until gui is ready
yield server yield server
dispatcher.disconnect_all() dispatcher.disconnect_all()
@ -23,7 +23,7 @@ def rpc_server(qtbot, bec_client_lib, threads_check):
def test_rpc_waveform1d_custom_curve(rpc_server, qtbot): def test_rpc_waveform1d_custom_curve(rpc_server, qtbot):
fig = BECFigure(rpc_server.gui_id) fig = BECFigure(rpc_server.gui_id)
fig_server = rpc_server.fig fig_server = rpc_server.gui
ax = fig.add_plot() ax = fig.add_plot()
curve = ax.add_curve_custom([1, 2, 3], [1, 2, 3]) curve = ax.add_curve_custom([1, 2, 3], [1, 2, 3])
@ -37,7 +37,7 @@ def test_rpc_waveform1d_custom_curve(rpc_server, qtbot):
def test_rpc_plotting_shortcuts_init_configs(rpc_server, qtbot): def test_rpc_plotting_shortcuts_init_configs(rpc_server, qtbot):
fig = BECFigure(rpc_server.gui_id) fig = BECFigure(rpc_server.gui_id)
fig_server = rpc_server.fig fig_server = rpc_server.gui
plt = fig.plot("samx", "bpm4i") plt = fig.plot("samx", "bpm4i")
im = fig.image("eiger") im = fig.image("eiger")
@ -151,7 +151,7 @@ def test_rpc_image(rpc_server, qtbot):
def test_rpc_motor_map(rpc_server, qtbot): def test_rpc_motor_map(rpc_server, qtbot):
fig = BECFigure(rpc_server.gui_id) fig = BECFigure(rpc_server.gui_id)
fig_server = rpc_server.fig fig_server = rpc_server.gui
motor_map = fig.motor_map("samx", "samy") motor_map = fig.motor_map("samx", "samy")