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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
 
4
4
 
5
+ ## v0.55.0 (2024-05-24)
6
+
7
+ ### Feature
8
+
9
+ * feat(widgets/progressbar): SpiralProgressBar added with rpc interface ([`76bd0d3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/76bd0d339ac9ae9e8a3baa0d0d4e951ec1d09670))
10
+
11
+
5
12
  ## v0.54.0 (2024-05-24)
6
13
 
7
14
  ### Build
@@ -162,11 +169,3 @@
162
169
 
163
170
 
164
171
  ## v0.49.1 (2024-04-26)
165
-
166
- ### Build
167
-
168
- * build(pyqt6): fixing PyQt6-Qt6 package to 6.6.3 ([`a222298`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a22229849cbb57c15e4c1bae02d7e52e672f8c4c))
169
-
170
- ### Fix
171
-
172
- * fix(widgets/editor): qscintilla editor removed ([`ab85374`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ab8537483da6c87cb9a0b0f01706208c964f292d))
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 0.54.0
3
+ Version: 0.55.0
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
bec_widgets/cli/client.py CHANGED
@@ -1630,3 +1630,255 @@ class BECDockArea(RPCBase, BECGuiClientMixin):
1630
1630
  """
1631
1631
  Get all registered RPC objects.
1632
1632
  """
1633
+
1634
+
1635
+ class SpiralProgressBar(RPCBase):
1636
+ @rpc_call
1637
+ def get_all_rpc(self) -> "dict":
1638
+ """
1639
+ Get all registered RPC objects.
1640
+ """
1641
+
1642
+ @property
1643
+ @rpc_call
1644
+ def rpc_id(self) -> "str":
1645
+ """
1646
+ Get the RPC ID of the widget.
1647
+ """
1648
+
1649
+ @property
1650
+ @rpc_call
1651
+ def config_dict(self) -> "dict":
1652
+ """
1653
+ Get the configuration of the widget.
1654
+
1655
+ Returns:
1656
+ dict: The configuration of the widget.
1657
+ """
1658
+
1659
+ @property
1660
+ @rpc_call
1661
+ def rings(self):
1662
+ """
1663
+ None
1664
+ """
1665
+
1666
+ @rpc_call
1667
+ def update_config(self, config: "SpiralProgressBarConfig | dict"):
1668
+ """
1669
+ Update the configuration of the widget.
1670
+
1671
+ Args:
1672
+ config(SpiralProgressBarConfig|dict): Configuration to update.
1673
+ """
1674
+
1675
+ @rpc_call
1676
+ def add_ring(self, **kwargs) -> "Ring":
1677
+ """
1678
+ Add a new progress bar.
1679
+
1680
+ Args:
1681
+ **kwargs: Keyword arguments for the new progress bar.
1682
+
1683
+ Returns:
1684
+ Ring: Ring object.
1685
+ """
1686
+
1687
+ @rpc_call
1688
+ def remove_ring(self, index: "int"):
1689
+ """
1690
+ Remove a progress bar by index.
1691
+
1692
+ Args:
1693
+ index(int): Index of the progress bar to remove.
1694
+ """
1695
+
1696
+ @rpc_call
1697
+ def set_precision(self, precision: "int", bar_index: "int" = None):
1698
+ """
1699
+ Set the precision for the progress bars. If bar_index is not provide, the precision will be set for all progress bars.
1700
+
1701
+ Args:
1702
+ precision(int): Precision for the progress bars.
1703
+ bar_index(int): Index of the progress bar to set the precision for. If provided, only a single precision can be set.
1704
+ """
1705
+
1706
+ @rpc_call
1707
+ def set_min_max_values(
1708
+ self,
1709
+ min_values: "int | float | list[int | float]",
1710
+ max_values: "int | float | list[int | float]",
1711
+ ):
1712
+ """
1713
+ Set the minimum and maximum values for the progress bars.
1714
+
1715
+ Args:
1716
+ min_values(int|float | list[float]): Minimum value(s) for the progress bars. If multiple progress bars are displayed, provide a list of minimum values for each progress bar.
1717
+ max_values(int|float | list[float]): Maximum value(s) for the progress bars. If multiple progress bars are displayed, provide a list of maximum values for each progress bar.
1718
+ """
1719
+
1720
+ @rpc_call
1721
+ def set_number_of_bars(self, num_bars: "int"):
1722
+ """
1723
+ Set the number of progress bars to display.
1724
+
1725
+ Args:
1726
+ num_bars(int): Number of progress bars to display.
1727
+ """
1728
+
1729
+ @rpc_call
1730
+ def set_value(self, values: "int | list", ring_index: "int" = None):
1731
+ """
1732
+ Set the values for the progress bars.
1733
+
1734
+ Args:
1735
+ values(int | tuple): Value(s) for the progress bars. If multiple progress bars are displayed, provide a tuple of values for each progress bar.
1736
+ ring_index(int): Index of the progress bar to set the value for. If provided, only a single value can be set.
1737
+
1738
+ Examples:
1739
+ >>> SpiralProgressBar.set_value(50)
1740
+ >>> SpiralProgressBar.set_value([30, 40, 50]) # (outer, middle, inner)
1741
+ >>> SpiralProgressBar.set_value(60, bar_index=1) # Set the value for the middle progress bar.
1742
+ """
1743
+
1744
+ @rpc_call
1745
+ def set_colors_from_map(self, colormap, color_format: "Literal['RGB', 'HEX']" = "RGB"):
1746
+ """
1747
+ Set the colors for the progress bars from a colormap.
1748
+
1749
+ Args:
1750
+ colormap(str): Name of the colormap.
1751
+ color_format(Literal["RGB","HEX"]): Format of the returned colors ('RGB', 'HEX').
1752
+ """
1753
+
1754
+ @rpc_call
1755
+ def set_colors_directly(
1756
+ self, colors: "list[str | tuple] | str | tuple", bar_index: "int" = None
1757
+ ):
1758
+ """
1759
+ Set the colors for the progress bars directly.
1760
+
1761
+ Args:
1762
+ colors(list[str | tuple] | str | tuple): Color(s) for the progress bars. If multiple progress bars are displayed, provide a list of colors for each progress bar.
1763
+ bar_index(int): Index of the progress bar to set the color for. If provided, only a single color can be set.
1764
+ """
1765
+
1766
+ @rpc_call
1767
+ def set_line_widths(self, widths: "int | list[int]", bar_index: "int" = None):
1768
+ """
1769
+ Set the line widths for the progress bars.
1770
+
1771
+ Args:
1772
+ widths(int | list[int]): Line width(s) for the progress bars. If multiple progress bars are displayed, provide a list of line widths for each progress bar.
1773
+ bar_index(int): Index of the progress bar to set the line width for. If provided, only a single line width can be set.
1774
+ """
1775
+
1776
+ @rpc_call
1777
+ def set_gap(self, gap: "int"):
1778
+ """
1779
+ Set the gap between the progress bars.
1780
+
1781
+ Args:
1782
+ gap(int): Gap between the progress bars.
1783
+ """
1784
+
1785
+ @rpc_call
1786
+ def set_diameter(self, diameter: "int"):
1787
+ """
1788
+ Set the diameter of the widget.
1789
+
1790
+ Args:
1791
+ diameter(int): Diameter of the widget.
1792
+ """
1793
+
1794
+ @rpc_call
1795
+ def reset_diameter(self):
1796
+ """
1797
+ Reset the fixed size of the widget.
1798
+ """
1799
+
1800
+ @rpc_call
1801
+ def enable_auto_updates(self, enable: "bool" = True):
1802
+ """
1803
+ Enable or disable updates based on scan status. Overrides manual updates.
1804
+ The behaviour of the whole progress bar widget will be driven by the scan queue status.
1805
+
1806
+ Args:
1807
+ enable(bool): True or False.
1808
+
1809
+ Returns:
1810
+ bool: True if scan segment updates are enabled.
1811
+ """
1812
+
1813
+
1814
+ class Ring(RPCBase):
1815
+ @rpc_call
1816
+ def get_all_rpc(self) -> "dict":
1817
+ """
1818
+ Get all registered RPC objects.
1819
+ """
1820
+
1821
+ @property
1822
+ @rpc_call
1823
+ def rpc_id(self) -> "str":
1824
+ """
1825
+ Get the RPC ID of the widget.
1826
+ """
1827
+
1828
+ @property
1829
+ @rpc_call
1830
+ def config_dict(self) -> "dict":
1831
+ """
1832
+ Get the configuration of the widget.
1833
+
1834
+ Returns:
1835
+ dict: The configuration of the widget.
1836
+ """
1837
+
1838
+ @rpc_call
1839
+ def set_value(self, value: "int | float"):
1840
+ """
1841
+ None
1842
+ """
1843
+
1844
+ @rpc_call
1845
+ def set_color(self, color: "str | tuple"):
1846
+ """
1847
+ None
1848
+ """
1849
+
1850
+ @rpc_call
1851
+ def set_background(self, color: "str | tuple"):
1852
+ """
1853
+ None
1854
+ """
1855
+
1856
+ @rpc_call
1857
+ def set_line_width(self, width: "int"):
1858
+ """
1859
+ None
1860
+ """
1861
+
1862
+ @rpc_call
1863
+ def set_min_max_values(self, min_value: "int", max_value: "int"):
1864
+ """
1865
+ None
1866
+ """
1867
+
1868
+ @rpc_call
1869
+ def set_start_angle(self, start_angle: "int"):
1870
+ """
1871
+ None
1872
+ """
1873
+
1874
+ @rpc_call
1875
+ def set_connections(self, slot: "str", endpoint: "str | EndpointInfo"):
1876
+ """
1877
+ None
1878
+ """
1879
+
1880
+ @rpc_call
1881
+ def reset_connection(self):
1882
+ """
1883
+ None
1884
+ """
@@ -109,13 +109,14 @@ if __name__ == "__main__": # pragma: no cover
109
109
  import os
110
110
 
111
111
  from bec_widgets.utils import BECConnector
112
- from bec_widgets.widgets import BECDock, BECDockArea, BECFigure
112
+ from bec_widgets.widgets import BECDock, BECDockArea, BECFigure, SpiralProgressBar
113
113
  from bec_widgets.widgets.figure.plots.image.image import BECImageShow
114
114
  from bec_widgets.widgets.figure.plots.image.image_item import BECImageItem
115
115
  from bec_widgets.widgets.figure.plots.motor_map.motor_map import BECMotorMap
116
116
  from bec_widgets.widgets.figure.plots.plot_base import BECPlotBase
117
117
  from bec_widgets.widgets.figure.plots.waveform.waveform import BECWaveform
118
118
  from bec_widgets.widgets.figure.plots.waveform.waveform_curve import BECCurve
119
+ from bec_widgets.widgets.spiral_progress_bar.ring import Ring
119
120
 
120
121
  current_path = os.path.dirname(__file__)
121
122
  client_path = os.path.join(current_path, "client.py")
@@ -130,6 +131,8 @@ if __name__ == "__main__": # pragma: no cover
130
131
  BECMotorMap,
131
132
  BECDock,
132
133
  BECDockArea,
134
+ SpiralProgressBar,
135
+ Ring,
133
136
  ]
134
137
  generator = ClientGenerator()
135
138
  generator.generate_client(clss)
@@ -1,11 +1,12 @@
1
1
  from bec_widgets.utils import BECConnector
2
2
  from bec_widgets.widgets.figure import BECFigure
3
+ from bec_widgets.widgets.spiral_progress_bar.spiral_progress_bar import SpiralProgressBar
3
4
 
4
5
 
5
6
  class RPCWidgetHandler:
6
7
  """Handler class for creating widgets from RPC messages."""
7
8
 
8
- widget_classes = {"BECFigure": BECFigure}
9
+ widget_classes = {"BECFigure": BECFigure, "SpiralProgressBar": SpiralProgressBar}
9
10
 
10
11
  @staticmethod
11
12
  def create_widget(widget_type, **kwargs) -> BECConnector:
@@ -13,6 +13,7 @@ from bec_widgets.cli.rpc_register import RPCRegister
13
13
  from bec_widgets.utils import BECDispatcher
14
14
  from bec_widgets.widgets import BECFigure
15
15
  from bec_widgets.widgets.dock.dock_area import BECDockArea
16
+ from bec_widgets.widgets.spiral_progress_bar.spiral_progress_bar import SpiralProgressBar
16
17
 
17
18
 
18
19
  class JupyterConsoleWidget(RichJupyterWidget): # pragma: no cover:
@@ -62,6 +63,7 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
62
63
  "d1": self.d1,
63
64
  "d2": self.d2,
64
65
  "d3": self.d3,
66
+ "bar": self.bar,
65
67
  "b2a": self.button_2_a,
66
68
  "b2b": self.button_2_b,
67
69
  "b2c": self.button_2_c,
@@ -114,14 +116,14 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
114
116
  self.button_2_b = QtWidgets.QPushButton("button after without postions specified")
115
117
  self.button_2_c = QtWidgets.QPushButton("button super late")
116
118
  self.button_3 = QtWidgets.QPushButton("Button above Figure ")
117
- self.label_1 = QtWidgets.QLabel("some scan info label with useful information")
119
+ self.bar = SpiralProgressBar()
118
120
 
119
121
  self.label_2 = QtWidgets.QLabel("label which is added separately")
120
122
  self.label_3 = QtWidgets.QLabel("Label above figure")
121
123
 
122
124
  self.d1 = self.dock.add_dock(widget=self.button_1, position="left")
123
125
  self.d1.addWidget(self.label_2)
124
- self.d2 = self.dock.add_dock(widget=self.label_1, position="right")
126
+ self.d2 = self.dock.add_dock(widget=self.bar, position="right")
125
127
  self.d3 = self.dock.add_dock(name="figure")
126
128
  self.fig_dock3 = BECFigure()
127
129
  self.fig_dock3.plot(x_name="samx", y_name="bpm4d")
@@ -1,3 +1,4 @@
1
1
  from .dock import BECDock, BECDockArea
2
2
  from .figure import BECFigure, FigureConfig
3
3
  from .scan_control import ScanControl
4
+ from .spiral_progress_bar import SpiralProgressBar
@@ -0,0 +1 @@
1
+ from .spiral_progress_bar import SpiralProgressBar
@@ -0,0 +1,184 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, Optional
4
+
5
+ from bec_lib.endpoints import EndpointInfo
6
+ from pydantic import BaseModel, Field, field_validator
7
+ from pydantic_core import PydanticCustomError
8
+ from qtpy import QtGui
9
+
10
+ from bec_widgets.utils import BECConnector, ConnectionConfig
11
+
12
+
13
+ class RingConnections(BaseModel):
14
+ slot: Literal["on_scan_progress", "on_device_readback"] = None
15
+ endpoint: EndpointInfo | str = None
16
+
17
+ @field_validator("endpoint")
18
+ def validate_endpoint(cls, v, values):
19
+ slot = values.data["slot"]
20
+ endpoint = v.endpoint if isinstance(v, EndpointInfo) else v
21
+ if slot == "on_scan_progress":
22
+ if endpoint != "scans/scan_progress":
23
+ raise PydanticCustomError(
24
+ "unsupported endpoint",
25
+ "For slot 'on_scan_progress', endpoint must be MessageEndpoint.scan_progress or 'scans/scan_progress'.",
26
+ {"wrong_value": v},
27
+ )
28
+ elif slot == "on_device_readback":
29
+ if not endpoint.startswith("internal/devices/readback/"):
30
+ raise PydanticCustomError(
31
+ "unsupported endpoint",
32
+ "For slot 'on_device_readback', endpoint must be MessageEndpoint.device_readback(device) or 'internal/devices/readback/{device}'.",
33
+ {"wrong_value": v},
34
+ )
35
+ return v
36
+
37
+
38
+ class RingConfig(ConnectionConfig):
39
+ direction: int | None = Field(
40
+ -1, description="Direction of the progress bars. -1 for clockwise, 1 for counter-clockwise."
41
+ )
42
+ color: str | tuple | None = Field(
43
+ (0, 159, 227, 255),
44
+ description="Color for the progress bars. Can be tuple (R, G, B, A) or string HEX Code.",
45
+ )
46
+ background_color: str | tuple | None = Field(
47
+ (200, 200, 200, 50),
48
+ description="Background color for the progress bars. Can be tuple (R, G, B, A) or string HEX Code.",
49
+ )
50
+ index: int | None = Field(0, description="Index of the progress bar. 0 is outer ring.")
51
+ line_width: int | None = Field(5, description="Line widths for the progress bars.")
52
+ start_position: int | None = Field(
53
+ 90,
54
+ description="Start position for the progress bars in degrees. Default is 90 degrees - corespons to "
55
+ "the top of the ring.",
56
+ )
57
+ min_value: int | None = Field(0, description="Minimum value for the progress bars.")
58
+ max_value: int | None = Field(100, description="Maximum value for the progress bars.")
59
+ precision: int | None = Field(3, description="Precision for the progress bars.")
60
+ update_behaviour: Literal["manual", "auto"] | None = Field(
61
+ "auto", description="Update behaviour for the progress bars."
62
+ )
63
+ connections: RingConnections | None = Field(
64
+ default_factory=RingConnections, description="Connections for the progress bars."
65
+ )
66
+
67
+
68
+ class Ring(BECConnector):
69
+ USER_ACCESS = [
70
+ "get_all_rpc",
71
+ "rpc_id",
72
+ "config_dict",
73
+ "set_value",
74
+ "set_color",
75
+ "set_background",
76
+ "set_line_width",
77
+ "set_min_max_values",
78
+ "set_start_angle",
79
+ "set_connections",
80
+ "reset_connection",
81
+ ]
82
+
83
+ def __init__(
84
+ self,
85
+ parent=None,
86
+ parent_progress_widget=None,
87
+ config: RingConfig | dict | None = None,
88
+ client=None,
89
+ gui_id: Optional[str] = None,
90
+ ):
91
+ if config is None:
92
+ config = RingConfig(widget_class=self.__class__.__name__)
93
+ self.config = config
94
+ else:
95
+ if isinstance(config, dict):
96
+ config = RingConfig(**config)
97
+ self.config = config
98
+ super().__init__(client=client, config=config, gui_id=gui_id)
99
+
100
+ self.parent_progress_widget = parent_progress_widget
101
+ self.color = None
102
+ self.background_color = None
103
+ self.start_position = None
104
+ self.config = config
105
+ self.value = 0
106
+ self.RID = None
107
+ self._init_config_params()
108
+
109
+ def _init_config_params(self):
110
+ self.color = self.convert_color(self.config.color)
111
+ self.background_color = self.convert_color(self.config.background_color)
112
+ self.set_start_angle(self.config.start_position)
113
+ if self.config.connections:
114
+ self.set_connections(self.config.connections.slot, self.config.connections.endpoint)
115
+
116
+ def set_value(self, value: int | float):
117
+ self.value = round(
118
+ max(self.config.min_value, min(self.config.max_value, value)), self.config.precision
119
+ )
120
+
121
+ def set_color(self, color: str | tuple):
122
+ self.config.color = color
123
+ self.color = self.convert_color(color)
124
+
125
+ def set_background(self, color: str | tuple):
126
+ self.config.background_color = color
127
+ self.color = self.convert_color(color)
128
+
129
+ def set_line_width(self, width: int):
130
+ self.config.line_width = width
131
+
132
+ def set_min_max_values(self, min_value: int, max_value: int):
133
+ self.config.min_value = min_value
134
+ self.config.max_value = max_value
135
+
136
+ def set_start_angle(self, start_angle: int):
137
+ self.config.start_position = start_angle
138
+ self.start_position = start_angle * 16
139
+
140
+ @staticmethod
141
+ def convert_color(color):
142
+ converted_color = None
143
+ if isinstance(color, str):
144
+ converted_color = QtGui.QColor(color)
145
+ elif isinstance(color, tuple):
146
+ converted_color = QtGui.QColor(*color)
147
+ return converted_color
148
+
149
+ def set_connections(self, slot: str, endpoint: str | EndpointInfo):
150
+ if self.config.connections.endpoint == endpoint and self.config.connections.slot == slot:
151
+ return
152
+ else:
153
+ self.bec_dispatcher.disconnect_slot(
154
+ self.config.connections.slot, self.config.connections.endpoint
155
+ )
156
+ self.config.connections = RingConnections(slot=slot, endpoint=endpoint)
157
+ self.bec_dispatcher.connect_slot(getattr(self, slot), endpoint)
158
+
159
+ def reset_connection(self):
160
+ self.bec_dispatcher.disconnect_slot(
161
+ self.config.connections.slot, self.config.connections.endpoint
162
+ )
163
+ self.config.connections = RingConnections()
164
+
165
+ def on_scan_progress(self, msg, meta):
166
+ current_RID = meta.get("RID", None)
167
+ if current_RID != self.RID:
168
+ self.set_min_max_values(0, msg.get("max_value", 100))
169
+ self.set_value(msg.get("value", 0))
170
+ self.parent_progress_widget.update()
171
+
172
+ def on_device_readback(self, msg, meta):
173
+ if isinstance(self.config.connections.endpoint, EndpointInfo):
174
+ endpoint = self.config.connections.endpoint.endpoint
175
+ else:
176
+ endpoint = self.config.connections.endpoint
177
+ device = endpoint.split("/")[-1]
178
+ value = msg.get("signals").get(device).get("value")
179
+ self.set_value(value)
180
+ self.parent_progress_widget.update()
181
+
182
+ def cleanup(self):
183
+ self.reset_connection()
184
+ super().cleanup()