wip
CI for debye_bec / test (push) Successful in 54s
CI for debye_bec / test (pull_request) Successful in 55s

This commit is contained in:
x01da
2026-09-01 14:28:25 +02:00
parent 1150d1395b
commit 7c5bd4ce67
3 changed files with 116 additions and 62 deletions
@@ -26,6 +26,11 @@ can reopen the dialog pre-filled instead of asking the user to start over.
from __future__ import annotations
from bec_lib.logger import bec_logger
from bec_widgets.widgets.control.device_input.device_combobox.device_combobox import (
BECDeviceFilter,
DeviceComboBox,
)
from bec_widgets.widgets.control.scan_control.scan_control import ScanControl, ScanParameterConfig
from qtpy.QtWidgets import (
QCheckBox,
@@ -42,7 +47,9 @@ from qtpy.QtWidgets import (
QWidget,
)
from .scan_form import list_movable_device_names
from ..scan_control_xas.scan_control_xas import ScanControlXAS
logger = bec_logger.logger
_DSPIN_RANGE = (-1e12, 1e12)
@@ -68,6 +75,8 @@ class ScheduleItemDialog(QDialog):
self._build_custom_tab()
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttons.button(QDialogButtonBox.Ok).setText("Add")
buttons.setStyleSheet("QPushButton {qproperty-icon: none;}")
buttons.accepted.connect(self._on_accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
@@ -84,17 +93,17 @@ class ScheduleItemDialog(QDialog):
# client=None resolves to the same process-wide BEC client
# (bec_dispatcher.client) our own widget uses - no second Redis
# connection is opened.
self.scan_control = ScanControl(parent=tab, client=self._client)
self.scan_control = ScanControlXAS(parent=tab, client=self._client)
self.scan_control.button_run_scan.hide()
layout.addWidget(self.scan_control)
hint = QLabel(
"Configure the scan above, then confirm with OK below - it will be "
"added to the schedule, not started immediately."
)
hint.setWordWrap(True)
hint.setStyleSheet("color: gray;")
layout.addWidget(hint)
# hint = QLabel(
# "Configure the scan above, then confirm with OK below - it will be "
# "added to the schedule, not started immediately."
# )
# hint.setWordWrap(True)
# hint.setStyleSheet("color: gray;")
# layout.addWidget(hint)
self.tabs.addTab(tab, "Scan")
@@ -127,6 +136,7 @@ class ScheduleItemDialog(QDialog):
name=scan_name, args=args, kwargs=kwargs
)
self.scan_control.current_scan = scan_name
self.scan_control.restore_scan_parameters(scan_name)
# ------------------------------------------------------------------ #
# Move tab
@@ -134,18 +144,23 @@ class ScheduleItemDialog(QDialog):
def _build_move_tab(self):
tab = QWidget()
form = QFormLayout(tab)
form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.FieldsStayAtSizeHint)
# self.move_device_combo = QComboBox()
# self.move_device_combo.addItems(list_movable_device_names(self._dev))
# self.move_device_combo.setMaxVisibleItems(12)
self.move_device_combo = DeviceComboBox(self, device_filter=[BECDeviceFilter.POSITIONER])
self.move_device_combo = QComboBox()
self.move_device_combo.addItems(list_movable_device_names(self._dev))
form.addRow("Device", self.move_device_combo)
self.move_value_spin = QDoubleSpinBox()
self.move_value_spin.setDecimals(6)
self.move_value_spin.setRange(*_DSPIN_RANGE)
form.addRow("Target value", self.move_value_spin)
form.addRow("Value", self.move_value_spin)
self.move_relative_check = QCheckBox("Relative move")
form.addRow("", self.move_relative_check)
self.move_relative_check = QCheckBox("")
form.addRow("Relative move", self.move_relative_check)
self.tabs.addTab(tab, "Move")
@@ -209,6 +224,8 @@ class ScheduleItemDialog(QDialog):
# showing the raw command text as-is.
self.custom_edit.setText(state.get("text", initial["command"]))
self.tabs.setCurrentIndex(2)
else:
logger.warning(f"Unknown kind: {kind}")
def _on_accept(self):
try:
@@ -1,16 +0,0 @@
"""
Small device-listing helper for the "Move" tab of `item_dialog.py`.
Scan parameter introspection/forms used to live in this module too, but
that's now handled by embedding BEC's own `bec_widgets` `ScanControl`
widget directly (see `item_dialog.py`) instead of a plugin-owned
reimplementation - one less UI to keep in sync with BEC's scan
capabilities.
"""
from __future__ import annotations
def list_movable_device_names(dev) -> list[str]:
"""Device names that expose `.move(...)` (i.e. positioner-like devices)."""
return sorted(name for name, device in dev.items() if hasattr(device, "move"))
@@ -18,6 +18,7 @@ from typing import Literal, Optional
from bec_lib.endpoints import MessageEndpoints
from bec_lib.logger import bec_logger
from bec_lib.messages import VariableMessage
from bec_qthemes import material_icon
from bec_qthemes._theme import AccentColors
from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget
@@ -71,6 +72,15 @@ _STATUS_ICON = {
ScheduleItemStatus.ABORTED: "\u23f9", # stop
}
ICON_SIZE = 20
_ICON_MAP = {
ScheduleItemStatus.PENDING: ("hourglass", "#919090"),
ScheduleItemStatus.RUNNING: ("cycle", "#2980b9"),
ScheduleItemStatus.COMPLETED: ("check", "#27ae60"),
ScheduleItemStatus.FAILED: ("warning", "#e74c3c"),
ScheduleItemStatus.ABORTED: ("cancel", "#e74c3c"),
}
class ScheduleWidgetConfig(ConnectionConfig):
"""
@@ -239,16 +249,15 @@ class Scheduler(BECWidget, QWidget):
edit_row.addWidget(button)
edit_row.addStretch()
schedule_layout.addLayout(edit_row)
# layout.addLayout(edit_row)
# layout.addWidget(QLabel(f"Schedule: {self.schedule_name}"))
schedule_layout.addWidget(QLabel(f"Schedule: {self.schedule_name}"))
self.list_widget = MyListWidget()
self.list_widget.currentRowChanged.connect(self._on_selection_changed)
self.list_widget.deletePressed.connect(self._on_delete_clicked)
self.list_widget.emptySpaceClicked.connect(self._on_empty_space_clicked)
# layout.ada(scheduldWidget(self.list_widget)
self.list_widget.itemDoubleClicked.connect(self._on_edit_clicked)
schedule_layout.addWidget(self.list_widget)
self.add_btn.clicked.connect(self._on_add_clicked)
@@ -270,7 +279,6 @@ class Scheduler(BECWidget, QWidget):
run_row.addWidget(button)
run_row.addStretch()
control_layout.addLayout(run_row)
# layout.addLayout(run_row)
self.run_btn.clicked.connect(self.run_schedule)
self.abort_btn.clicked.connect(self.abort_schedule)
@@ -288,7 +296,6 @@ class Scheduler(BECWidget, QWidget):
settings_row.addWidget(self.guard_btn)
settings_row.addStretch()
settings_layout.addLayout(settings_row)
# layout.addLayout(settings_row)
self.notifications_btn.clicked.connect(self._on_notifications_clicked)
self.guard_btn.clicked.connect(self._on_guard_clicked)
@@ -437,11 +444,26 @@ class Scheduler(BECWidget, QWidget):
self.list_widget.clear()
selected_row = None
for row, item in enumerate(items):
text = f"{_STATUS_ICON.get(item.status, '?')} {item.command}"
# text = f"{_STATUS_ICON.get(item.status, '?')} {item.command}"
text = f"{item.command}"
icon_name, color = _ICON_MAP[item.status]
icon = material_icon(
icon_name, size=(ICON_SIZE, ICON_SIZE), color=color, convert_to_pixmap=True
)
# if item.status == ScheduleItemStatus.RUNNING:
# self._spin_anim.start()
# else:
# self._spin_anim.stop()
# self._label.setPixmap(icon)
if item.error:
text += f" ({item.error.strip().splitlines()[-1]})"
list_item = QListWidgetItem(text, self.list_widget)
list_item.setData(_ITEM_ID_ROLE, item.item_id)
list_item.setIcon(icon)
if item.item_id == self._selected_item_id:
selected_row = row
if selected_row is not None:
@@ -450,32 +472,32 @@ class Scheduler(BECWidget, QWidget):
self._selected_item_id = None
self.list_widget.blockSignals(False)
idx = None
selected_item = None
if self._selected_item_id is not None:
for i, item in enumerate(items):
if item.item_id == self._selected_item_id:
idx, selected_item = i, item
break
# idx = None
# selected_item = None
# if self._selected_item_id is not None:
# for i, item in enumerate(items):
# if item.item_id == self._selected_item_id:
# idx, selected_item = i, item
# break
# Editable = the selected item hasn't started yet. Thanks to the
# protected-prefix invariant, everything after the first PENDING
# item is guaranteed PENDING too, so a plain index/status check is
# enough here - see module docstring.
can_edit_selected = (
selected_item is not None and selected_item.status == ScheduleItemStatus.PENDING
)
# # Editable = the selected item hasn't started yet. Thanks to the
# # protected-prefix invariant, everything after the first PENDING
# # item is guaranteed PENDING too, so a plain index/status check is
# # enough here - see module docstring.
# can_edit_selected = (
# selected_item is not None and selected_item.status == ScheduleItemStatus.PENDING
# )
self.run_btn.setEnabled(not is_running)
# Adding is always safe: new items are clamped into the PENDING
# suffix regardless of what's selected (see add_item()).
self.add_btn.setEnabled(True)
self.edit_btn.setEnabled(can_edit_selected)
self.delete_btn.setEnabled(can_edit_selected)
self.move_up_btn.setEnabled(can_edit_selected and idx not in (None, 0))
self.move_down_btn.setEnabled(
can_edit_selected and idx is not None and idx < len(items) - 1
)
# self.run_btn.setEnabled(not is_running)
# # Adding is always safe: new items are clamped into the PENDING
# # suffix regardless of what's selected (see add_item()).
# self.add_btn.setEnabled(True)
# self.edit_btn.setEnabled(can_edit_selected)
# self.delete_btn.setEnabled(can_edit_selected)
# self.move_up_btn.setEnabled(can_edit_selected and idx not in (None, 0))
# self.move_down_btn.setEnabled(
# can_edit_selected and idx is not None and idx < len(items) - 1
# )
if self._guard.enabled:
state = "OK" if self._guard.is_clear() else "PAUSED - waiting to resume"
@@ -489,11 +511,39 @@ class Scheduler(BECWidget, QWidget):
else:
self.guard_status_label.hide()
def _update_buttons(self):
with self._lock:
items = list(self.schedule.items)
is_running = self.schedule.is_running
idx = None
selected_item = None
if self._selected_item_id is not None:
for i, item in enumerate(items):
if item.item_id == self._selected_item_id:
idx, selected_item = i, item
break
can_edit_selected = (
selected_item is not None and selected_item.status == ScheduleItemStatus.PENDING
)
self.run_btn.setEnabled(not is_running)
self.add_btn.setEnabled(True)
self.edit_btn.setEnabled(can_edit_selected)
self.delete_btn.setEnabled(can_edit_selected)
self.move_up_btn.setEnabled(can_edit_selected and idx not in (None, 0))
self.move_down_btn.setEnabled(
can_edit_selected and idx is not None and idx < len(items) - 1
)
@SafeSlot()
def _on_selection_changed(self, row: int):
logger.info("On selection changed")
item = self.list_widget.item(row) if row >= 0 else None
self._selected_item_id = item.data(_ITEM_ID_ROLE) if item is not None else None
self._refresh_ui()
self._update_buttons()
@SafeSlot()
def _on_empty_space_clicked(self):
@@ -567,9 +617,10 @@ class Scheduler(BECWidget, QWidget):
)
self._selected_item_id = new_id
self._refresh_ui()
self._update_buttons()
@SafeSlot()
def _on_edit_clicked(self):
def _on_edit_clicked(self, row=None):
if self._selected_item_id is None:
return
with self._lock:
@@ -583,6 +634,8 @@ class Scheduler(BECWidget, QWidget):
if initial is None:
return
logger.info(f"Initial: {initial}")
dialog = ScheduleItemDialog(
self.scans, self.dev, parent=self, initial=initial, client=self.client
)