mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-10-28 16:11:27 +01:00
275 lines
13 KiB
Markdown
275 lines
13 KiB
Markdown

|
||
|
||
# BEC Widgets
|
||
|
||
[](https://github.com/bec-project/bec_widgets/actions/workflows/ci.yml)
|
||
[](https://pypi.org/project/bec-widgets/)
|
||
[](./LICENSE)
|
||
[](https://github.com/psf/black)
|
||
[](https://www.python.org)
|
||
[](https://doc.qt.io/qtforpython/)
|
||
[](https://conventionalcommits.org)
|
||
[](https://codecov.io/gh/bec-project/bec_widgets)
|
||
|
||
A modular PySide6(Qt6) toolkit for [BEC (Beamline Experiment Control)](https://github.com/bec-project/bec). Create
|
||
high-performance, dockable GUIs to move devices, run scans, and stream live or disk data—powered by Redis and a modular
|
||
plugin system.
|
||
|
||
## Highlights
|
||
|
||
- **No-code first** — For ~90% of day-to-day workflows, you can compose, operate, and save workspaces **without writing
|
||
a single line of code**. Just launch, drag widgets, and do your experiment.
|
||
- **Flexible layout composition** — Build complex experiment GUIs in seconds with the `BECDockArea`: drag‑dock, tab,
|
||
split, and export profiles/workspaces for reuse.
|
||
- **CLI / scripting** — Control your beamline experiment from the command line a robust RPC layer using
|
||
`BECIPythonClient`.
|
||
- **Designer integration** — Use Qt Designer plugins to drop BEC widgets next to any Qt control, then launch the `.ui`
|
||
with the custom BEC loader for a zero‑glue workflow.
|
||
- **Operational integration** — Widgets stay in sync with your running BEC/Redis as the single source of truth:
|
||
Subscribe to events from BEC and create dynamically updating UIs. BECWidgets also grants you easy access the
|
||
acquisition history.
|
||
- **Extensible by design** — Build new widgets with minimal boilerplate using `BECWidget` and `BECDispatcher` for BEC data and
|
||
messaging. Use the generator command to scaffold RPC interfaces and Designer plugin stubs; beamline plugins can extend
|
||
or override behavior as needed.
|
||
|
||
## Table of Contents
|
||
|
||
- [Installation](#installation)
|
||
- [Features](#features)
|
||
- [1. Dock area interface: build GUIs in seconds](#1-dock-area-interface-build-guis-in-seconds)
|
||
- [2. Qt Designer plugins + BEC Launcher (no glue)](#2-qt-designer-plugins--bec-launcher-no-glue)
|
||
- [3. Robust RPC from CLI & remote scripting](#3-robust-rpc-from-cli--remote-scripting)
|
||
- [4. Rapid development (extensible by design)](#4-rapid-development-extensible-by-design)
|
||
- [Widget Library](#widget-library)
|
||
- [Documentation](#documentation)
|
||
- [License](#license)
|
||
|
||
## Installation
|
||
|
||
Use any of the following setups:
|
||
|
||
### Stable release
|
||
|
||
Use the package manager [pip](https://pip.pypa.io/en/stable/) to install BEC Widgets:
|
||
|
||
```bash
|
||
pip install bec_widgets
|
||
```
|
||
|
||
### From source (recommended for development)
|
||
|
||
For development purposes, you can clone the repository and install the package locally in editable mode:
|
||
|
||
```bash
|
||
git clone https://github.com/bec-project/bec_widgets.git
|
||
cd bec_widgets
|
||
pip install -e .[dev]
|
||
```
|
||
|
||
## Features
|
||
|
||
### 1. Dock area interface: build GUIs in seconds
|
||
|
||
The fastest way to explore BEC Widgets. Launch the BEC IPython client with simply `bec` in terminal and the **BECDockArea** opens as the default UI:
|
||
drag widgets, dock/tab/split panes, and explore. Everything is live—widgets auto-connect to BEC/Redis, so you can
|
||
operate immediately and refine later with RPC or Designer if needed.
|
||
|
||

|
||
|
||
### 2. Qt Designer plugins + BEC Launcher (no glue)
|
||
|
||
All BEC Widgets ship as **Qt Designer plugins** with our custom Qt Designer launchable by `bec-designer`. Design your UI
|
||
visually in Designer, save a `.ui`, then launch it with
|
||
the **BEC Launcher**—no glue code. Widgets auto‑connect to BEC/Redis on startup, so your UI is operational immediately.
|
||
|
||

|
||
|
||
### 3. Robust RPC from CLI & remote scripting
|
||
|
||
Operate and automate BEC Widgets directly from the `BECIPythonClient`. Create or attach to GUIs, address any sub-widget
|
||
via a simple hierarchical API with tab-completion, and script event-driven behavior that reacts to BEC (scan lifecycle,
|
||
active devices, topics)—so your UI can be heavily automated.
|
||
|
||
- Create & control GUIs: launch, load profiles, open/close panels, tweak properties—all from the shell.
|
||
- Hierarchical addressing: navigate widgets and sub-widgets with discoverable paths and tab-completion.
|
||
- Event scripting: subscribe to BEC events (e.g., scan start/finish, device readiness, topic updates) and trigger
|
||
actions,switch profiles, open diagnostic views, or start specific scans.
|
||
- Remote & headless: run automation on analysis nodes or from notebooks without a local GUI process.
|
||
- Plays with no-code: Use the Dock Area / BEC Designer to set up the layout and add automation with RPC when needed.
|
||
|
||

|
||
|
||
#### Window visibility and display diagnostics
|
||
|
||
`gui.show()` shows hidden windows, restores minimized windows, and requests foreground activation
|
||
when a window is behind another application. `gui.raise_window()` uses the same behaviour;
|
||
`gui.flomni.raise_window()` targets one window. Already-visible, active windows are left unchanged.
|
||
|
||
On XCB (X11/XWayland), BEC retains its GNOME/RHEL window-raising workaround and restores the original
|
||
window flags afterwards. On native Wayland, the experimental workaround releases an inactive
|
||
window's existing native resources before showing it. This avoids reusing the Wayland surface
|
||
involved in alternating visibility with Qt 6.11.1 on the affected GNOME 40 console. It also applies to
|
||
previously hidden windows. A window that has never been shown is shown normally.
|
||
|
||
The QWidget instances and their BEC connections stay alive; constructors and BEC cleanup are not
|
||
called. Native QWindow handles and backing stores are recreated, so this has a rendering cost and
|
||
cached native handles become invalid. Embedded renderers may have additional resource costs.
|
||
Flags and the non-minimized window state are preserved, but the compositor may reposition the
|
||
window and a visible window may flicker. Foreground activation remains compositor-dependent.
|
||
macOS uses Qt's normal activation request.
|
||
|
||
Wayland's supported focus transfer uses an XDG activation token from the focused application.
|
||
The terminal's input event/token is not carried by BEC's Redis RPC, so GNOME may reject activation.
|
||
See [GNOME's explanation of focus stealing prevention](https://blogs.gnome.org/shell-dev/2024/09/20/understanding-gnome-shells-focus-stealing-prevention/).
|
||
|
||
To inspect the **running GUI server process**, from the BEC IPython client:
|
||
|
||
```python
|
||
from pprint import pprint
|
||
|
||
pprint(gui.get_display_info())
|
||
```
|
||
|
||
The report includes the loaded Qt platform (`wayland`, `xcb` for X11/XWayland, or `cocoa` on macOS),
|
||
Qt version and plugin search paths, Python executable, selected inherited environment variables,
|
||
screens, and window visibility/focus state. `XDG_CURRENT_DESKTOP` and `DESKTOP_SESSION` are desktop
|
||
hints, not authoritative compositor identification. The session can be Wayland while Qt uses `xcb`.
|
||
Qt settings or plugin paths inherited from a shell or another application's launcher appear in the
|
||
environment section. Diagnostics do not start the GUI or change its settings.
|
||
|
||
After updating this code, restart both the BEC client and its GUI server before testing. On the
|
||
affected console, compare the report before and after repeated `gui.show()` and `gui.raise_window()`
|
||
calls. `visible=True` with `active=False` means Qt considers the window shown but without keyboard
|
||
focus; it does not prove the window is unobscured or on the current workspace.
|
||
|
||
For an XWayland comparison on a Linux desktop with XWayland available, start a fresh session with
|
||
`QT_QPA_PLATFORM=xcb bec --session flomni` and verify that `qt_platform` is `xcb`. This sets the backend
|
||
for that invocation only. For plugin-loading details, start with `QT_DEBUG_PLUGINS=1 bec --session flomni`.
|
||
|
||
To isolate a visibility problem from BEC, run the plain Qt probe directly from this checkout,
|
||
using the same Python environment as the affected GUI:
|
||
|
||
```bash
|
||
QT_QPA_PLATFORM=wayland python bec_widgets/examples/qt_window_probe.py
|
||
```
|
||
|
||
Type `cycle` repeatedly in its terminal to test plain Qt hide/show. Compare with separate `hide`
|
||
and `show` commands; `raise` tests activation without hiding. The probe reports Qt's state but
|
||
cannot determine whether another application's window obscures it. Record what you see alongside
|
||
the output and the result of `gnome-shell --version` when reporting a GNOME issue. Run again with
|
||
`QT_QPA_PLATFORM=xcb` to compare backends. This script starts no BEC services and imports no BEC code.
|
||
|
||
If `cycle` alternates between appearing and disappearing, try `recreate` at least four times,
|
||
one command at a time. It runs hide/destroy/show on the same QWidget: unlike `cycle`, it releases
|
||
the native window resources before showing the widget again. This isolates native surface reuse
|
||
without starting BEC. A successful plain-widget probe still needs verification with BEC's docked
|
||
widgets and embedded renderers on the affected compositor.
|
||
|
||
To capture the Wayland protocol alongside the probe output, write to a unique temporary file if
|
||
the checkout is not writable:
|
||
|
||
```bash
|
||
probe_log=$(mktemp /tmp/qt-window-wayland.XXXXXX)
|
||
WAYLAND_DEBUG=1 QT_QPA_PLATFORM=wayland python -u bec_widgets/examples/qt_window_probe.py 2>&1 | tee "$probe_log"
|
||
echo "$probe_log"
|
||
```
|
||
|
||
### 4. Rapid development (extensible by design)
|
||
|
||
Build new widgets fast: Inherit from `BECWidget`, list your RPC methods in `USER_ACCESS`, and use `bec_dispatcher` to
|
||
bind endpoints. Then run `bw-generate-cli --target <your-plugin-repo>`. This generates the RPC CLI bindings and a Qt
|
||
Designer plugin that are immediately usable with your BEC setup. Widgets
|
||
come online with live BEC/Redis wiring out of the box. 
|
||
|
||
<details>
|
||
<summary> View code: Example Widget </summary>
|
||
|
||
```python
|
||
from typing import Literal
|
||
|
||
from qtpy.QtWidgets import QWidget, QLabel, QPushButton, QHBoxLayout, QVBoxLayout, QApplication
|
||
from qtpy.QtCore import Slot
|
||
|
||
from bec_lib.endpoints import MessageEndpoints
|
||
from bec_widgets import BECWidget, SafeSlot
|
||
|
||
|
||
class SimpleMotorWidget(BECWidget, QWidget):
|
||
USER_ACCESS = ["move"]
|
||
|
||
def __init__(self, parent=None, motor_name="samx", step=5.0, **kwargs):
|
||
super().__init__(parent=parent, **kwargs)
|
||
self.motor_name = motor_name
|
||
self.step = float(step)
|
||
|
||
self.get_bec_shortcuts()
|
||
|
||
self.value_label = QLabel(f"{self.motor_name}: —")
|
||
self.btn_left = QPushButton("◀︎ -5")
|
||
self.btn_right = QPushButton("+5 ▶︎")
|
||
|
||
row = QHBoxLayout()
|
||
row.addWidget(self.btn_left)
|
||
row.addWidget(self.btn_right)
|
||
|
||
col = QVBoxLayout(self)
|
||
col.addWidget(self.value_label)
|
||
col.addLayout(row)
|
||
|
||
self.btn_left.clicked.connect(lambda: self.move("left", self.step))
|
||
self.btn_right.clicked.connect(lambda: self.move("right", self.step))
|
||
|
||
self.bec_dispatcher.connect_slot(self.on_readback, MessageEndpoints.device_readback(self.motor_name))
|
||
|
||
@SafeSlot(dict, dict)
|
||
def on_readback(self, data: dict, meta: dict):
|
||
current_value = data.get("signals").get(self.motor_name).get('value')
|
||
self.value_label.setText(f"{self.motor_name}: {current_value:.3f}")
|
||
|
||
@Slot(str, float)
|
||
def move(self, direction: Literal["left", "right"] = "left", step: float = 5.0):
|
||
if direction == "left":
|
||
self.dev[self.motor_name].move(-step, relative=True)
|
||
else:
|
||
self.dev[self.motor_name].move(step, relative=True)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import sys
|
||
|
||
app = QApplication(sys.argv)
|
||
w = SimpleMotorWidget(motor_name="samx", step=5.0)
|
||
w.setWindowTitle("MotorJogWidget")
|
||
w.resize(280, 90)
|
||
w.show()
|
||
sys.exit(app.exec_())
|
||
```
|
||
|
||
</details>
|
||
|
||
## Widget Library
|
||
|
||
A large and growing catalog—plug, configure, run:
|
||
|
||
### Plotting
|
||
|
||
Waveform, MultiWaveform, and Image/Heatmap widgets deliver responsive plots with crosshairs and ROIs for live and
|
||
history data.
|
||
|
||
<img width="1108" height="838" alt="plotting_hr" src="https://github.com/user-attachments/assets/f50462a5-178d-44d4-aee5-d378c74b107b" />
|
||
|
||
### Scan orchestration and motion control.
|
||
|
||
Start and stop scans, track progress, reuse parameter presets, and browse history from a focused control surface.
|
||
Positioner boxes and tweak controls handle precise moves, homing, and calibration for day‑to‑day alignment.
|
||
|
||
<img width="1496" height="1388" alt="control" src="https://github.com/user-attachments/assets/d4fb2e2e-04f9-4621-8087-790680797620" />
|
||
|
||
## Documentation
|
||
|
||
The documentation can be found [here](https://bec.readthedocs.io/).
|
||
|
||
## License
|
||
|
||
[BSD-3-Clause](https://choosealicense.com/licenses/bsd-3-clause/)
|