wip from beamline, 22.05.2026 #83
@@ -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]
|
||||
|
||||
@@ -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"]))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -416,3 +416,16 @@ def pipe_geometries() -> list[dict[str, np.ndarray]]:
|
||||
}
|
||||
)
|
||||
return pipes
|
||||
|
||||
|
||||
def table_to_smpl_pos(table: str) -> float:
|
||||
"""
|
||||
Return the sample position based on the table name.
|
||||
|
||||
Args:
|
||||
table (str): Table name, e.g. ES1 or ES2
|
||||
"""
|
||||
|
||||
if table in bl.tables:
|
||||
return bl.tables[table]["smpl"]
|
||||
raise ValueError(f"Table {table} not found in beamline parameter file")
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Digital Twin: Custom BEC widget to support the beamline alignment.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Literal, cast
|
||||
@@ -20,12 +21,16 @@ from qtpy.QtCore import Qt, QTimer
|
||||
from qtpy.QtGui import QFont
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFrame,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPlainTextEdit,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QSizePolicy,
|
||||
QStyle,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
@@ -46,17 +51,19 @@ from debye_bec.bec_widgets.widgets.digital_twin.calculations.calc_varia import (
|
||||
mo1_bragg_angle,
|
||||
mo1_energy_resolution,
|
||||
sldi_gap_to_acc,
|
||||
table_to_smpl_pos,
|
||||
)
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.panels.input_panel import InputPanel
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.panels.mover_panel import MoverPanel
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.panels.plots import SideviewPlot, SurfacePlots
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.panels.settings_panel import SettingsPanel
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.types import ConfigDict
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.types import BeamlineId, ConfigDict
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import ComboBox, InputNumberField
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
OFFSET_FILE = "debye_bec/debye_bec/bec_widgets/widgets/digital_twin/x01da_offsets.yaml"
|
||||
|
||||
OFFSET_FILE_X01DA = Path(__file__).with_name("x01da_offsets.yaml")
|
||||
OFFSET_FILE_X10DA = Path(__file__).with_name("x10da_offsets.yaml")
|
||||
|
||||
class DigitalTwin(BECWidget, QWidget):
|
||||
"""
|
||||
@@ -70,36 +77,66 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
super().__init__(parent=parent, theme_update=True, *arg, **kwargs)
|
||||
self.get_bec_shortcuts()
|
||||
|
||||
# Check if devices are all in config
|
||||
self.check_config()
|
||||
self.bec_dispatcher.connect_slot(self.check_config, MessageEndpoints.device_config_update())
|
||||
self.beamline = self.get_beamline_id()
|
||||
# Debugging, override beamline!
|
||||
# self.beamline = BeamlineId.X10DA
|
||||
|
||||
central = QWidget()
|
||||
self.root_layout = QHBoxLayout(central)
|
||||
self.offset_file = Path()
|
||||
match self.beamline:
|
||||
case "x01da":
|
||||
self.offset_file = OFFSET_FILE_X01DA
|
||||
case "x10da":
|
||||
self.offset_file = OFFSET_FILE_X10DA
|
||||
|
||||
# Check if devices are all in config
|
||||
self.check_bec_config()
|
||||
self.bec_dispatcher.connect_slot(
|
||||
self.check_bec_config, MessageEndpoints.device_config_update()
|
||||
)
|
||||
|
||||
logger.info(f"Start Digital Twin with beamline {self.beamline} and all devices available")
|
||||
|
||||
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 = InputPanel()
|
||||
self.input_layout.setContentsMargins(4, 4, 4, 4)
|
||||
self.input_layout.setSpacing(6)
|
||||
self.input = InputPanel(self.beamline)
|
||||
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, 950)
|
||||
|
||||
self.input.energy.value_changed_connect(self.calc_assistant)
|
||||
self.input.sldi_hacc.value_changed_connect(self.calc_assistant)
|
||||
@@ -113,7 +150,11 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.input.fm_rotx.value_changed_connect(self.calc_assistant)
|
||||
self.input.fm_focx.value_changed_connect(self.calc_assistant)
|
||||
self.input.fm_focy.value_changed_connect(self.calc_assistant)
|
||||
self.input.smpl.value_changed_connect(self.calc_assistant)
|
||||
match self.input.smpl:
|
||||
case InputNumberField():
|
||||
self.input.smpl.value_changed_connect(self.calc_assistant)
|
||||
case ComboBox():
|
||||
self.input.smpl.activated_connect(self.calc_assistant)
|
||||
|
||||
self.input.adapt_reality.clicked_connect(self.adapt_reality)
|
||||
self.settings.load_offsets.clicked_connect(self.load_offsets)
|
||||
@@ -127,12 +168,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
|
||||
@@ -144,16 +199,48 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.surface_plots.apply_theme(theme)
|
||||
self.mover.apply_theme(theme)
|
||||
|
||||
def get_beamline_id(self) -> BeamlineId:
|
||||
"""
|
||||
Based on the bec servers hostname, tries to extract the beamline
|
||||
identifier (e.g. x01da, x10da, etc).
|
||||
|
||||
Raises:
|
||||
ValueError if beamline cannot be extracted from hostname or beamline not implemented.
|
||||
"""
|
||||
bec_hostname = socket.gethostname()
|
||||
start = bec_hostname.find("x")
|
||||
if start != -1:
|
||||
beamline = bec_hostname[start : start + 5]
|
||||
match beamline:
|
||||
case "x01da":
|
||||
return BeamlineId.X01DA
|
||||
case "x10da":
|
||||
return BeamlineId.X10DA
|
||||
case _:
|
||||
raise ValueError(f"Not implemented beamline {beamline}")
|
||||
else:
|
||||
logger.warning(f"Failed to extract beamline from bec server hostname {bec_hostname}")
|
||||
choice = input("Do you want to manually select a beamline? (yes/no): ").strip().lower()
|
||||
if choice in ["yes", "y"]:
|
||||
bl = input(f"Choose from: {[bl.value for bl in BeamlineId]}")
|
||||
if bl in BeamlineId:
|
||||
logger.info(f'Manually selected beamline {bl}')
|
||||
return BeamlineId(bl)
|
||||
else:
|
||||
raise ValueError(f'Wrong selection {bl}')
|
||||
else:
|
||||
raise ValueError('Cannot open digital twin without a beamline')
|
||||
|
||||
@SafeSlot()
|
||||
def check_config(self, *args):
|
||||
def check_bec_config(self, *args):
|
||||
"""
|
||||
Checks the BEC config and opens a window if not all necessary
|
||||
devices are loaded in the config. If called from a slot from
|
||||
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",
|
||||
@@ -161,7 +248,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
"sldi_gapy",
|
||||
"cm_trx",
|
||||
"cm_try",
|
||||
"cm_bnd_radius",
|
||||
"cm_bnd",
|
||||
"cm_rotx",
|
||||
"mo1_bragg",
|
||||
"mo1_trx",
|
||||
@@ -171,23 +258,33 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
"bm1_try",
|
||||
"fm_trx",
|
||||
"fm_try",
|
||||
"fm_bnd_radius",
|
||||
"fm_bnd",
|
||||
"fm_rotx",
|
||||
"fm_roty",
|
||||
"fm_rotz",
|
||||
"sl2_centery",
|
||||
"sl2_gapy",
|
||||
"bm2_try",
|
||||
"ot_try",
|
||||
"ot_rotx",
|
||||
"es0wi_try",
|
||||
"ot_es1_trz",
|
||||
]
|
||||
if self.beamline == "x01da": # X01DA specific devices
|
||||
devices.extend(
|
||||
[
|
||||
"cm_bnd_radius",
|
||||
"fm_bnd_radius",
|
||||
"sl2_centery",
|
||||
"sl2_gapy",
|
||||
"ot_try",
|
||||
"ot_es1_trz",
|
||||
]
|
||||
)
|
||||
if self.beamline == "x10da": # X10DA specific devices
|
||||
devices.extend(["mo1_rotx", "es1_try", "es1ic0_try", "es1ic1_try", "es1ic2_try"])
|
||||
|
||||
while True:
|
||||
missing = [d for d in devices if d not in self.dev]
|
||||
if not missing:
|
||||
break
|
||||
dialog = QDialog()
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("Digital Twin - Config Check")
|
||||
dialog.setFixedWidth(400)
|
||||
layout = QVBoxLayout()
|
||||
@@ -234,7 +331,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()
|
||||
@@ -309,10 +406,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
|
||||
@@ -327,6 +424,13 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
assert cm_trx is not None, f"No cm_trx found for given stripe {cm_stripe}!"
|
||||
assert fm_trx is not None, f"No fm_trx found for given stripe {fm_stripe}!"
|
||||
|
||||
match self.input.smpl:
|
||||
case InputNumberField():
|
||||
smpl = self.input.smpl.value()
|
||||
case ComboBox():
|
||||
table = self.input.smpl.currentText()
|
||||
smpl = table_to_smpl_pos(table)
|
||||
|
||||
config: ConfigDict = {
|
||||
"energy": self.input.energy.value(),
|
||||
"h_acc": self.input.sldi_hacc.value(),
|
||||
@@ -342,7 +446,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
"fm_trx": fm_trx,
|
||||
"fm_qy": fm_qy,
|
||||
"fm_gain_height": 1,
|
||||
"smpl": self.input.smpl.value(),
|
||||
"smpl": smpl,
|
||||
}
|
||||
|
||||
# Apply offsets
|
||||
@@ -508,9 +612,52 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.input.fm_focus.set_current_text("Manual")
|
||||
fm_rotx_real = 2 * pos["cm_rotx"] - pos["fm_rotx"]
|
||||
self.input.fm_rotx.set_number(fm_rotx_real)
|
||||
self.input.smpl.set_number(pos["ot_es1_trz"])
|
||||
match self.input.smpl:
|
||||
case InputNumberField():
|
||||
self.input.smpl.set_number(pos["ot_es1_trz"])
|
||||
case ComboBox():
|
||||
table = self.ask_table_selection(self.input.smpl.currentText())
|
||||
self.input.smpl.set_current_text(table)
|
||||
|
||||
self.calc_assistant(identifier="init")
|
||||
|
||||
def ask_table_selection(self, preset=None) -> str | None:
|
||||
"""
|
||||
Opens a dialog asking the user to select a table (ES1 or ES2).
|
||||
|
||||
Args:
|
||||
preset (str): Preset text for the table, either 'ES1' or 'ES2'.
|
||||
|
||||
Returns:
|
||||
The selected table ('ES1' or 'ES2'), or None if the user cancelled.
|
||||
"""
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("Select Table")
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
text = QLabel("Select the current table in use.")
|
||||
|
||||
combo = QComboBox()
|
||||
choice = ["ES1", "ES2"]
|
||||
combo.addItems(choice)
|
||||
if preset is not None:
|
||||
if preset in choice:
|
||||
combo.setCurrentText(preset)
|
||||
|
||||
buttons = QDialogButtonBox(
|
||||
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
)
|
||||
buttons.accepted.connect(dialog.accept)
|
||||
buttons.rejected.connect(dialog.reject)
|
||||
|
||||
layout.addWidget(text)
|
||||
layout.addWidget(combo)
|
||||
layout.addWidget(buttons)
|
||||
|
||||
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||
return combo.currentText()
|
||||
return None
|
||||
|
||||
@SafeSlot()
|
||||
def load_offsets(self, *_, recalculate: bool = True):
|
||||
"""
|
||||
@@ -523,11 +670,10 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
|
||||
if self.offsets == {}:
|
||||
# Load offsets
|
||||
file = Path(OFFSET_FILE)
|
||||
if not file.exists():
|
||||
raise FileNotFoundError(f"Offset file not found: {OFFSET_FILE}")
|
||||
|
||||
with file.open("r", encoding="utf-8") as f:
|
||||
if not self.offset_file.exists():
|
||||
raise FileNotFoundError(f"Offset file not found: {self.offset_file}")
|
||||
|
||||
with self.offset_file.open("r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
@@ -568,7 +714,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
intro_label.setWordWrap(True)
|
||||
layout.addWidget(intro_label)
|
||||
|
||||
file = QLabel(OFFSET_FILE)
|
||||
file = QLabel(str(self.offset_file))
|
||||
file.setWordWrap(True)
|
||||
font = QFont()
|
||||
font.setItalic(True)
|
||||
@@ -600,13 +746,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 +805,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 +891,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 +913,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)
|
||||
@@ -784,7 +930,12 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
Literal["Defocused", "Focused", "Manual"], self.input.fm_focus.currentText()
|
||||
)
|
||||
fm_stripe = self.input.fm_stripe.currentText()
|
||||
smpl = self.input.smpl.value()
|
||||
match self.input.smpl:
|
||||
case InputNumberField():
|
||||
smpl = self.input.smpl.value()
|
||||
case ComboBox():
|
||||
table = self.input.smpl.currentText()
|
||||
smpl = table_to_smpl_pos(table)
|
||||
sldi_hacc = self.input.sldi_hacc.value() * 1e-3
|
||||
sldi_vacc = self.input.sldi_vacc.value() * 1e-3
|
||||
fm_focx = self.input.fm_focx.value()
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
Panel for user inputs of the digital twin widget
|
||||
"""
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget
|
||||
from typing import Union
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import QVBoxLayout, QWidget
|
||||
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.types import BeamlineId
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import (
|
||||
Button,
|
||||
ComboBox,
|
||||
@@ -15,12 +18,18 @@ from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import (
|
||||
|
||||
|
||||
class InputPanel(QWidget):
|
||||
"""Panel for user inputs of the digital twin widget"""
|
||||
"""
|
||||
Panel for user inputs of the digital twin widget
|
||||
|
||||
def __init__(self, parent=None):
|
||||
Args:
|
||||
beamline (BeamlineId): Beamline id type
|
||||
"""
|
||||
|
||||
def __init__(self, beamline: BeamlineId, 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)
|
||||
@@ -91,9 +100,11 @@ class InputPanel(QWidget):
|
||||
)
|
||||
|
||||
# Focusing Mirror
|
||||
self.fm_stripe = ComboBox(
|
||||
"fm_stripe", "Stripe", ["Rh (toroid)", "Rh (flat)", "Pt (toroid)", "Pt (flat)"]
|
||||
)
|
||||
stripes: dict[BeamlineId, list[str]] = {
|
||||
BeamlineId.X01DA: ["Rh (toroid)", "Rh (flat)", "Pt (toroid)", "Pt (flat)"],
|
||||
BeamlineId.X10DA: ["Rh (toroid)", "Pt (toroid)"],
|
||||
}
|
||||
self.fm_stripe = ComboBox("fm_stripe", "Stripe", stripes[beamline])
|
||||
self.fm_focus = ComboBox("fm_focus", "Focus Type", ["Manual", "Focused", "Defocused"])
|
||||
self.fm_rotx = InputNumberField(
|
||||
"fm_rotx",
|
||||
@@ -144,18 +155,9 @@ class InputPanel(QWidget):
|
||||
|
||||
# Sample
|
||||
self.cm_fm_harm_suppr = NumberIndicator("Total Suppression Factor at x eV", "", decimals=0)
|
||||
self.smpl = InputNumberField(
|
||||
"smpl",
|
||||
"Sample Position",
|
||||
unit="mm",
|
||||
init=23511,
|
||||
decimals=0,
|
||||
single_step=100,
|
||||
ll=23000,
|
||||
hl=30000,
|
||||
)
|
||||
self.smpl = self._create_smpl(beamline)
|
||||
|
||||
# Assemble complete assitant group
|
||||
# Assemble complete assistant group
|
||||
self.input_group = Group(
|
||||
"User Input",
|
||||
[
|
||||
@@ -172,3 +174,19 @@ class InputPanel(QWidget):
|
||||
|
||||
self._layout.addWidget(self.input_group)
|
||||
self._layout.addStretch()
|
||||
|
||||
def _create_smpl(self, beamline: BeamlineId) -> Union[InputNumberField, ComboBox]:
|
||||
match beamline:
|
||||
case BeamlineId.X01DA:
|
||||
return InputNumberField(
|
||||
"smpl",
|
||||
"Sample Position",
|
||||
unit="mm",
|
||||
init=23511,
|
||||
decimals=0,
|
||||
single_step=100,
|
||||
ll=23000,
|
||||
hl=30000,
|
||||
)
|
||||
case BeamlineId.X10DA:
|
||||
return ComboBox("smpl", "Sample Position", ["ES1", "ES2"])
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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": {
|
||||
@@ -74,7 +76,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 +132,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 +167,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:
|
||||
@@ -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)
|
||||
@@ -223,7 +226,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:
|
||||
@@ -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()
|
||||
@@ -281,7 +283,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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
"""Types used for the beamline config and for plotting data"""
|
||||
|
||||
from enum import Enum
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class BeamlineId(str, Enum):
|
||||
"""
|
||||
Identifier for supported beamlines.
|
||||
"""
|
||||
|
||||
X01DA = "x01da"
|
||||
X10DA = "x10da"
|
||||
|
||||
|
||||
class ConfigDict(TypedDict):
|
||||
"""
|
||||
Typed dictionary representing the beamline configuration.
|
||||
|
||||
@@ -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)
|
||||
@@ -485,7 +483,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:
|
||||
@@ -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)
|
||||
|
||||
@@ -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,13 @@ 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.enable_button(enabled)
|
||||
layout.addWidget(self.button)
|
||||
|
||||
@@ -204,11 +203,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 +221,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())
|
||||
# )
|
||||
|
||||
@@ -304,6 +304,8 @@ smpl = sample(name="EH-SMPL", center=[0, 23365, sourceHeight])
|
||||
|
||||
smpl2 = sample(name="EH-SMPL2", center=[0, 27500, sourceHeight])
|
||||
|
||||
tables = {}
|
||||
|
||||
# Vacuum pipes
|
||||
# DN40CF ID = 35 mm oder 37 mm
|
||||
# DN50CF ID = 47.5 mm
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
"""
|
||||
X10DA / SuperXAS Beamline Parameters.
|
||||
This file describes the parameter of each component of the SuperXAS beamline
|
||||
to be used for raytracing and geometrical calculations.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
import xrt.backends.raycing.materials as rm
|
||||
|
||||
# XRT definitions
|
||||
filterBeryl = rm.Material("Be", rho=1.85, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
filterDiamond = rm.Material("C", rho=3.52, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
filterGraphite = rm.Material("C", rho=2.266, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
|
||||
stripeSi = rm.Material("Si", rho=2.33) # pyright: ignore[reportArgumentType]
|
||||
stripePt = rm.Material("Pt", rho=21.45) # pyright: ignore[reportArgumentType]
|
||||
stripeRh = rm.Material("Rh", rho=12.41) # pyright: ignore[reportArgumentType]
|
||||
stripeCr = rm.Material("Cr", rho=7.14) # pyright: ignore[reportArgumentType]
|
||||
stripePyrex = rm.Material(
|
||||
"Si", rho=2.20
|
||||
) # Use Si as bare element and the density of SiO2 # pyright: ignore[reportArgumentType]
|
||||
|
||||
si111_1 = rm.CrystalSi(hkl=(1, 1, 1), tK=77) # first xtal surface
|
||||
si311_1 = rm.CrystalSi(hkl=(3, 1, 1), tK=77) # first xtal surface
|
||||
si333_1 = rm.CrystalSi(hkl=(3, 3, 3), tK=77) # first xtal surface
|
||||
si511_1 = rm.CrystalSi(hkl=(5, 1, 1), tK=77) # first xtal surface
|
||||
si111_2 = rm.CrystalSi(hkl=(1, 1, 1), tK=77) # second xtal surface
|
||||
si311_2 = rm.CrystalSi(hkl=(3, 1, 1), tK=77) # second xtal surface
|
||||
si333_2 = rm.CrystalSi(hkl=(3, 3, 3), tK=77) # second xtal surface
|
||||
si511_2 = rm.CrystalSi(hkl=(5, 1, 1), tK=77) # second xtal surface
|
||||
|
||||
filterDiamond = rm.Material("C", rho=3.52, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
filterBe = rm.Material("Be", rho=1.85, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
filterSi3N4 = rm.Material(
|
||||
["Si", "N"], quantities=[3, 4], rho=3.44, kind="plate"
|
||||
) # pyright: ignore[reportArgumentType]
|
||||
filterAl = rm.Material("Al", rho=2.69, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
filterGraphite = rm.Material("C", rho=2.266, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
|
||||
# General parameters
|
||||
sourceHeight = 0
|
||||
|
||||
# Synchrotron
|
||||
synchrotron = namedtuple(
|
||||
"synchrotron", ["eE", "eI", "eEspread", "eEpsilonX", "eEpsilonZ", "betaX", "betaZ"]
|
||||
)
|
||||
|
||||
sls1 = synchrotron(
|
||||
eE=2.4, eI=0.4, eEspread=0.878e-3, eEpsilonX=5.63, eEpsilonZ=0.007, betaX=0.45, betaZ=14.4
|
||||
)
|
||||
|
||||
sls2 = synchrotron(
|
||||
eE=2.7, eI=0.4, eEspread=1.147e-3, eEpsilonX=0.156, eEpsilonZ=0.01, betaX=0.18, betaZ=4.6
|
||||
)
|
||||
|
||||
# Source
|
||||
bendingMagnet = namedtuple("bendingMagnet", ["name", "center", "sync", "B0"])
|
||||
|
||||
sls1_14t = bendingMagnet(name="FE-BM-SLS1-1.4T", center=(0, 0, 0), sync=sls1, B0=1.4)
|
||||
|
||||
sls2_21t = bendingMagnet(name="FE-BM-SLS2-2.1T", center=(0, 0, 0), sync=sls2, B0=2.1)
|
||||
|
||||
sls2_35t = bendingMagnet(name="FE-BM-SLS2-3.5T", center=(0, 0, 0), sync=sls2, B0=3.5)
|
||||
|
||||
sls2_50t = bendingMagnet(name="FE-BM-SLS2-5.0T", center=(0, 0, 0), sync=sls2, B0=5.0)
|
||||
|
||||
# FE slits
|
||||
slits = namedtuple("slits", ["name", "center", "maxDivH", "maxDivV"])
|
||||
|
||||
feSlits = slits(name="FE-SLITS", center=(0, 5290, sourceHeight), maxDivH=1.8e-3, maxDivV=0.8e-3)
|
||||
|
||||
# Filters
|
||||
filt = namedtuple(
|
||||
"filt", ["name", "center", "pitch", "limPhysX", "limPhysY", "surface", "material", "thickness"]
|
||||
)
|
||||
|
||||
feWindow = filt(
|
||||
name="FE-WINDOW",
|
||||
center=(0.0, 6158, sourceHeight),
|
||||
pitch=np.pi / 2,
|
||||
limPhysX=(-6, 6),
|
||||
limPhysY=(-3.0, 3.0),
|
||||
surface="None",
|
||||
material=filterDiamond,
|
||||
thickness=0.1,
|
||||
)
|
||||
feWindow = feWindow._replace(
|
||||
surface="CVD Diamond window {0:0.0f} $\mu$m".format(feWindow.thickness * 1e3)
|
||||
)
|
||||
|
||||
feFilt = filt(
|
||||
name="FE-FI",
|
||||
center=(0.0, 6590, sourceHeight),
|
||||
pitch=np.pi / 2,
|
||||
limPhysX=(-15, 15),
|
||||
limPhysY=(-10, 10),
|
||||
surface="None",
|
||||
material=filterGraphite,
|
||||
thickness=0.25,
|
||||
)
|
||||
feFilt = feFilt._replace(surface="Graphite filter {0:0.0f} $\mu$m".format(feFilt.thickness * 1e3))
|
||||
|
||||
# Collimating mirror
|
||||
collimatingMirror = namedtuple(
|
||||
"collimatingMirror",
|
||||
[
|
||||
"name",
|
||||
"center",
|
||||
"surface",
|
||||
"material",
|
||||
"limPhysX",
|
||||
"limPhysY",
|
||||
"limOptX",
|
||||
"limOptY",
|
||||
"R",
|
||||
"pitch",
|
||||
"jack1",
|
||||
"jack2",
|
||||
"jack3",
|
||||
"tx1",
|
||||
"tx2",
|
||||
],
|
||||
)
|
||||
|
||||
cm = collimatingMirror(
|
||||
name="FE-CM",
|
||||
center=[0, 7618, sourceHeight],
|
||||
surface=("Si", "Pt", "Rh"),
|
||||
material=(stripeSi, stripePt, stripeRh),
|
||||
limPhysX=(-30, 30),
|
||||
limPhysY=(-600, 600),
|
||||
limOptX=((-21, -8, 5), (-11, 2, 21)),
|
||||
limOptY=((-500, -500, -500), (500, 500, 500)),
|
||||
R=[3e6, 15e6],
|
||||
pitch=[1.4e-3, 4.5e-3],
|
||||
jack1=[0.0, 7210.0, 0.0], # Tripod X, Y, Z (global)
|
||||
jack2=[-210.0, 8310.0, 0.0],
|
||||
jack3=[210.0, 8310.0, 0.0],
|
||||
tx1=[0.0, -575.5], # X-Stage 1 [x, y] (local)
|
||||
tx2=[0.0, 575],
|
||||
) # X-Stage 2
|
||||
|
||||
apertures = namedtuple("apertures", ["name", "center", "opening"])
|
||||
|
||||
fePS = apertures(
|
||||
name="FE-PS", center=[0, 8760, sourceHeight], opening=[-39 / 2, 39 / 2, -10, 29]
|
||||
) # left, right, bottom, top
|
||||
|
||||
opWbBsBlock = apertures(
|
||||
name="OP-WB-BS-BLOCK", center=[0.0, 13606 - 135, sourceHeight], opening=[-18.0, 18.0, 42, 76]
|
||||
) # left, right, bottom, top
|
||||
|
||||
opSlits = apertures(
|
||||
name="OP-SLITS", center=[0, 14145 - 135, sourceHeight], opening=[-35 / 2, 35 / 2, 47.5, 82.5]
|
||||
)
|
||||
|
||||
# Monochromator
|
||||
monochromator = namedtuple(
|
||||
"monochromator",
|
||||
[
|
||||
"name",
|
||||
"center",
|
||||
"xtal",
|
||||
"material1",
|
||||
"material2",
|
||||
"xtalWidth",
|
||||
"xtalOffsetX",
|
||||
"xtalLength1",
|
||||
"xtalLength2",
|
||||
"xtalGap",
|
||||
"rotOffset",
|
||||
"heightOffset",
|
||||
"braggLim",
|
||||
"jack1",
|
||||
"jack2",
|
||||
"jack3",
|
||||
"tx",
|
||||
],
|
||||
)
|
||||
|
||||
mo1 = monochromator(
|
||||
name="OP-CCM1",
|
||||
center=[0.0, 11670 - 135, sourceHeight],
|
||||
xtal=("Si311", "Si111"),
|
||||
material1=(si311_1, si111_1),
|
||||
material2=(si311_2, si111_2),
|
||||
xtalWidth=(20, 20),
|
||||
xtalOffsetX=(-19.2, 19.2),
|
||||
xtalLength1=(60, 60),
|
||||
xtalLength2=(60, 60),
|
||||
xtalGap=(8, 8),
|
||||
rotOffset=6, # not sure what it is
|
||||
heightOffset=8.5, # not sure what it is
|
||||
braggLim=[4, 35],
|
||||
jack1=[0.0, 11350.0, 0.0], # Tripod not available!
|
||||
jack2=[-400.0, 12350.0, 0.0],
|
||||
jack3=[400.0, 12350.0, 0.0],
|
||||
tx=0.0,
|
||||
) # X-Stage [x]
|
||||
|
||||
# Focusing mirror
|
||||
focusingMirror = namedtuple(
|
||||
"focusingMirror",
|
||||
[
|
||||
"name",
|
||||
"center",
|
||||
"surfaceToroid",
|
||||
"materialToroid",
|
||||
"limPhysXToroid",
|
||||
"limPhysYToroid",
|
||||
"limOptXToroid",
|
||||
"limOptYToroid",
|
||||
"R",
|
||||
"pitch",
|
||||
"r",
|
||||
"xToroid",
|
||||
"hToroid",
|
||||
"jack1",
|
||||
"jack2",
|
||||
"jack3",
|
||||
"tx1",
|
||||
"tx2",
|
||||
],
|
||||
)
|
||||
|
||||
fm = focusingMirror(
|
||||
name="OP-FM",
|
||||
center=[0.0, 15580 - 135, sourceHeight],
|
||||
surfaceToroid=("Rh (toroid)", "Pt (toroid)"),
|
||||
materialToroid=(stripeRh, stripePt),
|
||||
limPhysXToroid=(-54.0, 54.0),
|
||||
limPhysYToroid=(-565.0, 565.0),
|
||||
limOptXToroid=((4.865, -40.882), (43.388, -4.865)),
|
||||
limOptYToroid=((-500.0, -500.0), (500.0, 500.0)),
|
||||
R=[3e6, 15e6],
|
||||
pitch=[1.4e-3, 4.5e-3],
|
||||
r=[30, 20],
|
||||
xToroid=[24.126, -22, 874], # offset in local x
|
||||
hToroid=[7.0, 11.3], # depth of the cylinder at x = xCylinder1 and x = xCylinder2.
|
||||
jack1=[0.0, 14980.0, 0.0],
|
||||
jack2=[-75.0, 16180.0, 0.0],
|
||||
jack3=[75.0, 16180.0, 0.0],
|
||||
tx1=[0.0, -575.0], # X-Stage 1 [x, y]
|
||||
tx2=[0.0, 575.0],
|
||||
) # X-Stage 2 [x, y]
|
||||
|
||||
ehWindow = filt(
|
||||
name="EH-WINDOW",
|
||||
center=(0.0, 22225 - 135, sourceHeight),
|
||||
pitch=np.pi / 2,
|
||||
limPhysX=(-10.0, 10.0),
|
||||
limPhysY=(17.5, 92.5),
|
||||
surface="None",
|
||||
material=filterBe,
|
||||
thickness=0.25,
|
||||
)
|
||||
ehWindow = ehWindow._replace(
|
||||
surface="Beryllium window {0:0.0f} $\mu$m".format(ehWindow.thickness * 1e3)
|
||||
)
|
||||
|
||||
# Sample
|
||||
sample = namedtuple("sample", ["name", "center"])
|
||||
|
||||
smpl = sample(name="OP-SMPL", center=[0, 24000 - 135, sourceHeight])
|
||||
|
||||
ES1 = sample(name="EH-ES1", center=[0, 24000, sourceHeight])
|
||||
ES2 = sample(name="EH-ES2", center=[0, 25000, sourceHeight])
|
||||
@@ -0,0 +1,302 @@
|
||||
|
||||
###################################
|
||||
## Frontend Absorber ##
|
||||
###################################
|
||||
|
||||
abs:
|
||||
readoutPriority: baseline
|
||||
description: Frontend Absorber
|
||||
deviceClass: debye_bec.devices.sim.absorber.SimAbsorber
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
|
||||
###################################
|
||||
## Frontend Slits ##
|
||||
###################################
|
||||
|
||||
sldi_gapx:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-gap
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 4.75
|
||||
enabled: true
|
||||
|
||||
sldi_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-gap
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 1.5
|
||||
enabled: true
|
||||
|
||||
###################################
|
||||
## Collimating Mirror ##
|
||||
###################################
|
||||
|
||||
cm_bnd:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror bender
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: -2573
|
||||
enabled: true
|
||||
|
||||
cm_bnd_radius:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Bending Radius (simulated)
|
||||
deviceClass: ophyd.Signal
|
||||
deviceConfig:
|
||||
value: 5.51
|
||||
readOnly: true
|
||||
enabled: true
|
||||
|
||||
cm_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Pitch
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: -2.498
|
||||
enabled: true
|
||||
|
||||
cm_rotz:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Roll
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 0
|
||||
enabled: true
|
||||
|
||||
cm_roty:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Yaw
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 0
|
||||
enabled: true
|
||||
|
||||
cm_trx:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Center Point X
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: -1.5
|
||||
enabled: true
|
||||
|
||||
cm_try:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Center Point Y
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 0.2
|
||||
enabled: true
|
||||
|
||||
|
||||
###################################
|
||||
## Monochromator ##
|
||||
###################################
|
||||
|
||||
mo1_bragg:
|
||||
readoutPriority: baseline
|
||||
deviceClass: debye_bec.devices.sim.mo1_bragg.SimMo1BraggPositioner
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
|
||||
mo1_try:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator Y Translation
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 30.4
|
||||
enabled: true
|
||||
|
||||
mo1_trx:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator X Translation
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 21.2
|
||||
enabled: true
|
||||
|
||||
###################################
|
||||
## Optics Slits + Beam Monitor 1 ##
|
||||
###################################
|
||||
|
||||
bm1_try:
|
||||
readoutPriority: baseline
|
||||
description: Beam Monitor 1 Y-translation
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 105
|
||||
enabled: true
|
||||
|
||||
sl1_centery:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 Y-center
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 52
|
||||
enabled: true
|
||||
|
||||
sl1_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 Y-gap
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 5
|
||||
enabled: true
|
||||
|
||||
###################################
|
||||
## Focusing Mirror ##
|
||||
###################################
|
||||
|
||||
fm_bnd:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror bender
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: -30500
|
||||
enabled: true
|
||||
|
||||
fm_bnd_radius:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror Bending Radius (simulated)
|
||||
deviceClass: ophyd.Signal
|
||||
deviceConfig:
|
||||
value: 6.55
|
||||
readOnly: true
|
||||
enabled: true
|
||||
|
||||
fm_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Pitch
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: -2.578
|
||||
enabled: true
|
||||
|
||||
fm_roty:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Yaw
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: -0.038
|
||||
enabled: true
|
||||
|
||||
fm_rotz:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Roll
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: -0.004
|
||||
enabled: true
|
||||
|
||||
fm_trx:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Center Point X
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: -49.5
|
||||
enabled: true
|
||||
|
||||
fm_try:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Center Point Y
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 51.75
|
||||
enabled: true
|
||||
|
||||
###################################
|
||||
## Optics Slits + Beam Monitor 2 ##
|
||||
###################################
|
||||
|
||||
bm2_try:
|
||||
readoutPriority: baseline
|
||||
description: Beam Monitor 2 Y-translation
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 115
|
||||
enabled: true
|
||||
|
||||
sl2_centery:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 Y-center
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 58.9
|
||||
enabled: true
|
||||
|
||||
sl2_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 Y-gap
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 5
|
||||
enabled: true
|
||||
|
||||
###################################
|
||||
## Optical Table ##
|
||||
###################################
|
||||
|
||||
ot_es1_trz:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table ES1 Z-Translation
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 23508
|
||||
enabled: true
|
||||
|
||||
ot_try:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table Y-Translation
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 60.185
|
||||
enabled: true
|
||||
|
||||
ot_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table Pitch
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: -0.2
|
||||
enabled: true
|
||||
|
||||
###################################
|
||||
## Exit Window ##
|
||||
###################################
|
||||
|
||||
es0wi_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station 0 Exit Window Y-translation
|
||||
deviceClass: debye_bec.devices.sim.positioner.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 5
|
||||
enabled: true
|
||||
@@ -0,0 +1,74 @@
|
||||
"""Frontend Absorber"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd.sim import SynSignal
|
||||
from ophyd_devices import CompareStatus, DeviceStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
|
||||
class AbsorberError(Exception):
|
||||
"""Absorber specific exception"""
|
||||
|
||||
class STATUS(int, enum.Enum):
|
||||
"""Absorber States"""
|
||||
|
||||
MOVING_CLOSE = 0
|
||||
OPEN = 1
|
||||
MOVING_OPEN = 2
|
||||
CLOSED = 3
|
||||
NOT_ENABLED = 4
|
||||
TIMEOUT_CLOSE = 5
|
||||
TIMEOUT_OPEN = 6
|
||||
CLOSE_LS_LOST = 7
|
||||
OPEN_LS_LOST = 8
|
||||
CLOSE_LS_NOT_FREE = 9
|
||||
OPEN_LS_NOT_FREE = 10
|
||||
ERROR_LS = 11
|
||||
TO_CONNECT = 12
|
||||
MAN_OPEN = 13
|
||||
UNDEFINED = 14
|
||||
|
||||
class SimAbsorber(PSIDeviceBase):
|
||||
"""Simulated class for the Frontend Absorber"""
|
||||
|
||||
USER_ACCESS = ["open", "close"]
|
||||
|
||||
request = Cpt(SynSignal, name="request", kind="config", doc="Open/Close Absorber")
|
||||
status = Cpt(SynSignal, name="status", kind="normal", doc="Absorber Status")
|
||||
status_string = Cpt(SynSignal, name="status_string", kind="normal", doc="Absorber Status")
|
||||
|
||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
|
||||
self.timeout_for_move = 10
|
||||
# Set initial simulated state
|
||||
self.status.put(STATUS.CLOSED)
|
||||
self.status_string.put("CLOSED")
|
||||
|
||||
def open(self) -> DeviceStatus | None:
|
||||
"""Open the Absorber"""
|
||||
if self.status.get() == STATUS.CLOSED:
|
||||
self.status.put(STATUS.OPEN)
|
||||
self.status_string.put("OPEN")
|
||||
status_open = CompareStatus(self.status, STATUS.OPEN, timeout=self.timeout_for_move)
|
||||
return status_open
|
||||
else:
|
||||
return None
|
||||
|
||||
def close(self) -> DeviceStatus | None:
|
||||
"""Close the Absorber"""
|
||||
if self.status.get() == STATUS.OPEN:
|
||||
self.status.put(STATUS.CLOSED)
|
||||
self.status_string.put("CLOSED")
|
||||
status_close = CompareStatus(self.status, STATUS.CLOSED, timeout=self.timeout_for_move)
|
||||
return status_close
|
||||
else:
|
||||
return None
|
||||
@@ -0,0 +1,94 @@
|
||||
"""Module for the Mo1 Bragg positioner"""
|
||||
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import (
|
||||
Device,
|
||||
DeviceStatus,
|
||||
)
|
||||
from ophyd.sim import SynSignal, Signal
|
||||
from ophyd_devices.sim.sim_positioner import ReadOnlySignal
|
||||
from ophyd.ophydobj import Kind
|
||||
|
||||
from debye_bec.devices.sim.positioner import SimPositionerWithInitVal
|
||||
|
||||
|
||||
class SimMo1BraggCrystalCurrent(Device):
|
||||
|
||||
d_spacing = Cpt(Signal, value=3.1344024, kind=Kind.normal)
|
||||
bragg_off = Cpt(Signal, value=1.01063, kind=Kind.normal)
|
||||
phi_off = Cpt(Signal, value=0.07078, kind=Kind.normal)
|
||||
azm_off = Cpt(Signal, value=0.0425, kind=Kind.normal)
|
||||
miscut = Cpt(Signal, value=0.05, kind=Kind.normal)
|
||||
xtal = Cpt(Signal, value=0, kind=Kind.normal)
|
||||
xtal_string = Cpt(Signal, value="Si(111)", kind=Kind.normal)
|
||||
|
||||
|
||||
class SimMo1BraggCrystal(Device):
|
||||
|
||||
current = Cpt(SimMo1BraggCrystalCurrent, "")
|
||||
d_spacing_si111 = Cpt(Signal, value=3.1344024, kind=Kind.normal)
|
||||
d_spacing_si311 = Cpt(Signal, value=1.6375, kind=Kind.normal)
|
||||
|
||||
|
||||
class SimMo1BraggPositioner(SimPositionerWithInitVal):
|
||||
|
||||
crystal = Cpt(SimMo1BraggCrystal, "")
|
||||
angle = Cpt(Signal, value=6.310127056633, kind=Kind.normal)
|
||||
|
||||
####### Energy PVs #######
|
||||
|
||||
readback = Cpt(SynSignal, name="readback", kind="hinted")
|
||||
setpoint = Cpt(SynSignal, name="setpoint", kind="normal")
|
||||
motor_is_moving = Cpt(SynSignal, name="motor_is_moving", kind="normal")
|
||||
low_lim = Cpt(SynSignal, name="low_lim", kind="config")
|
||||
high_lim = Cpt(SynSignal, name="high_lim", kind="config")
|
||||
velocity = Cpt(SynSignal, name="velocity", kind="config")
|
||||
|
||||
####### Move Command PVs #######
|
||||
|
||||
move_abs = Cpt(SynSignal, name="move_abs", kind="config")
|
||||
move_stop = Cpt(SynSignal, name="move_stop", kind="config")
|
||||
|
||||
SUB_READBACK = "readback"
|
||||
_default_sub = SUB_READBACK
|
||||
SUB_PROGRESS = "progress"
|
||||
|
||||
def __init__(self, *, name: str, prefix: str = "", **kwargs):
|
||||
super().__init__(name=name, prefix=prefix, **kwargs)
|
||||
|
||||
self.tolerance.put(0.001)
|
||||
|
||||
self._set_status = None
|
||||
self.readback.put(17995)
|
||||
self.setpoint.put(17995)
|
||||
self.motor_is_moving.put(0)
|
||||
self.low_lim.put(-100.0)
|
||||
self.high_lim.put(100.0)
|
||||
self.velocity.put(1.0)
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return self.readback.get()
|
||||
|
||||
@property
|
||||
def egu(self) -> str:
|
||||
"""Return the engineering units of the positioner"""
|
||||
return "eV"
|
||||
|
||||
def set(self, new_pos: float, timeout: float = 10.0) -> DeviceStatus:
|
||||
"""Move to new_pos immediately (no real motion)."""
|
||||
self.motor_is_moving.put(1)
|
||||
self.readback.put(new_pos)
|
||||
self.setpoint.put(new_pos)
|
||||
self._position = new_pos
|
||||
self.motor_is_moving.put(0)
|
||||
self._run_subs(sub_type=self.SUB_READBACK, value=new_pos, obj=self)
|
||||
|
||||
status = DeviceStatus(self)
|
||||
status._finished()
|
||||
return status
|
||||
|
||||
def stop(self, success: bool = False):
|
||||
self.motor_is_moving.put(0)
|
||||
if self._set_status is not None and not self._set_status.done:
|
||||
self._set_status._finished(success=success)
|
||||
@@ -0,0 +1,27 @@
|
||||
from ophyd_devices.sim.sim_positioner import SimPositioner
|
||||
|
||||
class SimPositionerWithInitVal(SimPositioner):
|
||||
"""SimPositioner with support for an initial readback value via sim_init.
|
||||
|
||||
Example YAML config:
|
||||
samx:
|
||||
deviceClass: my_plugin.devices.SimPositionerWithInitVal
|
||||
deviceConfig:
|
||||
sim_init:
|
||||
init_val: 1234.0
|
||||
enabled: true
|
||||
"""
|
||||
|
||||
def __init__(self, *args, sim_init: dict = None, **kwargs):
|
||||
# Extract init_val before passing sim_init to the parent,
|
||||
# since the parent's set_init doesn't know about it.
|
||||
self._init_val = None
|
||||
if sim_init:
|
||||
self._init_val = sim_init.pop("init_val", None)
|
||||
|
||||
super().__init__(*args, sim_init=sim_init if sim_init else None, update_frequency=10, **kwargs)
|
||||
|
||||
self.tolerance.put(0.001)
|
||||
|
||||
if self._init_val is not None:
|
||||
self._update_readback(self._init_val)
|
||||
Reference in New Issue
Block a user