wip: digital twin
This commit is contained in:
@@ -155,15 +155,15 @@ def calc_positions(cfg):
|
||||
|
||||
# Bender radius
|
||||
if cfg['fm_qy'] is None:
|
||||
radius = 2 * q / np.sin(cfg['fm_pitch']) # ideal bending radius for focused beam
|
||||
radius = 2 * q / np.sin(cfg['fm_rotx']) # ideal bending radius for focused beam
|
||||
else:
|
||||
radius = 2 * cfg['fm_qy'] / np.sin(cfg['fm_pitch']) # ideal bending radius for unfocused beam
|
||||
radius = 2 * cfg['fm_qy'] / np.sin(cfg['fm_rotx']) # ideal bending radius for unfocused beam
|
||||
pos['fm_bnd_radius'] = {'value': radius * 1e-6} # Convert to km
|
||||
|
||||
# Pitch
|
||||
d = bl.fm.center[1] - bl.cm.center[1] - dz
|
||||
fm_pitch = 2 * cfg['cm_pitch'] - cfg['fm_pitch'] # calculate pitch in absolute values (according to horizontal plane)
|
||||
pos['fm_rotx'] = {'value': -fm_pitch * 1e3} # invert and convert to mrad (same as EGU of rotx axis)
|
||||
fm_rotx = 2 * cfg['cm_pitch'] - cfg['fm_rotx'] # calculate pitch in absolute values (according to horizontal plane)
|
||||
pos['fm_rotx'] = {'value': -fm_rotx * 1e3} # invert and convert to mrad (same as EGU of rotx axis)
|
||||
|
||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
||||
|
||||
@@ -205,28 +205,31 @@ def calc_positions(cfg):
|
||||
else:
|
||||
raise Exception('FM Stripe selection not valid')
|
||||
|
||||
pos['fm_roty'] = {'value': 0}
|
||||
pos['fm_rotz'] = {'value': 0}
|
||||
|
||||
## Slits 2
|
||||
d = bl.opSlits2.center[1] - bl.fm.center[1]
|
||||
sl2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_pitch']))
|
||||
sl2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
||||
pos['sl2_centery'] = {'value': sl2_beam_height}
|
||||
pos['sl2_gapy'] = {'value': beam_vs + 1} # Add 0.5 mm space on both sides of the beam
|
||||
|
||||
## Beam Monitor 2
|
||||
d = bl.opBM2.center[1] - bl.fm.center[1]
|
||||
bm2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_pitch']))
|
||||
bm2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
||||
pos['bm2_try'] = {'value': bm2_beam_height}
|
||||
|
||||
## Optical Table
|
||||
|
||||
# TRY
|
||||
d = bl.ehWindow.center[1] - bl.fm.center[1]
|
||||
ot_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_pitch']))
|
||||
ot_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
||||
# logger.info(fm_height)
|
||||
# logger.info(d * np.tan((2 * cfg['cm_pitch'] - 2 * cfg['fm_pitch'])))
|
||||
# logger.info(d * np.tan((2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx'])))
|
||||
pos['ot_try'] = {'value': ot_height}
|
||||
|
||||
# Pitch
|
||||
ot_pitch = - (2 * cfg['cm_pitch'] - 2 * cfg['fm_pitch'])
|
||||
ot_pitch = - (2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx'])
|
||||
pos['ot_rotx'] = {'value': ot_pitch * 1e3}
|
||||
|
||||
# TRZ ES1
|
||||
|
||||
@@ -25,12 +25,12 @@ def calc_sideview(cfg):
|
||||
beam['x'].append(bl.fm.center[1]) # FM
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy)
|
||||
beam['x'].append(cfg['smpl']) # Experiment
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy+np.tan(2*(cfg['cm_pitch']-cfg['fm_pitch']))*(cfg['smpl']-bl.fm.center[1]))
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy+np.tan(2*(cfg['cm_pitch']-cfg['fm_rotx']))*(cfg['smpl']-bl.fm.center[1]))
|
||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
beam['x'].append(bl.fm.center[1]) # FM
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]))
|
||||
beam['x'].append(cfg['smpl']) # Experiment
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1])+np.tan(2*(cfg['cm_pitch']-cfg['fm_pitch']))*(cfg['smpl']-bl.fm.center[1]))
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1])+np.tan(2*(cfg['cm_pitch']-cfg['fm_rotx']))*(cfg['smpl']-bl.fm.center[1]))
|
||||
|
||||
dy_fm_ex = beam['y'][-1] - beam['y'][-2]
|
||||
dz_fm_ex = beam['x'][-1] - beam['x'][-2]
|
||||
|
||||
@@ -81,16 +81,16 @@ def calc_surfaces(cfg):
|
||||
|
||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
||||
|
||||
l = heightBeam/np.sin(cfg['fm_pitch'])
|
||||
l = heightBeam/np.sin(cfg['fm_rotx'])
|
||||
alpha = np.arccos(1-widthBeam**2/(2*r**2))
|
||||
h = r-(r*np.cos(alpha/2))
|
||||
z = h/np.tan(cfg['fm_pitch'])
|
||||
z = h/np.tan(cfg['fm_rotx'])
|
||||
|
||||
x = [off-widthBeam/2, off-widthBeam/2]
|
||||
y = [l/2-z/2, -l/2-z/2]
|
||||
|
||||
# logger.info(f'stripe: {cfg["fm_stripe"]}')
|
||||
# logger.info(f'fm_pitch: {cfg["fm_pitch"]}')
|
||||
# logger.info(f'fm_rotx: {cfg["fm_rotx"]}')
|
||||
# logger.info(f'h: {h}')
|
||||
# logger.info(f'z: {z}')
|
||||
# logger.info(f'r: {r}')
|
||||
@@ -120,7 +120,7 @@ def calc_surfaces(cfg):
|
||||
out['fm']['y'] = y
|
||||
|
||||
else: # flat surface, no toroid
|
||||
l = heightBeam/np.sin(cfg['fm_pitch'])
|
||||
l = heightBeam/np.sin(cfg['fm_rotx'])
|
||||
|
||||
w1 = 2 * (bl.fm.center[1]-l/2) * np.tan(cfg['h_acc'])
|
||||
w2 = 2 * (bl.fm.center[1]+l/2) * np.tan(cfg['h_acc'])
|
||||
|
||||
@@ -4,6 +4,8 @@ Digital Twin: Custom BEC widget to support the beamline alignment.
|
||||
|
||||
import sys
|
||||
import numpy as np
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from bec_lib import bec_logger
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
|
||||
@@ -56,10 +58,13 @@ from debye_bec.bec_widgets.widgets.digital_twin.calc_varia import (
|
||||
wall_geometries,
|
||||
pipe_geometries,
|
||||
)
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.offset_settings import OffsetSettings
|
||||
from debye_bec.devices.absorber import STATUS as ABS_STATUS
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
OFFSET_FILE = "debye_bec/debye_bec/bec_widgets/widgets/x01da_offsets.yaml"
|
||||
|
||||
class DigitalTwin(BECWidget, QWidget):
|
||||
"""
|
||||
Main widget of Digital Twin
|
||||
@@ -74,21 +79,28 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
|
||||
central = QWidget()
|
||||
self.root_layout = QHBoxLayout(central)
|
||||
|
||||
self.input_widget = QWidget()
|
||||
self.input_layout = QVBoxLayout(self.input_widget)
|
||||
self.input = InputPanel()
|
||||
self.settings = SettingsPanel()
|
||||
self.input_layout.addWidget(self.input) # type: ignore
|
||||
self.input_layout.addWidget(self.settings) # type: ignore
|
||||
|
||||
self.plot_widget = QWidget()
|
||||
self.plot_layout = QVBoxLayout(self.plot_widget)
|
||||
|
||||
self.input = InputPanel()
|
||||
self.sideview_plot = SideviewPlot()
|
||||
self.surface_plots = SurfacePlots()
|
||||
self.plot_layout.addWidget(self.sideview_plot) # type: ignore
|
||||
self.plot_layout.addWidget(self.surface_plots) # type: ignore
|
||||
|
||||
self.positions = PositionsPanel()
|
||||
self.mover = MoverPanel(self.dev)
|
||||
|
||||
self.root_layout.addWidget(self.input, stretch=1, alignment=Qt.AlignTop) # type: ignore
|
||||
self.plot_layout.addWidget(self.sideview_plot) # type: ignore
|
||||
self.plot_layout.addWidget(self.surface_plots) # type: ignore
|
||||
self.root_layout.addWidget(self.plot_widget, stretch=1, alignment=Qt.AlignTop) # type: ignore
|
||||
# self.root_layout.addWidget(self.positions, stretch=1, alignment=Qt.AlignTop) # type: ignore
|
||||
self.root_layout.addWidget(self.mover, stretch=1, alignment=Qt.AlignTop)
|
||||
self.root_layout.addWidget(self.input_widget, alignment=Qt.AlignTop) # type: ignore
|
||||
self.root_layout.addWidget(self.plot_widget, alignment=Qt.AlignTop) # type: ignore
|
||||
# self.root_layout.addWidget(self.positions, alignment=Qt.AlignTop) # type: ignore
|
||||
self.root_layout.addWidget(self.mover, alignment=Qt.AlignTop)
|
||||
|
||||
self.setLayout(self.root_layout)
|
||||
self.setWindowTitle("Digital Twin")
|
||||
@@ -103,17 +115,21 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.input.mo1_xtal.activated_connect(self.calc_assistant)
|
||||
self.input.fm_stripe.activated_connect(self.calc_assistant)
|
||||
self.input.fm_focus.activated_connect(self.calc_assistant)
|
||||
self.input.fm_pitch.value_changed_connect(self.calc_assistant)
|
||||
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)
|
||||
|
||||
self.input.adapt_reality.clicked_connect(self.adapt_reality)
|
||||
self.settings.reload_offsets.clicked_connect(self.load_offsets)
|
||||
self.settings.unload_offsets.clicked_connect(self.unload_offsets)
|
||||
|
||||
self.bragg_angle = 0
|
||||
self.qy = 0
|
||||
self.offsets = {}
|
||||
|
||||
# Initialize all values
|
||||
self.load_offsets(recalculate=False)
|
||||
self.calc_assistant(identifier='init')
|
||||
|
||||
# Timer: update plot every 1 second
|
||||
@@ -181,13 +197,13 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
|
||||
fm_focus = self.input.fm_focus.currentText()
|
||||
if fm_focus in 'Manual':
|
||||
fm_pitch = self.input.fm_pitch.value()
|
||||
fm_rotx = self.input.fm_rotx.value()
|
||||
fm_qy = None
|
||||
elif fm_focus in 'Focused':
|
||||
fm_pitch = self.input.fm_pitch_ideal.value()
|
||||
fm_rotx = self.input.fm_rotx_ideal.value()
|
||||
fm_qy = None
|
||||
else: # Focused
|
||||
fm_pitch = self.input.fm_pitch_ideal.value()
|
||||
fm_rotx = self.input.fm_rotx_ideal.value()
|
||||
fm_qy = self.qy
|
||||
|
||||
config = { # Config in SI units!
|
||||
@@ -200,7 +216,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
'mo1_mode' : self.input.mo1_mode.currentText(),
|
||||
'mo1_xtal' : self.input.mo1_xtal.currentText(),
|
||||
'mo1_bragg' : self.bragg_angle,
|
||||
'fm_pitch' : -fm_pitch * 1e-3,
|
||||
'fm_rotx' : -fm_rotx * 1e-3,
|
||||
'fm_stripe' : self.input.fm_stripe.currentText(),
|
||||
'fm_trx' : None,
|
||||
'fm_qy' : fm_qy,
|
||||
@@ -225,8 +241,8 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
cm_pitch = self.dev.cm_rotx.read(cached=True)['cm_rotx']['value']
|
||||
fm_trx = self.dev.fm_trx.read(cached=True)['fm_trx']['value']
|
||||
fm_stripe = fm_trx_to_stripe(-fm_trx)
|
||||
fm_pitch = self.dev.fm_rotx.read(cached=True)['fm_rotx']['value']
|
||||
fm_pitch_real = 2 * cm_pitch - fm_pitch
|
||||
fm_rotx = self.dev.fm_rotx.read(cached=True)['fm_rotx']['value']
|
||||
fm_rotx_real = 2 * cm_pitch - fm_rotx
|
||||
smpl = self.dev.ot_es1_trz.read(cached=True)['ot_es1_trz']['value']
|
||||
config = { # Config in SI units!
|
||||
'energy' : mo1_bragg['mo1_bragg']['value'],
|
||||
@@ -238,7 +254,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
'mo1_mode' : mo1_mode,
|
||||
'mo1_xtal' : mo1_bragg['mo1_bragg_crystal_current_xtal_string']['value'],
|
||||
'mo1_bragg' : mo1_bragg['mo1_bragg_angle']['value']/180*np.pi,
|
||||
'fm_pitch' : -fm_pitch_real * 1e-3,
|
||||
'fm_rotx' : -fm_rotx_real * 1e-3,
|
||||
'fm_stripe' : fm_stripe,
|
||||
'fm_trx' : -fm_trx,
|
||||
'fm_gain_height' : 1,
|
||||
@@ -253,11 +269,11 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
if mover.status in ('moving', 'error'):
|
||||
ready = False
|
||||
if ready:
|
||||
self.mover.abs.enable_open(1) # Enable open button
|
||||
self.mover.abs.enable_open(True) # Enable open button
|
||||
else:
|
||||
self.mover.abs.enable_open(0) # Disable open button
|
||||
self.mover.abs.enable_open(False) # Disable open button
|
||||
else:
|
||||
self.mover.abs.enable_open(0) # Disable open button
|
||||
self.mover.abs.enable_open(False) # Disable open button
|
||||
|
||||
self.mover.sldi_gapx.set_feedback(sldi_gapx)
|
||||
self.mover.sldi_gapy.set_feedback(sldi_gapy)
|
||||
@@ -274,7 +290,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.mover.fm_trx.set_feedback(fm_trx)
|
||||
self.mover.fm_try.set_feedback(self.dev.fm_try.read(cached=True)['fm_try']['value'])
|
||||
self.mover.fm_bnd.set_feedback(self.dev.fm_bnd_radius.read(cached=True)['fm_bnd_radius']['value'])
|
||||
self.mover.fm_rotx.set_feedback(fm_pitch)
|
||||
self.mover.fm_rotx.set_feedback(fm_rotx)
|
||||
self.mover.fm_roty.set_feedback(self.dev.fm_roty.read(cached=True)['fm_roty']['value'])
|
||||
self.mover.fm_rotz.set_feedback(self.dev.fm_rotz.read(cached=True)['fm_rotz']['value'])
|
||||
self.mover.sl2_centery.set_feedback(self.dev.sl2_centery.read(cached=True)['sl2_centery']['value'])
|
||||
@@ -288,20 +304,40 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
return config
|
||||
|
||||
def adapt_reality(self, *args):
|
||||
pos = {}
|
||||
pos['sldi_gapx'] = self.dev.sldi_gapx.read(cached=True)['sldi_gapx']['value']
|
||||
pos['sldi_gapy'] = self.dev.sldi_gapy.read(cached=True)['sldi_gapy']['value']
|
||||
pos['cm_trx'] = self.dev.cm_trx.read(cached=True)['cm_trx']['value']
|
||||
pos['cm_rotx'] = self.dev.cm_rotx.read(cached=True)['cm_rotx']['value']
|
||||
pos['mo1_trx'] = self.dev.mo1_trx.read(cached=True)['mo1_trx']['value']
|
||||
pos['fm_trx'] = self.dev.fm_trx.read(cached=True)['fm_trx']['value']
|
||||
pos['fm_rotx'] = self.dev.fm_rotx.read(cached=True)['fm_rotx']['value']
|
||||
pos['ot_es1_trz'] = self.dev.ot_es1_trz.read(cached=True)['ot_es1_trz']['value']
|
||||
|
||||
# Removing offsets
|
||||
for axis, value in pos.items():
|
||||
if axis in self.offsets:
|
||||
axis_offsets = self.offsets[axis]
|
||||
if 'modifier' in axis_offsets and 'offset' in axis_offsets:
|
||||
for idx, rng in enumerate(axis_offsets['modifier']['range']):
|
||||
if rng[0] < pos[axis_offsets['modifier']['axis']] < rng[1]:
|
||||
pos[axis] -= axis_offsets['offset'][idx]
|
||||
break
|
||||
elif 'offset' in axis_offsets:
|
||||
pos[axis] -= axis_offsets['offset']
|
||||
|
||||
self.input.energy.set_number(self.dev.mo1_bragg.read(cached=True)['mo1_bragg']['value'])
|
||||
h_acc, v_acc = sldi_gap_to_acc(
|
||||
self.dev.sldi_gapx.read(cached=True)['sldi_gapx']['value'],
|
||||
self.dev.sldi_gapy.read(cached=True)['sldi_gapy']['value']
|
||||
pos['sldi_gapx'],
|
||||
pos['sldi_gapy']
|
||||
)
|
||||
self.input.sldi_hacc.set_number(h_acc*1e3)
|
||||
self.input.sldi_vacc.set_number(v_acc*1e3)
|
||||
self.input.cm_stripe.set_current_text(
|
||||
cm_trx_to_stripe(-self.dev.cm_trx.read(cached=True)['cm_trx']['value'])
|
||||
cm_trx_to_stripe(-pos['cm_trx'])
|
||||
)
|
||||
cm_pitch = self.dev.cm_rotx.read(cached=True)['cm_rotx']['value']
|
||||
self.input.cm_pitch.set_number(cm_pitch)
|
||||
mo1_trx = self.dev.mo1_trx.read(cached=True)['mo1_trx']['value']
|
||||
if abs(mo1_trx) > 5:
|
||||
self.input.cm_pitch.set_number(pos['cm_rotx'])
|
||||
if abs(pos['mo1_trx']) > 5:
|
||||
mo1_mode = 'Monochromatic'
|
||||
else:
|
||||
mo1_mode = 'Pinkbeam'
|
||||
@@ -310,37 +346,56 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.dev.mo1_bragg.read(cached=True)['mo1_bragg_crystal_current_xtal_string']['value']
|
||||
)
|
||||
self.input.fm_stripe.set_current_text(
|
||||
fm_trx_to_stripe(-self.dev.fm_trx.read(cached=True)['fm_trx']['value'])
|
||||
fm_trx_to_stripe(-pos['fm_trx'])
|
||||
)
|
||||
self.input.fm_focus.set_current_text('Manual')
|
||||
fm_pitch = self.dev.fm_rotx.read(cached=True)['fm_rotx']['value']
|
||||
fm_pitch_real = 2 * cm_pitch - fm_pitch
|
||||
self.input.fm_pitch.set_number(fm_pitch_real)
|
||||
fm_rotx_real = 2 * pos['cm_rotx'] - pos['fm_rotx']
|
||||
self.input.fm_rotx.set_number(fm_rotx_real)
|
||||
self.input.smpl.set_number(
|
||||
self.dev.ot_es1_trz.read(cached=True)['ot_es1_trz']['value']
|
||||
pos['ot_es1_trz']
|
||||
)
|
||||
self.calc_assistant(identifier='init')
|
||||
|
||||
def load_offsets(self, recalculate=True, *args):
|
||||
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:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError(f"Expected a YAML mapping, got {type(data).__name__}")
|
||||
|
||||
self.offsets = data
|
||||
|
||||
if recalculate:
|
||||
self.calc_assistant(identifier='init')
|
||||
|
||||
def unload_offsets(self, *args):
|
||||
self.offsets = {}
|
||||
self.calc_assistant(identifier='init')
|
||||
|
||||
def update_fm_mode(self):
|
||||
fm_focus = self.input.fm_focus.currentText()
|
||||
if fm_focus in 'Manual':
|
||||
self.input.fm_pitch.setVisible(True)
|
||||
self.input.fm_pitch_ideal.setVisible(True)
|
||||
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_pitch_ideal.setLabel('Incidence Angle for focused beam')
|
||||
self.input.fm_rotx_ideal.setLabel('Incidence Angle for focused beam')
|
||||
elif fm_focus in 'Focused':
|
||||
self.input.fm_pitch.setVisible(False)
|
||||
self.input.fm_pitch_ideal.setVisible(True)
|
||||
self.input.fm_rotx.setVisible(False)
|
||||
self.input.fm_rotx_ideal.setVisible(True)
|
||||
self.input.fm_focx.setVisible(False)
|
||||
self.input.fm_focy.setVisible(False)
|
||||
self.input.fm_pitch_ideal.setLabel('Incidence Angle for focused beam')
|
||||
self.input.fm_rotx_ideal.setLabel('Incidence Angle for focused beam')
|
||||
else: # Defocused
|
||||
self.input.fm_pitch.setVisible(False)
|
||||
self.input.fm_pitch_ideal.setVisible(True)
|
||||
self.input.fm_rotx.setVisible(False)
|
||||
self.input.fm_rotx_ideal.setVisible(True)
|
||||
self.input.fm_focx.setVisible(True)
|
||||
self.input.fm_focy.setVisible(True)
|
||||
self.input.fm_pitch_ideal.setLabel('Incidence Angle for defocused beam')
|
||||
self.input.fm_rotx_ideal.setLabel('Incidence Angle for defocused beam')
|
||||
|
||||
def calc_reality(self):
|
||||
config = self.get_reality_config()
|
||||
@@ -369,13 +424,13 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
fm_stripe = self.input.fm_stripe.currentText()
|
||||
fm_focus = self.input.fm_focus.currentText()
|
||||
if fm_focus in 'Manual':
|
||||
fm_pitch = -self.input.fm_pitch.value() * 1e-3
|
||||
fm_rotx = -self.input.fm_rotx.value() * 1e-3
|
||||
else:
|
||||
fm_pitch = -self.input.fm_pitch_ideal.value() * 1e-3
|
||||
fm_rotx = -self.input.fm_rotx_ideal.value() * 1e-3
|
||||
energy = self.input.energy.value()
|
||||
self.input.fm_refl.setValue(100 * fm_reflectivity(fm_stripe, fm_pitch, energy))
|
||||
self.input.fm_refl.setValue(100 * fm_reflectivity(fm_stripe, fm_rotx, energy))
|
||||
self.input.fm_refl.setLabel(f"Reflectivity at \n{energy:.0f} eV")
|
||||
self.input.fm_refl_harm.setValue(100 * fm_reflectivity(fm_stripe, fm_pitch, 3*energy))
|
||||
self.input.fm_refl_harm.setValue(100 * fm_reflectivity(fm_stripe, fm_rotx, 3*energy))
|
||||
self.input.fm_refl_harm.setLabel(f"Reflectivity at \n{3*energy:.0f} eV")
|
||||
|
||||
def calc_cm_fm_harm_suppr(self):
|
||||
@@ -395,6 +450,19 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
|
||||
def calc_positions(self):
|
||||
out = calc_positions(self.get_assistant_config())
|
||||
|
||||
# Apply offsets
|
||||
for axis, axis_data in out.items():
|
||||
if axis in self.offsets:
|
||||
axis_offsets = self.offsets[axis]
|
||||
if 'modifier' in axis_offsets and 'offset' in axis_offsets:
|
||||
for idx, rng in enumerate(axis_offsets['modifier']['range']):
|
||||
if rng[0] < out[axis_offsets['modifier']['axis']]['value'] < rng[1]:
|
||||
axis_data['value'] += axis_offsets['offset'][idx]
|
||||
break
|
||||
elif 'offset' in axis_offsets:
|
||||
axis_data['value'] += axis_offsets['offset']
|
||||
|
||||
self.positions.sldi_gapx.setValue(out['sldi_gapx']['value'])
|
||||
self.positions.sldi_gapy.setValue(out['sldi_gapy']['value'])
|
||||
self.positions.cm_trx.setValue(out['cm_trx']['value'])
|
||||
@@ -432,6 +500,8 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.mover.fm_try.set_target(out['fm_try']['value'])
|
||||
self.mover.fm_bnd.set_target(out['fm_bnd_radius']['value'])
|
||||
self.mover.fm_rotx.set_target(out['fm_rotx']['value'])
|
||||
self.mover.fm_roty.set_target(out['fm_roty']['value'])
|
||||
self.mover.fm_rotz.set_target(out['fm_rotz']['value'])
|
||||
self.mover.sl2_centery.set_target(out['sl2_centery']['value'])
|
||||
self.mover.sl2_gapy.set_target(out['sl2_gapy']['value'])
|
||||
self.mover.bm2_try.set_target(out['bm2_try']['value'])
|
||||
@@ -476,9 +546,9 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
sldi_vacc = self.input.sldi_vacc.value() * 1e-3
|
||||
fm_focx = self.input.fm_focx.value()
|
||||
fm_focy = self.input.fm_focy.value()
|
||||
fm_pitch, qy = fm_ideal_pitch(fm_focus, fm_stripe, smpl, sldi_hacc, sldi_vacc, fm_focx, fm_focy)
|
||||
fm_rotx, qy = fm_ideal_pitch(fm_focus, fm_stripe, smpl, sldi_hacc, sldi_vacc, fm_focx, fm_focy)
|
||||
self.qy = qy
|
||||
self.input.fm_pitch_ideal.setValue(-fm_pitch * 1e3)
|
||||
self.input.fm_rotx_ideal.setValue(-fm_rotx * 1e3)
|
||||
|
||||
def calc_cm_crit_pitch(self):
|
||||
cm_stripe = self.input.cm_stripe.currentText()
|
||||
@@ -545,10 +615,10 @@ class InputPanel(QWidget):
|
||||
# Focusing Mirror
|
||||
self.fm_stripe = ComboBox('fm_stripe', 'Stripe', ['Rh (toroid)', 'Rh (flat)', 'Pt (toroid)', 'Pt (flat)'])
|
||||
self.fm_focus = ComboBox('fm_focus', 'Focus Type', ['Manual', 'Focused', 'Defocused'])
|
||||
self.fm_pitch = InputNumberField('fm_pitch', 'Incidence Angle', unit='mrad', init=-2.391, decimals=3, single_step=0.01, ll=-10, hl=2)
|
||||
self.fm_rotx = InputNumberField('fm_rotx', 'Incidence Angle', unit='mrad', init=-2.391, decimals=3, single_step=0.01, ll=-10, hl=2)
|
||||
self.fm_focx = InputNumberField('fm_focx', 'Beam Size Horizontal', unit='mm', init=1, decimals=1, single_step=0.1, ll=0, hl=30)
|
||||
self.fm_focy = InputNumberField('fm_focy', 'Beam Size Vertical', unit='mm', init=1, decimals=1, single_step=0.1, ll=0, hl=10)
|
||||
self.fm_pitch_ideal = NumberIndicator('Incidence Angle for focused beam', 'mrad', decimals=3)
|
||||
self.fm_rotx_ideal = NumberIndicator('Incidence Angle for focused beam', 'mrad', decimals=3)
|
||||
self.fm_refl = NumberIndicator('Reflectivity at x eV', '%', decimals=0)
|
||||
self.fm_refl_harm = NumberIndicator('Reflectivity at x eV', '%', decimals=0)
|
||||
self.fm_ass_group = Group(
|
||||
@@ -556,10 +626,10 @@ class InputPanel(QWidget):
|
||||
[
|
||||
self.fm_stripe,
|
||||
self.fm_focus,
|
||||
self.fm_pitch,
|
||||
self.fm_rotx,
|
||||
self.fm_focx,
|
||||
self.fm_focy,
|
||||
self.fm_pitch_ideal,
|
||||
self.fm_rotx_ideal,
|
||||
self.fm_refl,
|
||||
self.fm_refl_harm,
|
||||
]
|
||||
@@ -587,6 +657,30 @@ class InputPanel(QWidget):
|
||||
self._layout .addWidget(self.input_group)
|
||||
self._layout .addStretch()
|
||||
|
||||
class SettingsPanel(QWidget):
|
||||
"""Right-side control panel: input field, indicator, send, recording."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._layout = QVBoxLayout(self)
|
||||
self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
|
||||
|
||||
# Reload offsets
|
||||
self.reload_offsets = Button(label='Reload Offsets', label_button='Reload', enabled=True)
|
||||
self.unload_offsets = Button(label='Unload Offsets', label_button='Unload', enabled=True)
|
||||
|
||||
# Assemble complete offset group
|
||||
self.offset_group = Group(
|
||||
'Axes Offsets',
|
||||
[
|
||||
self.reload_offsets,
|
||||
self.unload_offsets,
|
||||
]
|
||||
)
|
||||
|
||||
self._layout .addWidget(self.offset_group)
|
||||
self._layout .addStretch()
|
||||
|
||||
class PositionsPanel(QWidget):
|
||||
"""Right-side control panel: input field, indicator, send, recording."""
|
||||
|
||||
|
||||
@@ -104,25 +104,25 @@ class MotionWorker(QObject):
|
||||
def stop(self):
|
||||
self._stop_flag.set()
|
||||
|
||||
# def run(self):
|
||||
# logger.info(f'Would run motor {self.motor}')
|
||||
# simulated_run_time = 3
|
||||
# start = time.time()
|
||||
# while (time.time() - start) < simulated_run_time:
|
||||
# if self._stop_flag.is_set():
|
||||
# break
|
||||
# time.sleep(0.01)
|
||||
|
||||
# # self.motor.move(self._target, relative=False)
|
||||
# # while self.motor.motor_is_moving.get():
|
||||
# # if self._stop_flag.is_set():
|
||||
# # self.motor.motor_stop()
|
||||
# # self.position_changed.emit(self.motor.read[self.name]['value'])
|
||||
# # time.sleep(0.1)
|
||||
# self.finished.emit(True)
|
||||
|
||||
def run(self):
|
||||
logger.info(f'Would run motor {self.motor}')
|
||||
simulated_run_time = 3
|
||||
start = time.time()
|
||||
while (time.time() - start) < simulated_run_time:
|
||||
if self._stop_flag.is_set():
|
||||
break
|
||||
time.sleep(0.01)
|
||||
|
||||
# self.motor.move(self._target, relative=False)
|
||||
# while self.motor.motor_is_moving.get():
|
||||
# if self._stop_flag.is_set():
|
||||
# self.motor.motor_stop()
|
||||
# self.position_changed.emit(self.motor.read[self.name]['value'])
|
||||
# time.sleep(0.1)
|
||||
self.finished.emit(True)
|
||||
|
||||
def run2(self):
|
||||
match self.name:
|
||||
match self.motor:
|
||||
case 'sldi_gapx' | 'sldi_gapy' | 'sldi_centerx' | 'sldi_centery':
|
||||
self.motion()
|
||||
case 'cm_trx':
|
||||
@@ -149,7 +149,7 @@ class MotionWorker(QObject):
|
||||
{'device': self.dev['cm_rotx'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'cm_bnd':
|
||||
p1 = (1/(self.dev.cm_bnd_radius.read['cm_bnd_radius']['value']*1e3) + 0.0284)/2e-6
|
||||
p1 = (1/(self.dev.cm_bnd_radius.read()['cm_bnd_radius']['value']*1e3) + 0.0284)/2e-6
|
||||
p2 = (1/(self._target*1e3) + 0.0284)/2e-6
|
||||
self._target = p2 - p1
|
||||
self.motion(relative=True, rb=
|
||||
@@ -185,7 +185,7 @@ class MotionWorker(QObject):
|
||||
{'device': self.dev['fm_rotx'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'fm_bnd':
|
||||
p1 = (1/(self.dev.fm_bnd_radius.read['fm_bnd_radius']['value']*1e3) + 4.28e-5)/1.84e-9
|
||||
p1 = (1/(self.dev.fm_bnd_radius.read()['fm_bnd_radius']['value']*1e3) + 4.28e-5)/1.84e-9
|
||||
p2 = (1/(self._target*1e3) + 4.28e-5)/1.84e-9
|
||||
self._target = p2 - p1
|
||||
self.motion(relative=True, rb=
|
||||
@@ -216,22 +216,24 @@ class MotionWorker(QObject):
|
||||
if surveyed_axes is not None:
|
||||
for surv_ax in surveyed_axes:
|
||||
surv_ax['name'] = surv_ax['device'].dotted_name
|
||||
surv_ax['old_value'] = surv_ax['device'].read()[surv_ax['name']]['value']
|
||||
surv_ax['old_value'] = surv_ax['device'].read(cached=True)[surv_ax['name']]['value']
|
||||
if rb is not None:
|
||||
rb['name'] = rb['device'].dotted_name
|
||||
self.dev[self.motor].move(self._target, relative=relative)
|
||||
time.sleep(0.5)
|
||||
while self.dev[self.motor].motor_is_moving.get():
|
||||
if self._stop_flag.is_set():
|
||||
self.dev[self.motor].motor_stop()
|
||||
self.dev[self.motor].stop()
|
||||
self._stop_flag.clear()
|
||||
if rb is not None:
|
||||
self.position_changed.emit(rb['device'].read()[rb['name']]['value'])
|
||||
self.position_changed.emit(rb['device'].read(cached=True)[rb['name']]['value'])
|
||||
else:
|
||||
self.position_changed.emit(self.dev[self.motor].read[self.motor]['value'])
|
||||
self.position_changed.emit(self.dev[self.motor].read(cached=True)[self.motor]['value'])
|
||||
if surveyed_axes is not None:
|
||||
for surv_ax in surveyed_axes:
|
||||
fb = surv_ax['device'].read()[surv_ax['name']]['value']
|
||||
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].motor_stop()
|
||||
self.dev[self.motor].stop()
|
||||
self.error.emit(1)
|
||||
break
|
||||
time.sleep(0.1)
|
||||
@@ -333,12 +335,12 @@ class MoveWidget(QWidget):
|
||||
|
||||
def _apply_button_style(self, mode: str):
|
||||
if mode == "start":
|
||||
self.btn_action.setText("▶ Move")
|
||||
self.btn_action.setText("Move")
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
)
|
||||
else: # stop
|
||||
self.btn_action.setText("■ Stop")
|
||||
self.btn_action.setText("Stop")
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().emergency.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import sys
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QApplication,
|
||||
QLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QDialog,
|
||||
)
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtCore import (
|
||||
Qt,
|
||||
QTimer,
|
||||
)
|
||||
from qtpy.QtGui import (
|
||||
QColor,
|
||||
QBrush,
|
||||
QCloseEvent,
|
||||
)
|
||||
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.error_popups import SafeSlot
|
||||
|
||||
from debye_bec.bec_widgets.widgets.qt_widgets import (
|
||||
InputNumberField,
|
||||
ComboBox,
|
||||
Group,
|
||||
NumberIndicator,
|
||||
Button,
|
||||
)
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
class OffsetSettings(QDialog):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Axes Offset Settings")
|
||||
self.setMinimumSize(300, 150)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
label = QLabel("👋 Hello from the secondary window!")
|
||||
label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
close_btn = QPushButton("Close")
|
||||
close_btn.clicked.connect(self.close)
|
||||
|
||||
layout.addWidget(label)
|
||||
layout.addWidget(close_btn)
|
||||
self.setLayout(layout)
|
||||
@@ -0,0 +1,50 @@
|
||||
cm_try:
|
||||
offset: 0.15
|
||||
|
||||
mo1_trx:
|
||||
modifier:
|
||||
axis: mo1_trx
|
||||
range: [[-30, -0.1], [0.1, 30]]
|
||||
offset: [0, 2.21]
|
||||
|
||||
mo1_try:
|
||||
modifier:
|
||||
axis: mo1_trx
|
||||
range: [[-30, -0.1], [0.1, 30]]
|
||||
offset: [0, -1.6]
|
||||
|
||||
sl1_centery:
|
||||
offset: -1.8
|
||||
|
||||
fm_trx:
|
||||
modifier:
|
||||
axis: fm_trx
|
||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
||||
offset: [0, 0, 0, -0.16]
|
||||
|
||||
fm_try:
|
||||
modifier:
|
||||
axis: fm_trx
|
||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
||||
offset: [0, 0, 0, -0.45]
|
||||
|
||||
fm_rotx:
|
||||
modifier:
|
||||
axis: fm_trx
|
||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
||||
offset: [0, 0, 0, 0.063]
|
||||
|
||||
fm_roty:
|
||||
modifier:
|
||||
axis: fm_trx
|
||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
||||
offset: [0, 0, 0, -0.04]
|
||||
|
||||
sl2_centery:
|
||||
offset: 1.2
|
||||
|
||||
ot_try:
|
||||
offset: 0
|
||||
|
||||
ot_rotx:
|
||||
offset: 0
|
||||
Reference in New Issue
Block a user