# -*- coding: utf-8 -*-
#
# Project name: MXCuBE
# https://github.com/mxcube
#
# This file is part of MXCuBE software.
#
# MXCuBE is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# MXCuBE is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU General Lesser Public License
# along with MXCuBE. If not, see <http://www.gnu.org/licenses/>.
"""Abstract Energy and Wavelength class.
Defines the get/set wavelength, get_wavelength_limits methods and is_tunable
property. Implements update_value.
Emits signals valueChanged and attributeChanged.
"""
import abc
from mxcubecore.HardwareObjects.abstract.AbstractActuator import AbstractActuator
from mxcubecore.utils.conversion import HC_OVER_E
__copyright__ = """ Copyright © 2010-2020 by the MXCuBE collaboration """
__license__ = "LGPLv3+"
[docs]class AbstractEnergy(AbstractActuator):
"""Abstract Energy and Wavelength"""
# Unit for 'value' attribute
unit = "keV"
__metaclass__ = abc.ABCMeta
def __init__(self, name):
super().__init__(name)
self._wavelength_limits = (None, None)
[docs] def is_ready(self):
"""Check if the state is ready.
Returns:
(bool): True if ready, False otherwise.
"""
if self.read_only:
return True
return super().is_ready()
@property
def is_tunable(self):
"""Check if not fixed energy.
Returns:
(bool): True if tunable, False if fixed energy.
"""
return not self.read_only
[docs] def get_wavelength(self):
"""Read the wavelength
Returns:
(float): Wavelength [Å].
"""
return self.calculate_wavelength()
[docs] def get_wavelength_limits(self):
"""Return wavelength low and high limits.
Returns:
(tuple): two floats tuple (low limit, high limit) [Å].
"""
_low, _high = self.get_limits()
self._wavelength_limits = (
self.calculate_wavelength(_high),
self.calculate_wavelength(_low),
)
return self._wavelength_limits
[docs] def set_wavelength(self, value, timeout=None):
"""Move motor to absolute value. Wait the move to finish.
Args:
value (float): target position [keV]
timeout (float): optional - timeout [s].
if timeout = 0: return at once and do not wait
if timeout is None: wait forever
"""
self.set_value(self.calculate_energy(value), timeout=timeout)
[docs] def calculate_wavelength(self, energy=None):
"""Calculate wavelength from energy
Args:
energy(float): Energy [keV]
Returns:
(float): wavelength [Å]
"""
energy = energy or self.get_value()
# TODO NBNB This is naughty. Could we not put the heuristic switch
# in the calling functions, to avoid surprises?
# rhfogh 20210826
# energy in KeV to get wavelength in Å
energy = energy / 1000.0 if energy > 1000 else energy
return HC_OVER_E / energy
[docs] def calculate_energy(self, wavelength=None):
"""Calculate energy from wavelength
Args:
value((float): wavelength [Å]
Returns:
(float): Energy [keV]
"""
wavelength = wavelength or self.get_wavelength()
return HC_OVER_E / wavelength
[docs] def update_value(self, value=None):
"""Emist signal energyChanged for both energy and wavelength
Argin:
value: Not used, but kept in the method signature.
"""
if value is None:
value = self.get_value()
self._nominal_value = value
_wavelength_value = self.calculate_wavelength(value)
self.emit("energyChanged", (value, _wavelength_value))
self.emit("valueChanged", (value,))
[docs] def force_emit_signals(self):
super().force_emit_signals()
_energy_value = self.get_value()
_wavelength_value = self.calculate_wavelength(_energy_value)
self.emit("energyChanged", (_energy_value, _wavelength_value))