From b718b438bacff6eb6cd6015f1a67dcf75c05dce4 Mon Sep 17 00:00:00 2001 From: appel_c Date: Fri, 18 Jul 2025 09:29:20 +0200 Subject: [PATCH] fix(positioner-box): Test to fix handling of none integer values for precision --- .../_base/positioner_box_base.py | 21 ++++++++--- .../positioner_group/positioner_group.py | 6 +++- tests/unit_tests/test_positioner_box.py | 35 +++++++++++++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py b/bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py index 24e4b2c0..07c0f526 100644 --- a/bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py +++ b/bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py @@ -138,7 +138,7 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget): signals = msg_content.get("signals", {}) # pylint: disable=protected-access hinted_signals = self.dev[device]._hints - precision = self.dev[device].precision + precision = getattr(self.dev[device], "precision", None) spinner = ui_components["spinner"] position_indicator = ui_components["position_indicator"] @@ -178,11 +178,19 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget): spinner.setVisible(False) if readback_val is not None: - readback.setText(f"{readback_val:.{precision}f}") + if not isinstance(precision, bool) and isinstance(precision, int): + text = f"{readback_val:.{precision}f}" + else: + text = str(readback_val) + readback.setText(text) position_emit(readback_val) if setpoint_val is not None: - setpoint.setText(f"{setpoint_val:.{precision}f}") + if not isinstance(precision, bool) and isinstance(precision, int): + text = f"{setpoint_val:.{precision}f}" + else: + text = str(setpoint_val) + setpoint.setText(text) limits = self.dev[device].limits limit_update(limits) @@ -205,10 +213,13 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget): ui["readback"].setToolTip(f"{device} readback") ui["setpoint"].setToolTip(f"{device} setpoint") ui["step_size"].setToolTip(f"Step size for {device}") - precision = self.dev[device].precision - if precision is not None: + precision = getattr(self.dev[device], "precision", None) + if not isinstance(precision, bool) and isinstance(precision, int): ui["step_size"].setDecimals(precision) ui["step_size"].setValue(10**-precision * 10) + else: # Default to 8 decimals if precision is not specified + ui["step_size"].setDecimals(8) + ui["step_size"].setValue(10**-8 * 10) def _swap_readback_signal_connection(self, slot, old_device, new_device): self.bec_dispatcher.disconnect_slot(slot, MessageEndpoints.device_readback(old_device)) diff --git a/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py b/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py index 2e4174c5..49be1367 100644 --- a/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py +++ b/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py @@ -45,7 +45,11 @@ class PositionerGroupBox(QGroupBox): def _on_position_update(self, pos: float): self.position_update.emit(pos) - self.widget.label = f"%.{self.widget.dev[self.widget.device].precision}f" % pos + precision = getattr(self.widget.dev[self.widget.device], "precision", None) + if not isinstance(precision, bool) and isinstance(precision, int): + self.widget.label = f"{pos:.{precision}f}" + else: + self.widget.label = f"{pos}" def close(self): self.widget.close() diff --git a/tests/unit_tests/test_positioner_box.py b/tests/unit_tests/test_positioner_box.py index 1e28452c..5485e5c1 100644 --- a/tests/unit_tests/test_positioner_box.py +++ b/tests/unit_tests/test_positioner_box.py @@ -7,6 +7,7 @@ from qtpy.QtCore import Qt, QTimer from qtpy.QtGui import QValidator from qtpy.QtWidgets import QPushButton +from bec_widgets.tests.utils import Positioner from bec_widgets.widgets.control.device_control.positioner_box import ( PositionerBox, PositionerControlLine, @@ -19,6 +20,18 @@ from .client_mocks import mocked_client from .conftest import create_widget +class PositionerWithoutPrecision(Positioner): + """just placeholder for testing embedded isinstance check in DeviceCombobox""" + + def __init__(self, precision, name="test", limits=None, read_value=1.0, enabled=True): + super().__init__(name, limits=limits, read_value=read_value, enabled=enabled) + self._precision = precision + + @property + def precision(self): + return self._precision + + @pytest.fixture def positioner_box(qtbot, mocked_client): """Fixture for PositionerBox widget""" @@ -165,3 +178,25 @@ def test_device_validity_check_rejects_non_positioner(): positioner_box = mock.MagicMock(spec=PositionerBox) positioner_box.dev = {"test": 5.123} assert not PositionerBox._check_device_is_valid(positioner_box, "test") + + +def test_positioner_box_device_without_precision(qtbot, positioner_box): + """Test positioner box with device without precision""" + + for ii, mock_return in enumerate([None, 2, 2.0, True, "tmp"]): + dev_name = f"samy_{ii}" + device = PositionerWithoutPrecision( + precision=mock_return, name=dev_name, limits=[-5, 5], read_value=3.0 + ) + positioner_box.bec_dispatcher.client.device_manager.add_devices(devices=[device]) + + positioner_box.device = dev_name + + def check_title(): + return positioner_box.ui.device_box.title() == dev_name + + qtbot.waitUntil(check_title, timeout=3000) + if not isinstance(mock_return, bool) and isinstance(mock_return, int): + assert positioner_box.ui.step_size.value() == 10**-mock_return * 10 + else: + assert positioner_box.ui.step_size.value() == 10**-8 * 10