This commit is contained in:
@@ -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,6 +21,7 @@ from qtpy.QtCore import Qt, QTimer
|
||||
from qtpy.QtGui import QFont
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QHBoxLayout,
|
||||
@@ -46,12 +48,14 @@ 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
|
||||
|
||||
@@ -70,16 +74,25 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
super().__init__(parent=parent, theme_update=True, *arg, **kwargs)
|
||||
self.get_bec_shortcuts()
|
||||
|
||||
self.beamline = self.get_beamline_id()
|
||||
|
||||
# Debugging, override beamline!
|
||||
# self.beamline = BeamlineId.X10DA
|
||||
|
||||
# Check if devices are all in config
|
||||
self.check_config()
|
||||
self.bec_dispatcher.connect_slot(self.check_config, MessageEndpoints.device_config_update())
|
||||
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")
|
||||
|
||||
central = QWidget()
|
||||
self.root_layout = QHBoxLayout(central)
|
||||
|
||||
self.input_widget = QWidget()
|
||||
self.input_layout = QVBoxLayout(self.input_widget)
|
||||
self.input = InputPanel()
|
||||
self.input = InputPanel(self.beamline)
|
||||
self.settings = SettingsPanel()
|
||||
self.input_layout.addWidget(self.input)
|
||||
self.input_layout.addWidget(self.settings)
|
||||
@@ -113,7 +126,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)
|
||||
@@ -144,8 +161,30 @@ 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:
|
||||
raise ValueError(f"Failed to extract beamline from bec server hostname {bec_hostname}")
|
||||
|
||||
@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
|
||||
@@ -161,7 +200,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
"sldi_gapy",
|
||||
"cm_trx",
|
||||
"cm_try",
|
||||
"cm_bnd_radius",
|
||||
"cm_bnd",
|
||||
"cm_rotx",
|
||||
"mo1_bragg",
|
||||
"mo1_trx",
|
||||
@@ -171,18 +210,28 @@ 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:
|
||||
@@ -327,6 +376,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 +398,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 +564,45 @@ 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.set_current_text(table)
|
||||
|
||||
self.calc_assistant(identifier="init")
|
||||
|
||||
def ask_table_selection(self) -> str | None:
|
||||
"""
|
||||
Opens a dialog asking the user to select a table (ES1 or ES2).
|
||||
|
||||
Returns:
|
||||
The selected table ('ES1' or 'ES2'), or None if the user cancelled.
|
||||
"""
|
||||
dialog = QDialog()
|
||||
dialog.setWindowTitle("Select Table")
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
text = QLabel("Select the current table in use.")
|
||||
|
||||
combo = QComboBox()
|
||||
combo.addItems(["ES1", "ES2"])
|
||||
|
||||
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):
|
||||
"""
|
||||
@@ -784,7 +876,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
|
||||
"""
|
||||
|
||||
from typing import Union
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import QLayout, 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,9 +18,14 @@ 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
|
||||
@@ -91,9 +99,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 +154,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 +173,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"])
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user