From a6814eff2bcb668d9e6cd232457558a48156ce61 Mon Sep 17 00:00:00 2001 From: x01da Date: Tue, 26 May 2026 08:57:36 +0200 Subject: [PATCH] wip --- .../widgets/digital_twin/digital_twin.py | 38 ++- .../digital_twin/panels/mover_panel.py | 4 +- .../widgets/digital_twin/x10da_offsets.yaml | 0 .../widgets/digital_twin/x10da_parameters.py | 269 ++++++++++++++++++ 4 files changed, 299 insertions(+), 12 deletions(-) create mode 100644 debye_bec/bec_widgets/widgets/digital_twin/x10da_offsets.yaml create mode 100644 debye_bec/bec_widgets/widgets/digital_twin/x10da_parameters.py diff --git a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py index 00d1959..dcb083b 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py @@ -59,8 +59,6 @@ from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import ComboB logger = bec_logger.logger -OFFSET_FILE = "debye_bec/debye_bec/bec_widgets/widgets/digital_twin/x01da_offsets.yaml" - class DigitalTwin(BECWidget, QWidget): """ @@ -79,6 +77,9 @@ class DigitalTwin(BECWidget, QWidget): # Debugging, override beamline! # self.beamline = BeamlineId.X10DA + self.offset_fie = None + self.set_offset_file() + # Check if devices are all in config self.check_bec_config() self.bec_dispatcher.connect_slot( @@ -183,6 +184,16 @@ class DigitalTwin(BECWidget, QWidget): else: raise ValueError(f"Failed to extract beamline from bec server hostname {bec_hostname}") + def set_offset_file(self): + """ + Depending on the beamline, set the offset file path accordingly. + """ + files: dict[BeamlineId, str] = { + BeamlineId.X01DA: "debye_bec/debye_bec/bec_widgets/widgets/digital_twin/x01da_offsets.yaml", + BeamlineId.X10DA: "superxas_bec/superxas_bec/bec_widgets/widgets/digital_twin/x10da_offsets.yaml", + } + self.offset_file = files[self.beamline] + @SafeSlot() def check_bec_config(self, *args): """ @@ -236,7 +247,7 @@ class DigitalTwin(BECWidget, QWidget): 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() @@ -568,26 +579,33 @@ class DigitalTwin(BECWidget, QWidget): case InputNumberField(): self.input.smpl.set_number(pos["ot_es1_trz"]) case ComboBox(): - table = self.ask_table_selection() + 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) -> str | None: + 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() + dialog = QDialog(self) dialog.setWindowTitle("Select Table") layout = QVBoxLayout(dialog) text = QLabel("Select the current table in use.") combo = QComboBox() - combo.addItems(["ES1", "ES2"]) + 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 @@ -615,9 +633,9 @@ class DigitalTwin(BECWidget, QWidget): if self.offsets == {}: # Load offsets - file = Path(OFFSET_FILE) + file = Path(self.offset_file) if not file.exists(): - raise FileNotFoundError(f"Offset file not found: {OFFSET_FILE}") + raise FileNotFoundError(f"Offset file not found: {self.offset_file}") with file.open("r", encoding="utf-8") as f: data = yaml.safe_load(f) @@ -660,7 +678,7 @@ class DigitalTwin(BECWidget, QWidget): intro_label.setWordWrap(True) layout.addWidget(intro_label) - file = QLabel(OFFSET_FILE) + file = QLabel(self.offset_file) file.setWordWrap(True) font = QFont() font.setItalic(True) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py b/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py index 3f26f06..ec82186 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py @@ -189,7 +189,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 +197,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( diff --git a/debye_bec/bec_widgets/widgets/digital_twin/x10da_offsets.yaml b/debye_bec/bec_widgets/widgets/digital_twin/x10da_offsets.yaml new file mode 100644 index 0000000..e69de29 diff --git a/debye_bec/bec_widgets/widgets/digital_twin/x10da_parameters.py b/debye_bec/bec_widgets/widgets/digital_twin/x10da_parameters.py new file mode 100644 index 0000000..5bd76c3 --- /dev/null +++ b/debye_bec/bec_widgets/widgets/digital_twin/x10da_parameters.py @@ -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])