from functools import lru_cache
import logging
import time
from typing import Tuple, Union, List
from ..utils.misc import RequestBody
from ..utils.enums import FegState, HighTensionState, FegFlashingType
from .extras import Vector, SpecialObj
ERR_MSG_GUN1 = "Gun1 interface is not available. Requires TEM server 7.10+"
class GunObj(SpecialObj):
""" Wrapper around Gun COM object specifically for the Gun1 interface. """
def __init__(self, com_object):
super().__init__(com_object)
import comtypes.gen.TEMScripting as Ts
if hasattr(Ts, "Gun1"):
self.gun1 = self.com_object.QueryInterface(Ts.Gun1)
else:
self.gun1 = None
def is_available(self) -> bool:
""" Gun1 inherits from the Gun interface of the std scripting. """
return self.gun1 is not None
def get_hv_offset(self) -> float:
if not self.is_available():
raise NotImplementedError(ERR_MSG_GUN1)
return self.gun1.HighVoltageOffset
def set_hv_offset(self, value: float) -> None:
if not self.is_available():
raise NotImplementedError(ERR_MSG_GUN1)
self.gun1.HighVoltageOffset = value
def get_hv_offset_range(self) -> Tuple:
if not self.is_available():
raise NotImplementedError(ERR_MSG_GUN1)
result = self.gun1.GetHighVoltageOffsetRange()
return result[0], result[1]
[docs]
class Gun:
""" Gun functions. """
__slots__ = ("__client", "__id", "__id_adv", "__err_msg_gun1", "__err_msg_cfeg")
def __init__(self, client):
self.__client = client
self.__id = "tem.Gun"
self.__id_adv = "tem_adv.Source"
self.__err_msg_cfeg = "Source/C-FEG interface is not available"
@property
@lru_cache(maxsize=1)
def __has_gun1(self) -> bool:
body = RequestBody(attr=self.__id,
obj_cls=GunObj,
obj_method="is_available",
validator=bool)
return self.__client.call(method="exec_special", body=body)
@property
@lru_cache(maxsize=1)
def __has_source(self) -> bool:
body = RequestBody(attr=self.__id_adv + ".State", validator=bool)
return self.__client.call(method="has", body=body)
@property
def shift(self) -> Vector:
""" Gun shift. (read/write) """
shx = RequestBody(attr=self.__id + ".Shift.X", validator=float)
shy = RequestBody(attr=self.__id + ".Shift.Y", validator=float)
x = self.__client.call(method="get", body=shx)
y = self.__client.call(method="get", body=shy)
return Vector(x, y)
@shift.setter
def shift(self, vector: Union[Vector, List[float], Tuple[float, float]]) -> None:
value = Vector.convert_to(vector)
value.set_limits(-1.0, 1.0)
body = RequestBody(attr=self.__id + ".Shift", value=value)
self.__client.call(method="set", body=body)
@property
def tilt(self) -> Vector:
""" Gun tilt. (read/write) """
tx = RequestBody(attr=self.__id + ".Tilt.X", validator=float)
ty = RequestBody(attr=self.__id + ".Tilt.Y", validator=float)
x = self.__client.call(method="get", body=tx)
y = self.__client.call(method="get", body=ty)
return Vector(x, y)
@tilt.setter
def tilt(self, vector: Union[Vector, List[float], Tuple[float, float]]) -> None:
value = Vector.convert_to(vector)
value.set_limits(-1.0, 1.0)
body = RequestBody(attr=self.__id + ".Tilt", value=value)
self.__client.call(method="set", body=body)
@property
def voltage_offset_range(self) -> Tuple[float, float]:
""" Returns the high voltage offset range. """
if self.__has_gun1:
body = RequestBody(attr=self.__id,
obj_cls=GunObj,
obj_method="get_hv_offset_range",
validator=tuple)
return self.__client.call(method="exec_special", body=body)
else:
raise NotImplementedError(ERR_MSG_GUN1)
@property
def voltage_offset(self) -> float:
""" High voltage offset. (read/write) """
if self.__has_gun1:
body = RequestBody(attr=self.__id,
obj_cls=GunObj,
obj_method="get_hv_offset",
validator=float)
return self.__client.call(method="exec_special", body=body)
else:
raise NotImplementedError(ERR_MSG_GUN1)
@voltage_offset.setter
def voltage_offset(self, offset: float) -> None:
if self.__has_gun1:
hv_min, hv_max = self.voltage_offset_range
if not (hv_min <= float(offset) <= hv_max):
raise ValueError("Value is outside of allowed "
"range: %0.3f - %0.3f" % (hv_min, hv_max))
body = RequestBody(attr=self.__id,
obj_cls=GunObj,
obj_method="set_hv_offset",
value=offset)
self.__client.call(method="exec_special", body=body)
else:
raise NotImplementedError(ERR_MSG_GUN1)
@property
def feg_state(self) -> str:
""" FEG emitter status (FegState enum). """
if self.__has_source:
body = RequestBody(attr=self.__id_adv + ".State", validator=int)
result = self.__client.call(method="get", body=body)
return FegState(result).name
else:
raise NotImplementedError(self.__err_msg_cfeg)
@property
def ht_state(self) -> str:
""" High tension state: HighTensionState enum.
Disabling/enabling can only be done via the button on the
system on/off-panel, not via script. When switching on
the high tension, this function cannot check if and
when the set value is actually reached. (read/write)
"""
body = RequestBody(attr=self.__id + ".HTState", validator=int)
result = self.__client.call(method="get", body=body)
return HighTensionState(result).name
@ht_state.setter
def ht_state(self, value: HighTensionState) -> None:
body = RequestBody(attr=self.__id + ".HTState", value=value)
self.__client.call(method="set", body=body)
@property
def voltage(self) -> float:
""" The value of the HT setting as displayed in the TEM user
interface. Units: kVolts. (read/write)
"""
body = RequestBody(attr=self.__id + ".HTState", validator=int)
state = self.__client.call(method="get", body=body)
if state == HighTensionState.ON:
body = RequestBody(attr=self.__id + ".HTValue", validator=float)
return self.__client.call(method="get", body=body) * 1e-3
else:
return 0.0
@voltage.setter
def voltage(self, value: float) -> None:
voltage_max = self.voltage_max
if not (0.0 <= float(value) <= voltage_max):
raise ValueError("%s is outside of range 0.0-%s" % (value, voltage_max))
body = RequestBody(attr=self.__id + ".HTValue", value=float(value) * 1000)
self.__client.call(method="set", body=body)
while True:
body = RequestBody(attr=self.__id + ".HTValue", validator=float)
if self.__client.call(method="get", body=body) == float(value) * 1000:
logging.info("Changing HT voltage complete.")
break
else:
time.sleep(10)
@property
def voltage_max(self) -> float:
""" The maximum possible value of the HT on this microscope. Units: kVolts. """
body = RequestBody(attr=self.__id + ".HTMaxValue", validator=float)
return self.__client.call(method="get", body=body) * 1e-3
@property
def beam_current(self) -> float:
""" Returns the C-FEG beam current in nanoAmperes. """
if self.__has_source:
body = RequestBody(attr=self.__id_adv + ".BeamCurrent", validator=float)
return self.__client.call(method="get", body=body) * 1e9
else:
raise NotImplementedError(self.__err_msg_cfeg)
@property
def extractor_voltage(self) -> float:
""" Returns the extractor voltage. """
if self.__has_source:
body = RequestBody(attr=self.__id_adv + ".ExtractorVoltage", validator=float)
return self.__client.call(method="get", body=body)
else:
raise NotImplementedError(self.__err_msg_cfeg)
@property
def gun_lens(self) -> Tuple[int, int]:
""" Returns coarse and fine gun lens index. Not available on systems with a monochromator. """
if self.__has_source:
coarse = RequestBody(attr=self.__id_adv + ".FocusIndex.Coarse", validator=int)
fine = RequestBody(attr=self.__id_adv + ".FocusIndex.Fine", validator=int)
return (self.__client.call(method="get", body=coarse),
self.__client.call(method="get", body=fine))
else:
raise NotImplementedError(self.__err_msg_cfeg)
[docs]
def do_flashing(self, flash_type: FegFlashingType) -> None:
""" Perform cold FEG flashing.
:param FegFlashingType flash_type:
"""
if not self.__has_source:
raise NotImplementedError(self.__err_msg_cfeg)
if self.is_flashing_advised(flash_type):
# Warning: lowT flashing can be done even if not advised
doflash = RequestBody(attr=self.__id_adv + ".Flashing.PerformFlashing()",
arg=flash_type)
self.__client.call(method="exec", body=doflash)
else:
raise Warning("Flashing type %s is not advised" % flash_type)
[docs]
def is_flashing_advised(self, flash_type: FegFlashingType) -> bool:
""" Check if cold FEG flashing is advised.
:param FegFlashingType flash_type:
"""
if not self.__has_source:
raise NotImplementedError(self.__err_msg_cfeg)
body = RequestBody(attr=self.__id_adv + ".Flashing.IsFlashingAdvised()",
arg=flash_type, validator=bool)
return self.__client.call(method="exec", body=body)