From a82876b66c744b52be7405691818fd11e2e3cb6e Mon Sep 17 00:00:00 2001 From: appel_c Date: Tue, 28 Jul 2026 09:52:44 +0200 Subject: [PATCH] feat: Fix module and deprecate rotation base class --- tomcat_bec/devices/rotation_base/__initi__.py | 0 .../rotation_base/ophyd_rotation_base.py | 143 ++++++++++++++++++ tomcat_bec/devices/tomcat_rotation_motors.py | 4 +- 3 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 tomcat_bec/devices/rotation_base/__initi__.py create mode 100644 tomcat_bec/devices/rotation_base/ophyd_rotation_base.py diff --git a/tomcat_bec/devices/rotation_base/__initi__.py b/tomcat_bec/devices/rotation_base/__initi__.py new file mode 100644 index 0000000..e69de29 diff --git a/tomcat_bec/devices/rotation_base/ophyd_rotation_base.py b/tomcat_bec/devices/rotation_base/ophyd_rotation_base.py new file mode 100644 index 0000000..3006173 --- /dev/null +++ b/tomcat_bec/devices/rotation_base/ophyd_rotation_base.py @@ -0,0 +1,143 @@ +from abc import ABC, abstractmethod + +from bec_lib import bec_logger +from ophyd import Component as Cpt +from ophyd import EpicsMotor +from typeguard import typechecked +from typing import Protocol, runtime_checkable + +from ophyd_devices.utils.bec_utils import ConfigSignal + +logger = bec_logger.logger + + +class OphydRotationBaseError(Exception): + """Exception specific for implementation of rotation stages.""" + + +@runtime_checkable +class BECRotationProtocol(Protocol): + """Protocol which defines functionality for a tomography stage for ophyd devices""" + + allow_mod360: Cpt + """Signal to define whether mod360 operations are allowed. """ + + @property + def has_mod360(self) -> bool: + """Property to check if the motor has mod360 option + + Returns: + bool: True if mod360 is possible on device, False otherwise + """ + + @property + def has_freerun(self) -> bool: + """Property to check if the motor has freerun option + + Returns: + bool: True if freerun is allowed, False otherwise + """ + + @property + def valid_rotation_modes(self) -> list[str]: + """Method to get the valid rotation modes for the implemented motor. + + Returns: + list: List of strings with valid rotation modes + """ + + def apply_mod360(self) -> None: + """Method to apply the modulus 360 operation on the specific device. + + Childrens should override this method + """ + + +class OphydRotationBase(BECRotationProtocol, ABC): + + allow_mod360 = Cpt(ConfigSignal, name="allow_mod360", value=False, kind="config") + + def __init__(self, *args, **kwargs): + """ + Base class to implement functionality specific for rotation devices. + + Childrens should override the instance attributes: + - has_mod360 + - has_freerun + - valid_rotation_modes + + """ + # pylint: disable=protected-access + self._has_mod360 = False + self._has_freerun = False + self._valid_rotation_modes = [] + if "allow_mod360" in kwargs: + if not isinstance(kwargs["allow_mod360"], bool): + raise ValueError("allow_mod360 must be a boolean") + self.allow_mod360.put(kwargs["allow_mod360"]) + super().__init__(*args, **kwargs) + + @abstractmethod + def apply_mod360(self) -> None: + """Method to apply the modulus 360 operation on the specific device. + + Childrens should override this method + """ + + @property + def has_mod360(self) -> bool: + """Property to check if the device has mod360 operation. + + ReadOnly property, childrens should override this method. + """ + return self._has_mod360 + + @property + def has_freerun(self) -> bool: + """Property to check if the device has freerun operation. + + ReadOnly property, childrens should override this method. + """ + return self._has_freerun + + @property + def valid_rotation_modes(self) -> list: + """Method to get the valid rotation modes for the specific device.""" + return self._valid_rotation_modes + + @typechecked + @valid_rotation_modes.setter + def valid_rotation_modes(self, value: list[str]): + """Method to set the valid rotation modes for the specific device.""" + self._valid_rotation_modes = value + return self._valid_rotation_modes + + +# pylint: disable=too-many-ancestors +class EpicsRotationBase(OphydRotationBase, EpicsMotor): + """Class for Epics rotation devices.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._has_freerun = True + self._has_freerun = True + self._valid_rotation_modes = ["target", "radiography"] + + def apply_mod360(self) -> None: + """Apply modulos 360 operation for EpicsMotorRecord. + + EpicsMotor has the function "set_current_position" which can be used for this purpose. + In addition, there is a check if mod360 is allowed and available. + """ + if self.has_mod360 and self.allow_mod360.get(): + cur_val = self.user_readback.get() + new_val = cur_val % 360 + try: + self.set_current_position(new_val) + except Exception as exc: + error_msg = f"Failed to set new position {new_val} from {cur_val} on device {self.name} with error {exc}" + raise OphydRotationBaseError(error_msg) from exc + return + logger.info( + f"Did not apply mod360 for device {self.name} with has_mod={self.has_mod360} and allow_mod={self.allow_mod360.get()}" + ) diff --git a/tomcat_bec/devices/tomcat_rotation_motors.py b/tomcat_bec/devices/tomcat_rotation_motors.py index 1941247..7085bc2 100644 --- a/tomcat_bec/devices/tomcat_rotation_motors.py +++ b/tomcat_bec/devices/tomcat_rotation_motors.py @@ -1,4 +1,4 @@ -""" Module for Tomcat rotation motors. +"""Module for Tomcat rotation motors. The following classes implement the rotation motors for: @@ -11,7 +11,7 @@ import time import numpy as np from ophyd import DeviceStatus -from ophyd_devices.interfaces.base_classes.ophyd_rotation_base import EpicsRotationBase +from tomcat_bec.devices.rotation_base.ophyd_rotation_base import EpicsRotationBase from ophyd_devices.interfaces.protocols.bec_protocols import BECFlyerProtocol, BECScanProtocol