From 6bce6f890785e48c966362d2eff389b8e72c4be5 Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Tue, 9 Jun 2026 15:41:10 +0200 Subject: [PATCH 1/3] fix(digital-twin): use exact string comparisons Replace substring membership checks with equality for modes, stripes, scene names, and plot identifiers so partial strings cannot select the wrong branch. --- .../digital_twin/calculations/calc_positions.py | 6 +++--- .../digital_twin/calculations/calc_sideview.py | 2 +- .../digital_twin/calculations/calc_surfaces.py | 2 +- .../digital_twin/calculations/calc_varia.py | 14 +++++++------- .../widgets/digital_twin/digital_twin.py | 16 ++++++++-------- .../widgets/digital_twin/panels/plots.py | 16 ++++++++-------- .../widgets/digital_twin/widgets/move_widget.py | 2 +- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_positions.py b/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_positions.py index 79cb809..885dcb3 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_positions.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_positions.py @@ -183,7 +183,7 @@ def calc_positions(cfg: ConfigDict) -> dict[str, dict[str, float]]: if cfg["fm_stripe"] in ("Rh (toroid)", "Pt (toroid)"): # TRY - if cfg["fm_stripe"] in "Rh (toroid)": + if cfg["fm_stripe"] == "Rh (toroid)": r = bl.fm.r[0] h_cyl = bl.fm.hToroid[0] else: # PT toroid @@ -199,7 +199,7 @@ def calc_positions(cfg: ConfigDict) -> dict[str, dict[str, float]]: pos["fm_try"] = {"value": fm_height} # TRX - if cfg["fm_stripe"] in "Rh (toroid)": + if cfg["fm_stripe"] == "Rh (toroid)": x_cyl = -bl.fm.xToroid[0] else: x_cyl = -bl.fm.xToroid[1] @@ -213,7 +213,7 @@ def calc_positions(cfg: ConfigDict) -> dict[str, dict[str, float]]: pos["fm_try"] = {"value": fm_height} # TRX - if cfg["fm_stripe"] in "Rh (flat)": + if cfg["fm_stripe"] == "Rh (flat)": x_flat = -bl.fm.xFlat[0] else: x_flat = -bl.fm.xFlat[1] diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_sideview.py b/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_sideview.py index afa0b29..7ec677d 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_sideview.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_sideview.py @@ -27,7 +27,7 @@ def calc_sideview(cfg: ConfigDict) -> DataDict: beam["y"].append(bl.sourceHeight) beam["x"].append(bl.cm.center[1]) # CM beam["y"].append(bl.sourceHeight) - if cfg["mo1_mode"] in "Monochromatic": + if cfg["mo1_mode"] == "Monochromatic": diag = bl.mo1.xtalGap[0] / np.sin(cfg["mo1_bragg"]) # Calculations for Mono dy = diag * np.sin(2 * (cfg["cm_pitch"] + cfg["mo1_bragg"])) dz = diag * np.cos(2 * (cfg["cm_pitch"] + cfg["mo1_bragg"])) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_surfaces.py b/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_surfaces.py index dcac3dd..0b90b93 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_surfaces.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_surfaces.py @@ -65,7 +65,7 @@ def calc_surfaces(cfg: ConfigDict) -> SurfaceDict: height_beam = 2 * bl.cm.center[1] * np.tan(cfg["v_acc"]) w = height_beam / np.sin(cfg["mo1_bragg"]) - if cfg["mo1_mode"] in "Monochromatic": + if cfg["mo1_mode"] == "Monochromatic": out["mo1_1"]["x"] = [ xtal_pos - width_beam / 2, xtal_pos + width_beam / 2, diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_varia.py b/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_varia.py index 0cc43f0..e5911d8 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_varia.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/calculations/calc_varia.py @@ -258,7 +258,7 @@ def fm_ideal_pitch( """ p = bl.fm.center[1] # posFM q = smpl - bl.fm.center[1] # dist posFM to posEX - if fm_focus in "Defocused": + if fm_focus == "Defocused": assert sldi_hacc is not None, "sldi_hacc must be provided for Defocused mode" assert sldi_vacc is not None, "sldi_vacc must be provided for Defocused mode" assert fm_focx is not None, "fm_focx must be provided for Defocused mode" @@ -294,9 +294,9 @@ def cm_critical_angle(cm_stripe: Literal["Si", "Pt", "Rh"], energy) -> float: Returns: float: Critical angle in rad """ - if cm_stripe in "Si": + if cm_stripe == "Si": stripe = bl.stripeSi - elif cm_stripe in "Pt": + elif cm_stripe == "Pt": stripe = bl.stripePt else: stripe = bl.stripeRh @@ -320,15 +320,15 @@ def mirror_surface_geometries( dict[str, tuple[float, float, float, float]]: Dictionary mapping surface names to tuples of (x, y, width, height). """ - if mirror in "cm": + if mirror == "cm": surface = bl.cm.surface lim_opt_x = bl.cm.limOptX lim_opt_y = bl.cm.limOptY - elif mirror in "fm_toroid": + elif mirror == "fm_toroid": surface = bl.fm.surfaceToroid lim_opt_x = bl.fm.limOptXToroid lim_opt_y = bl.fm.limOptYToroid - elif mirror in "fm_flat": + elif mirror == "fm_flat": surface = bl.fm.surfaceFlat lim_opt_x = bl.fm.limOptXFlat lim_opt_y = bl.fm.limOptYFlat @@ -354,7 +354,7 @@ def mo_surface_geometries( dict[str, tuple[float, float, float, float]]: Dictionary mapping surface names to tuples of (x, y, width, height). """ - if mo in "mo1": + if mo == "mo1": xtal = bl.mo1.xtal xtal_width = bl.mo1.xtalWidth xtal_offset_x = bl.mo1.xtalOffsetX diff --git a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py index 7b7ee59..140efad 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py @@ -309,10 +309,10 @@ class DigitalTwin(BECWidget, QWidget): ConfigDict: config of the assistant """ fm_focus = self.input.fm_focus.currentText() - if fm_focus in "Manual": + if fm_focus == "Manual": fm_rotx = self.input.fm_rotx.value() fm_qy = None - elif fm_focus in "Focused": + elif fm_focus == "Focused": fm_rotx = self.input.fm_rotx_ideal.value() fm_qy = None else: # Focused @@ -600,13 +600,13 @@ class DigitalTwin(BECWidget, QWidget): selection of the focus strategy. """ fm_focus = self.input.fm_focus.currentText() - if fm_focus in "Manual": + if fm_focus == "Manual": self.input.fm_rotx.setVisible(True) self.input.fm_rotx_ideal.setVisible(True) self.input.fm_focx.setVisible(False) self.input.fm_focy.setVisible(False) self.input.fm_rotx_ideal.setLabel("Incidence Angle for focused beam") - elif fm_focus in "Focused": + elif fm_focus == "Focused": self.input.fm_rotx.setVisible(False) self.input.fm_rotx_ideal.setVisible(True) self.input.fm_focx.setVisible(False) @@ -659,7 +659,7 @@ class DigitalTwin(BECWidget, QWidget): """ fm_stripe = self.input.fm_stripe.currentText() fm_focus = self.input.fm_focus.currentText() - if fm_focus in "Manual": + if fm_focus == "Manual": fm_rotx = -self.input.fm_rotx.value() * 1e-3 else: fm_rotx = -self.input.fm_rotx_ideal.value() * 1e-3 @@ -745,11 +745,11 @@ class DigitalTwin(BECWidget, QWidget): Calculates bragg angle in rad """ xtal = self.input.mo1_xtal.currentText() - if xtal in "Si(111)": + if xtal == "Si(111)": d_spacing = self.dev.mo1_bragg.crystal.d_spacing_si111.read(cached=True)[ "mo1_bragg_crystal_d_spacing_si111" ]["value"] - elif xtal in "Si(311)": + elif xtal == "Si(311)": d_spacing = self.dev.mo1_bragg.crystal.d_spacing_si311.read(cached=True)[ "mo1_bragg_crystal_d_spacing_si311" ]["value"] @@ -767,7 +767,7 @@ class DigitalTwin(BECWidget, QWidget): Updates the monochromator input group based on the selection of the mode. """ - if self.input.mo1_mode.currentText() in "Monochromatic": + if self.input.mo1_mode.currentText() == "Monochromatic": self.input.mo1_xtal.setVisible(True) self.input.mo1_bragg_angle.setVisible(True) self.input.mo1_eres.setVisible(True) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py b/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py index 5e17c85..2eb0cf9 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py @@ -74,7 +74,7 @@ class SurfacePlots(QWidget): # Create surfaces for idx, scene in enumerate(self.surfaces): for name, _ in self.surfaces[scene].items(): - if scene in "assistant": + if scene == "assistant": brush = QBrush(QColor(*self.colors[idx], 255), Qt.BrushStyle.DiagCrossPattern) pen = pg.mkPen( QColor(*self.colors[idx], 255), width=1, style=Qt.PenStyle.DashLine @@ -130,7 +130,7 @@ class SurfacePlots(QWidget): for idx, scene in enumerate(self.surfaces): for name, _ in self.surfaces[scene].items(): - if scene in "assistant": + if scene == "assistant": brush = QBrush(QColor(*self.colors[idx], 255), Qt.BrushStyle.DiagCrossPattern) pen = pg.mkPen( QColor(*self.colors[idx], 255), width=1, style=Qt.PenStyle.DashLine @@ -165,13 +165,13 @@ class SurfacePlots(QWidget): self.texts.append(text) for name, plot in self.plots.items(): - if name in "cm": + if name == "cm": plot_surface(plot["widget"], mirror_surface_geometries("cm")) - elif name in "mo1_1": + elif name == "mo1_1": plot_surface(plot["widget"], mo_surface_geometries("mo1", 0)) - elif name in "mo1_2": + elif name == "mo1_2": plot_surface(plot["widget"], mo_surface_geometries("mo1", 1)) - elif name in "fm": + elif name == "fm": plot_surface(plot["widget"], mirror_surface_geometries("fm_flat")) plot_surface(plot["widget"], mirror_surface_geometries("fm_toroid")) else: @@ -223,7 +223,7 @@ class SideviewPlot(QWidget): self.walls = [] for idx, scene in enumerate(self.data.keys()): - if scene in "assistant": + if scene == "assistant": pen = pg.mkPen(color=self.colors[idx], width=2, style=Qt.PenStyle.DotLine) z_value = 2 else: @@ -281,7 +281,7 @@ class SideviewPlot(QWidget): self.text_color = (0, 0, 0) for idx, scene in enumerate(self.data): - if scene in "assistant": + if scene == "assistant": brush = QBrush(QColor(*self.colors[idx], 255), Qt.BrushStyle.DiagCrossPattern) pen = pg.mkPen(QColor(*self.colors[idx], 255), width=3, style=Qt.PenStyle.DashLine) else: diff --git a/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py b/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py index 8266b58..08e87d5 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py @@ -485,7 +485,7 @@ class MoveWidget(QWidget): def _on_motion_finished(self): """Finished a movement""" target = self.target - if self.status not in Status.ERROR: + if self.status != Status.ERROR: if abs(self.fb - target) <= self.deadband: self._set_status(Status.IN_POSITION) else: -- 2.54.0 From dc6966ee3109e7df07708f6475c26ddde8fce28c Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Tue, 9 Jun 2026 15:42:26 +0200 Subject: [PATCH 2/3] refactor(digital-twin): compact widget layout Wrap side panels in vertical scroll areas, tighten row and group spacing, use a balanced 1:1 plot area, and avoid assigning a second top-level layout. Also aligns MotionWorker signal signatures with the slots connected to them as part of the widget cleanup. --- .../widgets/digital_twin/digital_twin.py | 60 ++++++-- .../digital_twin/panels/input_panel.py | 5 +- .../digital_twin/panels/mover_panel.py | 9 +- .../widgets/digital_twin/panels/plots.py | 6 +- .../digital_twin/panels/settings_panel.py | 5 +- .../digital_twin/widgets/move_widget.py | 38 +++--- .../digital_twin/widgets/qt_widgets.py | 129 ++++-------------- 7 files changed, 102 insertions(+), 150 deletions(-) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py index 140efad..63af101 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py @@ -22,10 +22,13 @@ from qtpy.QtWidgets import ( QApplication, QDialog, QDialogButtonBox, + QFrame, QHBoxLayout, QLabel, QPlainTextEdit, QPushButton, + QScrollArea, + QSizePolicy, QStyle, QVBoxLayout, QWidget, @@ -74,32 +77,47 @@ class DigitalTwin(BECWidget, QWidget): self.check_config() self.bec_dispatcher.connect_slot(self.check_config, MessageEndpoints.device_config_update()) - central = QWidget() - self.root_layout = QHBoxLayout(central) + self.content_widget = QWidget(self) + self.root_layout = QHBoxLayout(self.content_widget) + self.root_layout.setContentsMargins(6, 6, 6, 6) + self.root_layout.setSpacing(6) self.input_widget = QWidget() self.input_layout = QVBoxLayout(self.input_widget) + self.input_layout.setContentsMargins(4, 4, 4, 4) + self.input_layout.setSpacing(6) self.input = InputPanel() self.settings = SettingsPanel() self.input_layout.addWidget(self.input) self.input_layout.addWidget(self.settings) + self.input_layout.addStretch() self.plot_widget = QWidget() self.plot_layout = QVBoxLayout(self.plot_widget) + self.plot_layout.setContentsMargins(4, 4, 4, 4) + self.plot_layout.setSpacing(6) self.sideview_plot = SideviewPlot() self.surface_plots = SurfacePlots() - self.plot_layout.addWidget(self.sideview_plot) - self.plot_layout.addWidget(self.surface_plots) + self.plot_layout.addWidget(self.sideview_plot, stretch=1) + self.plot_layout.addWidget(self.surface_plots, stretch=1) + self.plot_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.mover = MoverPanel(self.dev) - self.root_layout.addWidget(self.input_widget, alignment=Qt.AlignmentFlag.AlignTop) - self.root_layout.addWidget(self.plot_widget, alignment=Qt.AlignmentFlag.AlignTop) - self.root_layout.addWidget(self.mover, alignment=Qt.AlignmentFlag.AlignTop) + self.input_scroll = self._scroll_area(self.input_widget, min_width=320, max_width=360) + self.mover_scroll = self._scroll_area(self.mover, min_width=380, max_width=460) - self.setLayout(self.root_layout) + self.root_layout.addWidget(self.input_scroll) + self.root_layout.addWidget(self.plot_widget, stretch=1) + self.root_layout.addWidget(self.mover_scroll) + widget_layout = self.layout() + if widget_layout is None: + widget_layout = QVBoxLayout(self) + widget_layout.setContentsMargins(0, 0, 0, 0) + widget_layout.setSpacing(0) + widget_layout.addWidget(self.content_widget) self.setWindowTitle("Digital Twin") - self.resize(1800, 800) + self.resize(1450, 760) self.input.energy.value_changed_connect(self.calc_assistant) self.input.sldi_hacc.value_changed_connect(self.calc_assistant) @@ -127,12 +145,26 @@ class DigitalTwin(BECWidget, QWidget): self.load_offsets(recalculate=False) self.calc_assistant(identifier="init") - # Timer: update plots every 1 second + # Timer: update reality plots every 1 second self._timer = QTimer(self) - self._timer.setInterval(100) + self._timer.setInterval(1000) self._timer.timeout.connect(self.calc_reality) self._timer.start() + @staticmethod + def _scroll_area(widget: QWidget, min_width: int, max_width: int) -> QScrollArea: + """Wrap a side panel in a compact vertical scroll area.""" + scroll = QScrollArea() + scroll.setWidgetResizable(True) + widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.MinimumExpanding) + scroll.setWidget(widget) + scroll.setFrameShape(QFrame.Shape.NoFrame) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + scroll.setMinimumWidth(min_width) + scroll.setMaximumWidth(max_width) + scroll.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding) + return scroll + def apply_theme(self, theme: Literal["dark", "light"]): """ Apply the theme @@ -152,8 +184,8 @@ class DigitalTwin(BECWidget, QWidget): BEC dispatcher whenever there is a config update, stop the timer that updates the plot in the background. """ - reload = (args[0] if args else {}).get("action") == "reload" - if reload: + reload_config = (args[0] if args else {}).get("action") == "reload" + if reload_config: self._timer.stop() devices = [ "abs", @@ -234,7 +266,7 @@ class DigitalTwin(BECWidget, QWidget): running_app = QApplication.instance() if running_app is not None: running_app.exit(0) - if reload: + if reload_config: self._timer.start() @SafeSlot() diff --git a/debye_bec/bec_widgets/widgets/digital_twin/panels/input_panel.py b/debye_bec/bec_widgets/widgets/digital_twin/panels/input_panel.py index 093dae9..be65480 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/panels/input_panel.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/panels/input_panel.py @@ -3,7 +3,7 @@ Panel for user inputs of the digital twin widget """ # pylint: disable=E0611 -from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget +from qtpy.QtWidgets import QVBoxLayout, QWidget from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import ( Button, @@ -20,7 +20,8 @@ class InputPanel(QWidget): def __init__(self, parent=None): super().__init__(parent) self._layout = QVBoxLayout(self) - self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore + self._layout.setContentsMargins(4, 4, 4, 4) + self._layout.setSpacing(4) # Adapt to reality self.adapt_reality = Button(label_button="Adapt to reality", enabled=True) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py b/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py index 3f26f06..fcf3703 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py @@ -5,7 +5,7 @@ Panel to move an axis to a certain position from typing import Literal # pylint: disable=E0611 -from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget +from qtpy.QtWidgets import QVBoxLayout, QWidget from debye_bec.bec_widgets.widgets.digital_twin.widgets.move_widget import ( AbsorberWidget, @@ -20,7 +20,8 @@ class MoverPanel(QWidget): def __init__(self, dev, parent=None): super().__init__(parent) self._layout = QVBoxLayout(self) - self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore + self._layout.setContentsMargins(4, 4, 4, 4) + self._layout.setSpacing(4) self.mover_widgets = [] @@ -189,7 +190,7 @@ class MoverPanel(QWidget): ) self.mover_widgets.append(self.es0wi_try) - self.es0_mov_group = Group("Expperimental Station 0", [self.es0wi_try]) + self.es0_mov_group = Group("Experimental Station 0", [self.es0wi_try]) # Experimental Station 1 self.ot_es1_trz = MoveWidget( @@ -197,7 +198,7 @@ class MoverPanel(QWidget): ) self.mover_widgets.append(self.ot_es1_trz) - self.es1_mov_group = Group("Expperimental Station 1", [self.ot_es1_trz]) + self.es1_mov_group = Group("Experimental Station 1", [self.ot_es1_trz]) # Assemble complete mover group self.mover_group = Group( diff --git a/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py b/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py index 2eb0cf9..8cbafce 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py @@ -33,6 +33,8 @@ class SurfacePlots(QWidget): def __init__(self, parent=None): super().__init__(parent=parent) self._layout = QHBoxLayout(self) + self._layout.setContentsMargins(4, 4, 4, 4) + self._layout.setSpacing(6) self.surfaces: dict[str, SurfaceDict] = { "assistant": { @@ -202,7 +204,8 @@ class SideviewPlot(QWidget): def __init__(self, parent=None): super().__init__(parent=parent) self._layout = QVBoxLayout(self) - # self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore + self._layout.setContentsMargins(4, 4, 4, 4) + self._layout.setSpacing(0) self.plot_widget = pg.PlotWidget() self.plot_widget.getAxis("bottom").enableAutoSIPrefix(False) @@ -243,7 +246,6 @@ class SideviewPlot(QWidget): self.plot_widget.hideButtons() self._layout.addWidget(self.plot_group) - self._layout.addStretch() self.plot_vacuum_pipes() self.plot_walls() diff --git a/debye_bec/bec_widgets/widgets/digital_twin/panels/settings_panel.py b/debye_bec/bec_widgets/widgets/digital_twin/panels/settings_panel.py index 8947ea3..88ce3e1 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/panels/settings_panel.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/panels/settings_panel.py @@ -3,7 +3,7 @@ Settings panel for the digital twin widget """ # pylint: disable=E0611 -from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget +from qtpy.QtWidgets import QVBoxLayout, QWidget from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import ( Button, @@ -18,7 +18,8 @@ class SettingsPanel(QWidget): def __init__(self, parent=None): super().__init__(parent) self._layout = QVBoxLayout(self) - self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore + self._layout.setContentsMargins(4, 4, 4, 4) + self._layout.setSpacing(4) # Reload offsets self.load_offsets = Button(label="Load Offsets", label_button="Load", enabled=True) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py b/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py index 08e87d5..65efd74 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py @@ -128,8 +128,8 @@ class MotionWorker(QObject): """ position_changed = Signal(float) - error = Signal(bool) # True = error - finished = Signal(bool) # True = reached target, False = stopped + error = Signal() + finished = Signal() def __init__(self, dev, motor, target_pos: float): super().__init__() @@ -284,7 +284,7 @@ class MotionWorker(QObject): fb = surv_ax["device"].read(cached=True)[surv_ax["name"]]["value"] if abs(fb - surv_ax["old_value"]) > surv_ax["abs_tol"]: self.dev[self.motor].stop() - self.error.emit(1) + self.error.emit() break self.finished.emit() @@ -315,35 +315,33 @@ class MoveWidget(QWidget): self.decimals = decimals layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(4, 0, 4, 0) + layout.setSpacing(4) # Name self.label = QLabel(label) - self.label.setFixedWidth(100) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(76) self.label.setWordWrap(True) layout.addWidget(self.label) # Target self.target_label = QLabel("-") - self.target_label.setFixedWidth(100) + self.target_label.setFixedWidth(84) layout.addWidget(self.target_label) # Feedback self.fb_label = QLabel("-") - self.fb_label.setFixedWidth(100) + self.fb_label.setFixedWidth(84) layout.addWidget(self.fb_label) # Status icon self.status_icon = StatusIcon() - self.status_icon.setFixedWidth(30) - self.status_icon.setContentsMargins(0, 0, 10, 0) + self.status_icon.setFixedWidth(24) layout.addWidget(self.status_icon) # Start / Stop button self.btn_action = QPushButton("Move") - self.btn_action.setFixedWidth(90) + self.btn_action.setFixedWidth(64) self.btn_action.setFixedHeight(20) self.btn_action.clicked.connect(self._on_button_clicked) layout.addWidget(self.btn_action) @@ -522,35 +520,33 @@ class AbsorberWidget(QWidget): self.text_color = (0, 0, 0) layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(4, 0, 4, 0) + layout.setSpacing(4) # Name self.label = QLabel(label) - self.label.setFixedWidth(100) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(76) self.label.setWordWrap(True) layout.addWidget(self.label) # Blank self.blank_label = QLabel("") - self.blank_label.setFixedWidth(100) + self.blank_label.setFixedWidth(84) layout.addWidget(self.blank_label) # Feedback self.fb_label = QLabel("-") - self.fb_label.setFixedWidth(100) + self.fb_label.setFixedWidth(84) layout.addWidget(self.fb_label) # Blank icon self.blank_icon = QLabel("") - self.blank_icon.setFixedWidth(30) - self.blank_icon.setContentsMargins(0, 0, 10, 0) + self.blank_icon.setFixedWidth(24) layout.addWidget(self.blank_icon) # Open self.btn_action = QPushButton("Open") - self.btn_action.setFixedWidth(90) + self.btn_action.setFixedWidth(64) self.btn_action.setFixedHeight(20) self.btn_action.clicked.connect(self._on_button_clicked) layout.addWidget(self.btn_action) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/widgets/qt_widgets.py b/debye_bec/bec_widgets/widgets/digital_twin/widgets/qt_widgets.py index 5a0f59b..311c63b 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/widgets/qt_widgets.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/widgets/qt_widgets.py @@ -21,11 +21,17 @@ from qtpy.QtWidgets import ( QWidget, ) +LABEL_WIDTH = 118 +ROW_MARGINS = (4, 0, 4, 0) +ROW_SPACING = 6 + class Group(QGroupBox): def __init__(self, label, widgets): super().__init__(label) self.layout = QVBoxLayout(self) # type: ignore + self.layout.setContentsMargins(6, 6, 6, 6) + self.layout.setSpacing(4) for widget in widgets: self.layout.addWidget(widget) # type: ignore @@ -34,16 +40,14 @@ class NumberIndicator(QWidget): def __init__(self, label="", unit=None, highlight=False, decimals=3): super().__init__() layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(*ROW_MARGINS) + layout.setSpacing(ROW_SPACING) self.label = QLabel(label) - self.label.setFixedWidth(140) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(LABEL_WIDTH) self.label.setWordWrap(True) layout.addWidget(self.label) self.val = QLabel("-") self.val.setAlignment(Qt.AlignTop) # type: ignore - # self.val.setFixedWidth(140) layout.addWidget(self.val) self.unit = unit self.highlight = highlight @@ -85,12 +89,11 @@ class InputNumberField(QWidget): ): super().__init__() layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(*ROW_MARGINS) + layout.setSpacing(ROW_SPACING) self.identifier = identifier self.label = QLabel(label) - self.label.setFixedWidth(140) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(LABEL_WIDTH) self.label.setWordWrap(True) layout.addWidget(self.label) self.val = QDoubleSpinBox() @@ -102,7 +105,6 @@ class InputNumberField(QWidget): self.val.setSuffix(" " + unit) if prefix is not None: self.val.setPrefix(prefix + " ") - # self.val.setFixedWidth(140) layout.addWidget(self.val) def set_number(self, number): @@ -124,19 +126,18 @@ class InputNumberField(QWidget): class ComboBox(QWidget): - def __init__(self, identifier="", label="", enums=[]): + def __init__(self, identifier="", label="", enums=None): super().__init__() layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(*ROW_MARGINS) + layout.setSpacing(ROW_SPACING) self.identifier = identifier self.label = QLabel(label) - self.label.setFixedWidth(140) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(LABEL_WIDTH) self.label.setWordWrap(True) layout.addWidget(self.label) self.value = QComboBox() - for entry in enums: + for entry in enums or []: self.value.addItem(entry) layout.addWidget(self.value) @@ -168,15 +169,15 @@ class Button(QWidget): def __init__(self, label=None, label_button: str = "", enabled=False): super().__init__() layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(*ROW_MARGINS) + layout.setSpacing(ROW_SPACING) if label is not None: self.label = QLabel(label) - self.label.setFixedWidth(140) + self.label.setFixedWidth(LABEL_WIDTH) layout.addWidget(self.label) self.button = QPushButton(label_button) if label is not None: - self.button.setFixedWidth(160) + self.button.setFixedWidth(130) self.enable_button(enabled) layout.addWidget(self.button) @@ -204,11 +205,10 @@ class TextIndicator(QWidget): def __init__(self, label): super().__init__() layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(*ROW_MARGINS) + layout.setSpacing(ROW_SPACING) self.label = QLabel(label) - self.label.setFixedWidth(140) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(LABEL_WIDTH) self.label.setWordWrap(True) layout.addWidget(self.label) self.text = QLabel("-") @@ -223,84 +223,3 @@ class TextIndicator(QWidget): def setColor(self, color: str): self.text.setStyleSheet(f"QLabel {{color:{color}}}") - - -# class Button(QWidget): -# def __init__(self, label, label_button): -# super().__init__() -# layout = QHBoxLayout(self) -# layout.setContentsMargins(10, 0, 0, 0) -# layout.setSpacing(0) -# self.label = QLabel(label) -# self.label.setFixedWidth(150) -# layout.addWidget(self.label) -# self.button = QPushButton(label_button) -# self.button.setStyleSheet("color: black; background-color: dodgerblue;") -# self.button.setFixedWidth(160) -# layout.addWidget(self.button) - -# def set_on_press(self, func): -# """Connect a function to the button press.""" -# self.button.clicked.connect(func) - -# def enable_button(self): -# self.button.setEnabled(True) -# self.button.setStyleSheet("color: black; background-color: dodgerblue;") - -# def disable_button(self): -# self.button.setEnabled(False) -# self.button.setStyleSheet("color: black; background-color: grey;") - -# def set_button_text(self, text): -# self.button.setText(text) - -# class LED(QWidget): -# def __init__(self, states, colors, label): -# super().__init__() -# self.states = states -# self.colors = colors -# layout = QHBoxLayout(self) -# layout.setContentsMargins(10, 0, 0, 0) -# layout.setSpacing(0) -# self.label = QLabel(label) -# self.label.setFixedWidth(150) -# layout.addWidget(self.label) -# self.led = QLabel() -# self.led.setFixedWidth(160) -# layout.addWidget(self.led) - -# def apply_color(self, val): -# color = self.colors[self.states.index(val)] -# self.led.setStyleSheet(f"background-color: {color}; border: 1px solid black;") - -# class InputTextField(QWidget): -# def __init__(self, topic, label): -# super().__init__() -# self.topic = topic -# layout = QHBoxLayout(self) -# layout.setContentsMargins(10, 0, 0, 0) -# layout.setSpacing(0) -# self.label = QLabel(label) -# self.label.setFixedWidth(140) -# self.label.setContentsMargins(0, 0, 10, 0) -# self.label.setWordWrap(True) -# layout.addWidget(self.label) -# self.val = QLineEdit() -# self.val.setPlaceholderText('0') -# # self.val.setFixedWidth(140) -# layout.addWidget(self.val) - -# def set_text(self, text): -# self.val.setText(text) - -# def has_focus(self) -> bool: -# return self.val.hasFocus() - -# def text(self) -> str: -# return self.val.text() - -# def set_on_return(self, func): -# """Connect a function to the Enter/Return key press.""" -# self.val.returnPressed.connect( -# partial(func, self.val, self.topic, lambda: self.val.text()) -# ) -- 2.54.0 From d820d5adb5cb2f255b1b4374b3d3087ff2c17ec1 Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Tue, 9 Jun 2026 15:42:39 +0200 Subject: [PATCH 3/3] fix(digital-twin): resolve offsets file from package Load x01da_offsets.yaml relative to digital_twin.py instead of the process working directory so the widget starts correctly from any launch location. --- .../bec_widgets/widgets/digital_twin/digital_twin.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py index 63af101..6b466a4 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py @@ -58,7 +58,7 @@ from debye_bec.bec_widgets.widgets.digital_twin.types import ConfigDict logger = bec_logger.logger -OFFSET_FILE = "debye_bec/debye_bec/bec_widgets/widgets/digital_twin/x01da_offsets.yaml" +OFFSET_FILE = Path(__file__).with_name("x01da_offsets.yaml") class DigitalTwin(BECWidget, QWidget): @@ -555,11 +555,10 @@ class DigitalTwin(BECWidget, QWidget): if self.offsets == {}: # Load offsets - file = Path(OFFSET_FILE) - if not file.exists(): + if not OFFSET_FILE.exists(): raise FileNotFoundError(f"Offset file not found: {OFFSET_FILE}") - with file.open("r", encoding="utf-8") as f: + with OFFSET_FILE.open("r", encoding="utf-8") as f: data = yaml.safe_load(f) if not isinstance(data, dict): @@ -600,7 +599,7 @@ class DigitalTwin(BECWidget, QWidget): intro_label.setWordWrap(True) layout.addWidget(intro_label) - file = QLabel(OFFSET_FILE) + file = QLabel(str(OFFSET_FILE)) file.setWordWrap(True) font = QFont() font.setItalic(True) -- 2.54.0