wip: digital twin
This commit is contained in:
@@ -17,19 +17,20 @@ def calc_positions(cfg):
|
||||
tryb = -np.arctan(cfg['v_acc'])*bl.feSlits.center1[1]
|
||||
tryt = (np.arctan(cfg['v_acc'])*bl.feSlits.center1[1])/bl.feSlits.center1[1]*bl.feSlits.center2[1]
|
||||
|
||||
trxw_proj = trxw/bl.feSlits.center2[1]*bl.feSlits.center1[1]
|
||||
tryt_proj = tryt/bl.feSlits.center2[1]*bl.feSlits.center1[1]
|
||||
# trxw_proj = trxw/bl.feSlits.center2[1]*bl.feSlits.center1[1]
|
||||
# tryt_proj = tryt/bl.feSlits.center2[1]*bl.feSlits.center1[1]
|
||||
|
||||
xcen = (trxr + trxw_proj) / 2
|
||||
ycen = (tryb + tryt_proj) / 2
|
||||
xgap = trxw_proj - trxr
|
||||
ygap = tryt_proj - tryb
|
||||
# xcen = (trxr + trxw) / 2
|
||||
# ycen = (tryb + tryt) / 2
|
||||
xgap = trxw - trxr
|
||||
ygap = tryt - tryb
|
||||
|
||||
pos['sldi_gapx'] = {'value': xgap}
|
||||
pos['sldi_gapy'] = {'value': ygap}
|
||||
|
||||
## Collimating Mirror
|
||||
obj_dist = bl.cm.center[1] # object distance
|
||||
beam_vs = 2 * obj_dist * np.tan(cfg['v_acc']) # vertical size of beam after CM
|
||||
|
||||
# TRX
|
||||
try:
|
||||
@@ -52,17 +53,23 @@ def calc_positions(cfg):
|
||||
|
||||
## Monochromator
|
||||
# Bragg Angle
|
||||
# TODO Should the bragg angle be corrected for the symmetric bragg case?
|
||||
# See raytracing script or here: bragg = np.asin(rm.ch / (2.*cfg['dSpacing']*cfg['energyCCM'])) - aCrystal.get_dtheta_symmetric_Bragg(cfg['energyCCM'])
|
||||
# if cfg['mo1_mode'] == 'Monochromatic':
|
||||
# # Add 2x CM pitch to the bragg angle
|
||||
# bragg = ((2 * cfg['cm_pitch']) + cfg['mo1_bragg']) / np.pi * 180
|
||||
# elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
# # Align xtal surfaces parallel to beam
|
||||
# bragg = (2 * cfg['cm_pitch']) / np.pi * 180
|
||||
# else:
|
||||
# raise Exception('Monochromator mode not supported')
|
||||
if cfg['mo1_mode'] == 'Monochromatic':
|
||||
# Add 2x CM pitch to the bragg angle
|
||||
bragg = ((2 * cfg['cm_pitch']) + cfg['mo1_bragg']) / np.pi * 180
|
||||
bragg = cfg['mo1_bragg']
|
||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
# Align xtal surfaces parallel to beam
|
||||
bragg = (2 * cfg['cm_pitch']) / np.pi * 180
|
||||
bragg = 0
|
||||
else:
|
||||
raise Exception('Monochromator mode not supported')
|
||||
pos['mo1_bragg_angle'] = {'value': bragg} # Bragg angle in deg
|
||||
pos['mo1_bragg_angle'] = {'value': bragg/np.pi*180} # Bragg angle in deg
|
||||
|
||||
# TRY, Height
|
||||
l = bl.mo1.xtalGap[0]/np.sin(cfg['mo1_bragg'])
|
||||
@@ -124,13 +131,14 @@ def calc_positions(cfg):
|
||||
|
||||
|
||||
#TODO move to mono, calc for beam Z-movement between crystal surfaces
|
||||
diag = bl.mo1.xtalGap[0] / np.sin(bragg) # Calculations for Mono
|
||||
dz = diag * np.cos(2 * (cfg['cm_pitch'] + bragg))
|
||||
diag = bl.mo1.xtalGap[0] / np.sin(cfg['mo1_bragg']) # Calculations for Mono
|
||||
dz = diag * np.cos(2 * (cfg['cm_pitch'] + cfg['mo1_bragg']))
|
||||
|
||||
## Slits 1
|
||||
d = bl.opSlits1.center[1] - bl.cm.center[1] - dz
|
||||
sl1_beam_height = d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM
|
||||
pos['sl1_centery'] = {'value': sl1_beam_height}
|
||||
pos['sl1_gapy'] = {'value': beam_vs + 1} # Add 0.5 mm space on both sides of the beam
|
||||
|
||||
## Beam Monitor 1
|
||||
d = bl.opBM1.center[1] - bl.cm.center[1] - dz
|
||||
@@ -201,13 +209,14 @@ def calc_positions(cfg):
|
||||
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']))
|
||||
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']))
|
||||
pos['bm2_try'] = {'value': bm2_beam_height}
|
||||
|
||||
## Optical Table / Exit Window
|
||||
## Optical Table
|
||||
|
||||
# TRY
|
||||
d = bl.ehWindow.center[1] - bl.fm.center[1]
|
||||
@@ -224,4 +233,7 @@ def calc_positions(cfg):
|
||||
ot_es1_trz = cfg['smpl']
|
||||
pos['ot_es1_trz'] = {'value': ot_es1_trz}
|
||||
|
||||
# ES0 exit window
|
||||
pos['es0wi_try'] = {'value': 5} # At 5mm, the middle of the window is 500 mm from the table (neutral position)
|
||||
|
||||
return pos
|
||||
|
||||
@@ -3,6 +3,8 @@ import re
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
os.environ["USE_XRT"] = "False"
|
||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
||||
|
||||
@@ -60,7 +62,6 @@ def calc_surfaces(cfg):
|
||||
out['mo1_2']['y'] = []
|
||||
|
||||
# Focusing mirror
|
||||
|
||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
||||
surface = bl.fm.surfaceToroid
|
||||
stripe = re.sub(r'\s*\(.*?\)', '', cfg['fm_stripe']).strip()
|
||||
@@ -88,6 +89,12 @@ def calc_surfaces(cfg):
|
||||
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'h: {h}')
|
||||
# logger.info(f'z: {z}')
|
||||
# logger.info(f'r: {r}')
|
||||
|
||||
res = 20
|
||||
xElipse = np.linspace(0, np.pi, res)
|
||||
yElipse = np.linspace(0, np.pi, res)
|
||||
|
||||
@@ -10,8 +10,12 @@ logger = bec_logger.logger
|
||||
|
||||
def sldi_gap_to_acc(sldi_gapx, sldi_gapy):
|
||||
d1 = bl.feSlits.center1[1]
|
||||
h_acc = np.tan(sldi_gapx / (2 * d1))
|
||||
v_acc = np.tan(sldi_gapy / (2 * d1))
|
||||
d2 = bl.feSlits.center2[1]
|
||||
h_acc = np.tan(sldi_gapx / (d2 + d1))
|
||||
v_acc = np.tan(sldi_gapy / (d2 + d1))
|
||||
|
||||
# h_acc = np.tan(sldi_gapx / (2 * d1))
|
||||
# v_acc = np.tan(sldi_gapy / (2 * d1))
|
||||
return h_acc, v_acc
|
||||
|
||||
def cm_trx_to_stripe(cm_trx):
|
||||
|
||||
@@ -35,8 +35,9 @@ from debye_bec.bec_widgets.widgets.qt_widgets import (
|
||||
ComboBox,
|
||||
Group,
|
||||
NumberIndicator,
|
||||
Button,
|
||||
)
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.move_widget import MoveWidget
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.move_widget import MoveWidget, AbsorberWidget
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.calc_positions import calc_positions
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.calc_sideview import calc_sideview
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.calc_surfaces import calc_surfaces
|
||||
@@ -55,6 +56,7 @@ from debye_bec.bec_widgets.widgets.digital_twin.calc_varia import (
|
||||
wall_geometries,
|
||||
pipe_geometries,
|
||||
)
|
||||
from debye_bec.devices.absorber import STATUS as ABS_STATUS
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
@@ -106,6 +108,8 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
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.bragg_angle = 0
|
||||
self.qy = 0
|
||||
|
||||
@@ -128,6 +132,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
identifier = kwargs['identifier']
|
||||
match identifier:
|
||||
case 'init':
|
||||
self.update_mo1_mode()
|
||||
self.calc_mo1_bragg_angle()
|
||||
self.calc_cm_crit_pitch()
|
||||
self.calc_cm_reflectivity()
|
||||
@@ -215,11 +220,11 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
sldi_gapx = self.dev.sldi_gapx.read(cached=True)['sldi_gapx']['value']
|
||||
sldi_gapy = self.dev.sldi_gapy.read(cached=True)['sldi_gapy']['value']
|
||||
h_acc, v_acc = sldi_gap_to_acc(sldi_gapx, sldi_gapy)
|
||||
cm_trx = -self.dev.cm_trx.read(cached=True)['cm_trx']['value']
|
||||
cm_stripe = cm_trx_to_stripe(cm_trx)
|
||||
cm_trx = self.dev.cm_trx.read(cached=True)['cm_trx']['value']
|
||||
cm_stripe = cm_trx_to_stripe(-cm_trx)
|
||||
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_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
|
||||
smpl = self.dev.ot_es1_trz.read(cached=True)['ot_es1_trz']['value']
|
||||
@@ -229,17 +234,31 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
'v_acc' : v_acc,
|
||||
'cm_pitch' : -cm_pitch * 1e-3,
|
||||
'cm_stripe' : cm_stripe,
|
||||
'cm_trx' : cm_trx,
|
||||
'cm_trx' : -cm_trx,
|
||||
'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_stripe' : fm_stripe,
|
||||
'fm_trx' : fm_trx,
|
||||
'fm_trx' : -fm_trx,
|
||||
'fm_gain_height' : 1,
|
||||
'smpl' : smpl,
|
||||
}
|
||||
# logger.info(f'Config created: {config}')
|
||||
|
||||
abs_open = self.dev.abs.read(cached=True)['abs_status_string']['value'] == 'OPEN'
|
||||
if not abs_open:
|
||||
ready = True
|
||||
for mover in self.mover.mover_widgets:
|
||||
if mover.status in ('moving', 'error'):
|
||||
ready = False
|
||||
if ready:
|
||||
self.mover.abs.enable_open(1) # Enable open button
|
||||
else:
|
||||
self.mover.abs.enable_open(0) # Disable open button
|
||||
else:
|
||||
self.mover.abs.enable_open(0) # Disable open button
|
||||
|
||||
self.mover.sldi_gapx.set_feedback(sldi_gapx)
|
||||
self.mover.sldi_gapy.set_feedback(sldi_gapy)
|
||||
self.mover.cm_trx.set_feedback(cm_trx)
|
||||
@@ -250,17 +269,57 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.mover.mo1_trx.set_feedback(mo1_trx)
|
||||
self.mover.mo1_try.set_feedback(self.dev.mo1_try.read(cached=True)['mo1_try']['value'])
|
||||
self.mover.sl1_centery.set_feedback(self.dev.sl1_centery.read(cached=True)['sl1_centery']['value'])
|
||||
self.mover.sl1_gapy.set_feedback(self.dev.sl1_gapy.read(cached=True)['sl1_gapy']['value'])
|
||||
self.mover.bm1_try.set_feedback(self.dev.bm1_try.read(cached=True)['bm1_try']['value'])
|
||||
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_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'])
|
||||
self.mover.sl2_gapy.set_feedback(self.dev.sl2_gapy.read(cached=True)['sl2_gapy']['value'])
|
||||
self.mover.bm2_try.set_feedback(self.dev.bm2_try.read(cached=True)['bm2_try']['value'])
|
||||
self.mover.ot_try.set_feedback(self.dev.ot_try.read(cached=True)['ot_try']['value'])
|
||||
self.mover.ot_rotx.set_feedback(self.dev.ot_rotx.read(cached=True)['ot_rotx']['value'])
|
||||
self.mover.ot_es1_trz.set_feedback(smpl)
|
||||
self.mover.es0wi_try.set_feedback(self.dev.es0wi_try.read(cached=True)['es0wi_try']['value'])
|
||||
self.mover.abs.set_feedback(abs_open)
|
||||
return config
|
||||
|
||||
def adapt_reality(self, *args):
|
||||
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']
|
||||
)
|
||||
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_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:
|
||||
mo1_mode = 'Monochromatic'
|
||||
else:
|
||||
mo1_mode = 'Pinkbeam'
|
||||
self.input.mo1_mode.set_current_text(mo1_mode)
|
||||
self.input.mo1_xtal.set_current_text(
|
||||
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'])
|
||||
)
|
||||
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)
|
||||
self.input.smpl.set_number(
|
||||
self.dev.ot_es1_trz.read(cached=True)['ot_es1_trz']['value']
|
||||
)
|
||||
self.calc_assistant(identifier='init')
|
||||
|
||||
def update_fm_mode(self):
|
||||
fm_focus = self.input.fm_focus.currentText()
|
||||
@@ -288,6 +347,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
beam = calc_sideview(config)
|
||||
data = {'x': beam['x'], 'y': beam['y']}
|
||||
self.sideview_plot.update_curves('reality', data)
|
||||
# logger.info('Calc reality surfaces')
|
||||
surfaces = calc_surfaces(config)
|
||||
self.surface_plots.update_surfaces(scene='reality', data=surfaces)
|
||||
|
||||
@@ -329,6 +389,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.sideview_plot.update_curves('assistant', data)
|
||||
|
||||
def calc_assistant_surfaces(self):
|
||||
# logger.info('Calc assistant surfaces')
|
||||
surfaces = calc_surfaces(self.get_assistant_config())
|
||||
self.surface_plots.update_surfaces(scene='assistant', data=surfaces)
|
||||
|
||||
@@ -365,16 +426,19 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.mover.mo1_trx.set_target(out['mo1_trx']['value'])
|
||||
self.mover.mo1_try.set_target(out['mo1_try']['value'])
|
||||
self.mover.sl1_centery.set_target(out['sl1_centery']['value'])
|
||||
self.mover.sl1_gapy.set_target(out['sl1_gapy']['value'])
|
||||
self.mover.bm1_try.set_target(out['bm1_try']['value'])
|
||||
self.mover.fm_trx.set_target(out['fm_trx']['value'])
|
||||
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.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'])
|
||||
self.mover.ot_try.set_target(out['ot_try']['value'])
|
||||
self.mover.ot_rotx.set_target(out['ot_rotx']['value'])
|
||||
self.mover.ot_es1_trz.set_target(out['ot_es1_trz']['value'])
|
||||
self.mover.es0wi_try.set_target(out['es0wi_try']['value'])
|
||||
|
||||
def calc_mo1_bragg_angle(self):
|
||||
"""
|
||||
@@ -392,7 +456,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
energy = self.input.energy.value()
|
||||
theta, theta_cor = mo1_bragg_angle(mo1_mode, d_spacing, energy, cm_pitch)
|
||||
self.bragg_angle = theta
|
||||
self.input.mo1_bragg_angle.setValue(theta_cor / np.pi * 180)
|
||||
self.input.mo1_bragg_angle.setValue(theta / np.pi * 180)
|
||||
|
||||
def update_mo1_mode(self):
|
||||
if self.input.mo1_mode.currentText() in 'Monochromatic':
|
||||
@@ -429,12 +493,15 @@ class InputPanel(QWidget):
|
||||
self._layout = QVBoxLayout(self)
|
||||
self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
|
||||
|
||||
# Adapt to reality
|
||||
self.adapt_reality = Button(label_button='Adapt to reality', enabled=True)
|
||||
|
||||
# Energy
|
||||
self.energy = InputNumberField('energy', 'Energy', unit='eV', init=8979, decimals=0, single_step=100, ll=4000, hl=65000)
|
||||
|
||||
# FE Slits Acceptance
|
||||
self.sldi_hacc = InputNumberField('h_acc', 'Horizontal', unit='mrad', prefix='±', init=0.25, decimals=2, single_step=0.01, ll=-0.1, hl=0.9)
|
||||
self.sldi_vacc = InputNumberField('v_acc', 'Vertical', unit='mrad', prefix='±', init=0.1, decimals=2, single_step=0.01, ll=-0.1, hl=0.5)
|
||||
self.sldi_hacc = InputNumberField('h_acc', 'Horizontal', unit='mrad', prefix='±', init=0.25, decimals=3, single_step=0.01, ll=-0.1, hl=0.9)
|
||||
self.sldi_vacc = InputNumberField('v_acc', 'Vertical', unit='mrad', prefix='±', init=0.1, decimals=3, single_step=0.01, ll=-0.1, hl=0.5)
|
||||
self.sldi_ass_group = Group(
|
||||
'FE Slits Acceptance',
|
||||
[
|
||||
@@ -506,6 +573,7 @@ class InputPanel(QWidget):
|
||||
self.input_group = Group(
|
||||
'User Input',
|
||||
[
|
||||
self.adapt_reality,
|
||||
self.energy,
|
||||
self.sldi_ass_group,
|
||||
self.cm_ass_group,
|
||||
@@ -657,15 +725,12 @@ class MoverPanel(QWidget):
|
||||
self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
|
||||
|
||||
self.mover_widgets = []
|
||||
self.dev = dev
|
||||
|
||||
# FE Slits
|
||||
mot = self.dev.sldi_gapx
|
||||
self.sldi_gapx = MoveWidget(motor=mot, label='GAPX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.sldi_gapx = MoveWidget(dev=dev, motor='sldi_gapx', label='GAPX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.sldi_gapx)
|
||||
|
||||
mot = self.dev.sldi_gapy
|
||||
self.sldi_gapy = MoveWidget(motor=mot, label='GAPY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.sldi_gapy = MoveWidget(dev=dev, motor='sldi_gapy', label='GAPY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.sldi_gapy)
|
||||
|
||||
self.sldi_mov_group = Group(
|
||||
@@ -676,21 +741,27 @@ class MoverPanel(QWidget):
|
||||
]
|
||||
)
|
||||
|
||||
# Absorber
|
||||
self.abs = AbsorberWidget(absorber=dev.abs, label='')
|
||||
|
||||
self.abs_group = Group(
|
||||
'Absorber',
|
||||
[
|
||||
self.abs,
|
||||
]
|
||||
)
|
||||
|
||||
# Collimating mirror
|
||||
mot = self.dev.cm_trx
|
||||
self.cm_trx = MoveWidget(motor=mot, label='TRX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.cm_trx = MoveWidget(dev=dev, motor='cm_trx', label='TRX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.cm_trx)
|
||||
|
||||
mot = self.dev.cm_try
|
||||
self.cm_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.cm_try = MoveWidget(dev=dev, motor='cm_try', label='TRY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.cm_try)
|
||||
|
||||
mot = self.dev.cm_bnd
|
||||
self.cm_bnd = MoveWidget(motor=mot, label='BENDER', unit='km', decimals=2, deadband=0.2)
|
||||
self.cm_bnd = MoveWidget(dev=dev, motor='cm_bnd', label='BENDER', unit='km', decimals=2, deadband=0.2)
|
||||
self.mover_widgets.append(self.cm_bnd)
|
||||
|
||||
mot = self.dev.cm_rotx
|
||||
self.cm_rotx = MoveWidget(motor=mot, label='PITCH', unit='mrad', decimals=3, deadband=0.01)
|
||||
self.cm_rotx = MoveWidget(dev=dev, motor='cm_rotx', label='PITCH', unit='mrad', decimals=3, deadband=0.01)
|
||||
self.mover_widgets.append(self.cm_rotx)
|
||||
|
||||
self.cm_mov_group = Group(
|
||||
@@ -704,16 +775,13 @@ class MoverPanel(QWidget):
|
||||
)
|
||||
|
||||
# Monochromator
|
||||
mot = self.dev.mo1_bragg
|
||||
self.mo1_bragg_angle = MoveWidget(motor=mot, label='Bragg Angle', unit='deg', decimals=3, deadband=0.01)
|
||||
self.mo1_bragg_angle = MoveWidget(dev=dev, motor='mo1_bragg_angle', label='Bragg Angle', unit='deg', decimals=3, deadband=0.01)
|
||||
self.mover_widgets.append(self.mo1_bragg_angle)
|
||||
|
||||
mot = self.dev.mo1_trx
|
||||
self.mo1_trx = MoveWidget(motor=mot, label='TRX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mo1_trx = MoveWidget(dev=dev, motor='mo1_trx', label='TRX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.mo1_trx)
|
||||
|
||||
mot = self.dev.mo1_try
|
||||
self.mo1_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mo1_try = MoveWidget(dev=dev, motor='mo1_try', label='TRY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.mo1_try)
|
||||
|
||||
self.mo1_mov_group = Group(
|
||||
@@ -726,20 +794,22 @@ class MoverPanel(QWidget):
|
||||
)
|
||||
|
||||
# OP Slits 1
|
||||
mot = self.dev.sl1_centery
|
||||
self.sl1_centery = MoveWidget(motor=mot, label='CENTERY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.sl1_centery = MoveWidget(dev=dev, motor='sl1_centery', label='CENTERY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.mover_widgets.append(self.sl1_centery)
|
||||
|
||||
self.sl1_gapy = MoveWidget(dev=dev, motor='sl1_gapy', label='GAPY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.mover_widgets.append(self.sl1_gapy)
|
||||
|
||||
self.sl1_mov_group = Group(
|
||||
'OP Slits 1',
|
||||
[
|
||||
self.sl1_centery,
|
||||
self.sl1_gapy,
|
||||
]
|
||||
)
|
||||
|
||||
# OP Beam Monitor 1
|
||||
mot = self.dev.bm1_try
|
||||
self.bm1_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.bm1_try = MoveWidget(dev=dev, motor='bm1_try', label='TRY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.mover_widgets.append(self.bm1_try)
|
||||
|
||||
self.bm1_mov_group = Group(
|
||||
@@ -750,22 +820,24 @@ class MoverPanel(QWidget):
|
||||
)
|
||||
|
||||
# Focusing Mirror
|
||||
mot = self.dev.fm_trx
|
||||
self.fm_trx = MoveWidget(motor=mot, label='TRX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.fm_trx = MoveWidget(dev=dev, motor='fm_trx', label='TRX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.fm_trx)
|
||||
|
||||
mot = self.dev.fm_try
|
||||
self.fm_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.fm_try = MoveWidget(dev=dev, motor='fm_try', label='TRY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.fm_try)
|
||||
|
||||
mot = self.dev.fm_bnd
|
||||
self.fm_bnd = MoveWidget(motor=mot, label='BENDER', unit='km', decimals=2, deadband=0.2)
|
||||
self.fm_bnd = MoveWidget(dev=dev, motor='fm_bnd', label='BENDER', unit='km', decimals=2, deadband=0.2)
|
||||
self.mover_widgets.append(self.fm_bnd)
|
||||
|
||||
mot = self.dev.fm_rotx
|
||||
self.fm_rotx = MoveWidget(motor=mot, label='PITCH', unit='mrad', decimals=3, deadband=0.01)
|
||||
self.fm_rotx = MoveWidget(dev=dev, motor='fm_rotx', label='PITCH', unit='mrad', decimals=3, deadband=0.01)
|
||||
self.mover_widgets.append(self.fm_rotx)
|
||||
|
||||
self.fm_roty = MoveWidget(dev=dev, motor='fm_roty', label='YAW', unit='mrad', decimals=3, deadband=0.01)
|
||||
self.mover_widgets.append(self.fm_roty)
|
||||
|
||||
self.fm_rotz = MoveWidget(dev=dev, motor='fm_rotz', label='ROLL', unit='mrad', decimals=3, deadband=0.01)
|
||||
self.mover_widgets.append(self.fm_rotz)
|
||||
|
||||
self.fm_mov_group = Group(
|
||||
'Focusing Mirror',
|
||||
[
|
||||
@@ -773,24 +845,28 @@ class MoverPanel(QWidget):
|
||||
self.fm_try,
|
||||
self.fm_bnd,
|
||||
self.fm_rotx,
|
||||
self.fm_roty,
|
||||
self.fm_rotz,
|
||||
]
|
||||
)
|
||||
|
||||
# OP Slits 2
|
||||
mot = self.dev.sl2_centery
|
||||
self.sl2_centery = MoveWidget(motor=mot, label='CENTERY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.sl2_centery = MoveWidget(dev=dev, motor='sl2_centery', label='CENTERY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.mover_widgets.append(self.sl2_centery)
|
||||
|
||||
self.sl2_gapy = MoveWidget(dev=dev, motor='sl2_gapy', label='GAPY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.mover_widgets.append(self.sl2_gapy)
|
||||
|
||||
self.sl2_mov_group = Group(
|
||||
'OP Slits 2',
|
||||
[
|
||||
self.sl2_centery,
|
||||
self.sl2_gapy,
|
||||
]
|
||||
)
|
||||
|
||||
# OP Beam Monitor 2
|
||||
mot = self.dev.bm2_try
|
||||
self.bm2_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.bm2_try = MoveWidget(dev=dev, motor='bm2_try', label='TRY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.mover_widgets.append(self.bm2_try)
|
||||
|
||||
self.bm2_mov_group = Group(
|
||||
@@ -801,23 +877,38 @@ class MoverPanel(QWidget):
|
||||
)
|
||||
|
||||
# Optical Table
|
||||
mot = self.dev.ot_try
|
||||
self.ot_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.2)
|
||||
self.ot_try = MoveWidget(dev=dev, motor='ot_try', label='TRY', unit='mm', decimals=2, deadband=0.2)
|
||||
self.mover_widgets.append(self.ot_try)
|
||||
|
||||
mot = self.dev.ot_rotx
|
||||
self.ot_rotx = MoveWidget(motor=mot, label='ROTX', unit='mrad', decimals=3, deadband=0.05)
|
||||
self.ot_rotx = MoveWidget(dev=dev, motor='ot_rotx', label='ROTX', unit='mrad', decimals=3, deadband=0.05)
|
||||
self.mover_widgets.append(self.ot_rotx)
|
||||
|
||||
mot = self.dev.ot_es1_trz
|
||||
self.ot_es1_trz = MoveWidget(motor=mot, label='ES1 TRZ', unit='mm', decimals=0, deadband=5)
|
||||
self.mover_widgets.append(self.ot_es1_trz)
|
||||
|
||||
self.ot_mov_group = Group(
|
||||
'Optical Table',
|
||||
[
|
||||
self.ot_try,
|
||||
self.ot_rotx,
|
||||
]
|
||||
)
|
||||
|
||||
# Experimental Station 0
|
||||
self.es0wi_try = MoveWidget(dev=dev, motor='es0wi_try', label='ES0 WI', unit='mm', decimals=0, deadband=0.1)
|
||||
self.mover_widgets.append(self.es0wi_try)
|
||||
|
||||
self.es0_mov_group = Group(
|
||||
'Expperimental Station 0',
|
||||
[
|
||||
self.es0wi_try,
|
||||
]
|
||||
)
|
||||
|
||||
# Experimental Station 1
|
||||
self.ot_es1_trz = MoveWidget(dev=dev, motor='ot_es1_trz', label='ES1 TRZ', unit='mm', decimals=0, deadband=5)
|
||||
self.mover_widgets.append(self.ot_es1_trz)
|
||||
|
||||
self.es1_mov_group = Group(
|
||||
'Expperimental Station 1',
|
||||
[
|
||||
self.ot_es1_trz,
|
||||
]
|
||||
)
|
||||
@@ -827,6 +918,7 @@ class MoverPanel(QWidget):
|
||||
'Mover',
|
||||
[
|
||||
self.sldi_mov_group,
|
||||
self.abs_group,
|
||||
self.cm_mov_group,
|
||||
self.mo1_mov_group,
|
||||
self.sl1_mov_group,
|
||||
@@ -835,6 +927,8 @@ class MoverPanel(QWidget):
|
||||
self.sl2_mov_group,
|
||||
self.bm2_mov_group,
|
||||
self.ot_mov_group,
|
||||
self.es0_mov_group,
|
||||
self.es1_mov_group,
|
||||
]
|
||||
)
|
||||
|
||||
@@ -868,10 +962,10 @@ class SurfacePlots(QWidget):
|
||||
}
|
||||
|
||||
self.plots = {
|
||||
'cm': {},
|
||||
'mo1_1': {},
|
||||
'mo1_2': {},
|
||||
'fm': {},
|
||||
'mo1_2': {},
|
||||
'mo1_1': {},
|
||||
'cm': {},
|
||||
}
|
||||
|
||||
self.color_impenetrable = (0, 0, 0)
|
||||
@@ -961,7 +1055,7 @@ class SurfacePlots(QWidget):
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=1, style=Qt.DashLine)
|
||||
else:
|
||||
brush = QBrush(QColor(*self.colors[idx], 255))
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=1)
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=0)
|
||||
self.plots[name][scene].setPen(pen)
|
||||
self.plots[name][scene].setBrush(brush)
|
||||
|
||||
@@ -1020,6 +1114,7 @@ class SideviewPlot(QWidget):
|
||||
|
||||
self.plot_widget = pg.PlotWidget()
|
||||
self.plot_widget.getAxis('bottom').enableAutoSIPrefix(False)
|
||||
self.plot_widget.invertX(True)
|
||||
self.plot_widget.addLegend()
|
||||
|
||||
self.color_impenetrable = (0, 0, 0)
|
||||
@@ -1037,15 +1132,18 @@ class SideviewPlot(QWidget):
|
||||
|
||||
for idx, scene in enumerate(self.data.keys()):
|
||||
if scene in "assistant":
|
||||
pen = pg.mkPen(color=self.colors[idx], width=2, style=Qt.DashLine)
|
||||
pen = pg.mkPen(color=self.colors[idx], width=2, style=Qt.DotLine)
|
||||
z_value = 2
|
||||
else:
|
||||
pen = pg.mkPen(color=self.colors[idx], width=2)
|
||||
z_value = 1
|
||||
self.plots[scene] = self.plot_widget.plot(
|
||||
[],
|
||||
[],
|
||||
pen=pen,
|
||||
name=scene,
|
||||
)
|
||||
self.plots[scene].setZValue(z_value)
|
||||
|
||||
self.plot_group = Group(
|
||||
'Side View',
|
||||
@@ -1098,7 +1196,7 @@ class SideviewPlot(QWidget):
|
||||
for idx, scene in enumerate(self.data):
|
||||
if scene in 'assistant':
|
||||
brush = QBrush(QColor(*self.colors[idx], 255), Qt.DiagCrossPattern)
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=3, style=Qt.DashLine)
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=3, style=Qt.DotLine)
|
||||
else:
|
||||
brush = QBrush(QColor(*self.colors[idx], 255))
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=3)
|
||||
@@ -1141,7 +1239,7 @@ if __name__ == "__main__":
|
||||
from bec_widgets.utils.colors import apply_theme
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
apply_theme("dark")
|
||||
apply_theme("light")
|
||||
dispatcher = BECDispatcher(gui_id="digital_twin")
|
||||
win = DigitalTwin()
|
||||
win.show()
|
||||
|
||||
@@ -7,6 +7,8 @@ from bec_qthemes import material_icon
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_lib import bec_logger
|
||||
|
||||
from debye_bec.devices.absorber import STATUS as ABS_STATUS
|
||||
|
||||
from qtpy.QtCore import Qt, QThread, Signal, QObject, Property, QPropertyAnimation
|
||||
from qtpy.QtWidgets import (
|
||||
QGroupBox, QHBoxLayout, QVBoxLayout, QLabel, QPushButton,
|
||||
@@ -85,17 +87,17 @@ class StatusIcon(QWidget):
|
||||
|
||||
class MotionWorker(QObject):
|
||||
"""
|
||||
Simulates moving a stage from current_pos to target_pos.
|
||||
Emits position_changed and finished signals.
|
||||
Executes motion on the specified motor and includes some safety during
|
||||
motion for certain motors.
|
||||
"""
|
||||
position_changed = Signal(float)
|
||||
error = Signal(bool) # True = error
|
||||
finished = Signal(bool) # True = reached target, False = stopped
|
||||
|
||||
def __init__(self, motor, target_pos: float):
|
||||
def __init__(self, dev, motor, target_pos: float):
|
||||
super().__init__()
|
||||
self.dev = dev
|
||||
self.motor = motor
|
||||
self.name = motor.dotted_name
|
||||
self._target = target_pos
|
||||
self._stop_flag = threading.Event()
|
||||
|
||||
@@ -103,7 +105,7 @@ class MotionWorker(QObject):
|
||||
self._stop_flag.set()
|
||||
|
||||
def run(self):
|
||||
logger.info(f'Would run motor {self.name}')
|
||||
logger.info(f'Would run motor {self.motor}')
|
||||
simulated_run_time = 3
|
||||
start = time.time()
|
||||
while (time.time() - start) < simulated_run_time:
|
||||
@@ -119,22 +121,139 @@ class MotionWorker(QObject):
|
||||
# time.sleep(0.1)
|
||||
self.finished.emit(True)
|
||||
|
||||
def run2(self):
|
||||
match self.name:
|
||||
case 'sldi_gapx' | 'sldi_gapy' | 'sldi_centerx' | 'sldi_centery':
|
||||
self.motion()
|
||||
case 'cm_trx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_roty'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'cm_roty':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_trx'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'cm_try':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_rotx'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['cm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'cm_rotx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_try'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['cm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'cm_rotz':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_try'], 'abs_tol': 0.05},
|
||||
{'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
|
||||
p2 = (1/(self._target*1e3) + 0.0284)/2e-6
|
||||
self._target = p2 - p1
|
||||
self.motion(relative=True, rb=
|
||||
{'device': self.dev['cm_bnd_radius']}
|
||||
)
|
||||
case 'mo1_try' | 'mo1_trx' | 'mo1_roty':
|
||||
self.motion(abs_closed=True)
|
||||
case 'mo1_bragg_angle':
|
||||
self.motion()
|
||||
case 'sl1_centery' | 'sl1_gapy' | 'bm1_try':
|
||||
self.motion()
|
||||
case 'fm_trx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_roty'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'fm_roty':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_trx'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'fm_try':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_rotx'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['fm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'fm_rotx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_try'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['fm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'fm_rotz':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_try'], 'abs_tol': 0.05},
|
||||
{'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
|
||||
p2 = (1/(self._target*1e3) + 4.28e-5)/1.84e-9
|
||||
self._target = p2 - p1
|
||||
self.motion(relative=True, rb=
|
||||
{'device': self.dev['fm_bnd_radius']}
|
||||
)
|
||||
case 'sl2_centery' | 'sl2_gapy' | 'bm2_try':
|
||||
self.motion()
|
||||
case 'ot_try' | 'ot_rotx' | 'ot_es1_trz':
|
||||
self.motion()
|
||||
case _:
|
||||
logger.warning(f'Motor {self.motor} not integrated in digital twin!')
|
||||
|
||||
def motion(self, abs_closed=False, relative=False, rb=None, surveyed_axes = None):
|
||||
"""
|
||||
Moves an axis while surverying a set of axes (if set).
|
||||
Example surveyed_axes:
|
||||
[{'device': bec_device_object, 'abs_tol': 0.1},]
|
||||
|
||||
Args:
|
||||
surveyed_axes (list): List of dictionaries of devices
|
||||
"""
|
||||
if abs_closed:
|
||||
if self.dev.abs.status.get() == ABS_STATUS.OPEN:
|
||||
status = self.dev.abs.close()
|
||||
# TODO Set timeout to 0.001 and check if it actually raises (it should not start motion).
|
||||
# Check of behavior of digital twin afterwards.
|
||||
status.wait(timeout=5)
|
||||
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']
|
||||
if rb is not None:
|
||||
rb['name'] = rb['device'].dotted_name
|
||||
self.dev[self.motor].move(self._target, relative=relative)
|
||||
while self.dev[self.motor].motor_is_moving.get():
|
||||
if self._stop_flag.is_set():
|
||||
self.dev[self.motor].motor_stop()
|
||||
if rb is not None:
|
||||
self.position_changed.emit(rb['device'].read()[rb['name']]['value'])
|
||||
else:
|
||||
self.position_changed.emit(self.dev[self.motor].read[self.motor]['value'])
|
||||
if surveyed_axes is not None:
|
||||
for surv_ax in surveyed_axes:
|
||||
fb = surv_ax['device'].read()[surv_ax['name']]['value']
|
||||
if abs(fb - surv_ax['old_value']) > surv_ax['abs_tol']:
|
||||
self.dev[self.motor].motor_stop()
|
||||
self.error.emit(1)
|
||||
break
|
||||
time.sleep(0.1)
|
||||
self.finished.emit(True)
|
||||
|
||||
class MoveWidget(QWidget):
|
||||
"""
|
||||
One motor stage control group containing:
|
||||
- Value spinbox (target position)
|
||||
- Target label (target position)
|
||||
- Feedback label (current position)
|
||||
- Status icon (qtawesome)
|
||||
- Status icon (bec_qthemes)
|
||||
- Start / Stop button
|
||||
"""
|
||||
|
||||
def __init__(self, motor, label: str = '', unit=None, decimals=3, deadband=0.0):
|
||||
def __init__(self, dev, motor, label: str = '', unit=None, decimals=3, deadband=0.0):
|
||||
super().__init__()
|
||||
self.fb = 0.0
|
||||
self.target = 0
|
||||
self.dev = dev
|
||||
self.motor = motor
|
||||
self.deadband = deadband
|
||||
self._status = Status.IN_POSITION
|
||||
self.status = Status.IN_POSITION
|
||||
self._thread: QThread | None = None
|
||||
self._worker: MotionWorker | None = None
|
||||
|
||||
@@ -143,8 +262,6 @@ class MoveWidget(QWidget):
|
||||
self.unit = unit
|
||||
self.decimals = decimals
|
||||
|
||||
# self._set_status(Status.IN_POSITION)
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
@@ -206,7 +323,7 @@ class MoveWidget(QWidget):
|
||||
self._on_target_or_fb_changed()
|
||||
|
||||
def set_feedback(self, fb):
|
||||
if self._status != Status.MOVING:
|
||||
if self.status != Status.MOVING:
|
||||
self.fb = fb
|
||||
text = f'{fb:.{int(self.decimals)}f}'
|
||||
if self.unit is not None:
|
||||
@@ -214,9 +331,6 @@ class MoveWidget(QWidget):
|
||||
self.fb_label.setText(text)
|
||||
self._on_target_or_fb_changed()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Button style helpers
|
||||
# ------------------------------------------------------------------
|
||||
def _apply_button_style(self, mode: str):
|
||||
if mode == "start":
|
||||
self.btn_action.setText("▶ Move")
|
||||
@@ -229,19 +343,13 @@ class MoveWidget(QWidget):
|
||||
f"QPushButton {{background-color: {get_accent_colors().emergency.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Status management
|
||||
# ------------------------------------------------------------------
|
||||
def _set_status(self, status: str):
|
||||
self._status = status
|
||||
self.status = status
|
||||
self.status_icon.set_status(status)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Motion control
|
||||
# ------------------------------------------------------------------
|
||||
def _on_target_or_fb_changed(self):
|
||||
"""Re-evaluate in-position status whenever the target value changes."""
|
||||
if self._status in (Status.ERROR, Status.MOVING):
|
||||
if self.status in (Status.ERROR, Status.MOVING):
|
||||
return
|
||||
if abs(self.fb - self.target) <= self.deadband:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
@@ -263,11 +371,10 @@ class MoveWidget(QWidget):
|
||||
self._set_status(Status.MOVING)
|
||||
self._apply_button_style("stop")
|
||||
|
||||
self._worker = MotionWorker(self.motor, target)
|
||||
self._worker = MotionWorker(self.dev, self.motor, target)
|
||||
self._thread = QThread()
|
||||
self._worker.moveToThread(self._thread)
|
||||
|
||||
# Wire signals
|
||||
self._thread.started.connect(self._worker.run)
|
||||
self._worker.position_changed.connect(self._on_position_changed)
|
||||
self._worker.error.connect(self._on_error)
|
||||
@@ -285,7 +392,6 @@ class MoveWidget(QWidget):
|
||||
def _stop_motion(self):
|
||||
if self._worker:
|
||||
self._worker.stop()
|
||||
# UI will update via finished signal
|
||||
|
||||
def _on_position_changed(self, pos: float):
|
||||
self.fb = pos
|
||||
@@ -296,10 +402,11 @@ class MoveWidget(QWidget):
|
||||
|
||||
def _on_motion_finished(self, reached: bool):
|
||||
target = self.target
|
||||
if abs(self.fb - target) <= self.deadband:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
else:
|
||||
self._set_status(Status.NOT_IN_POSITION)
|
||||
if self.status not in Status.ERROR:
|
||||
if abs(self.fb - target) <= self.deadband:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
else:
|
||||
self._set_status(Status.NOT_IN_POSITION)
|
||||
self._apply_button_style("start")
|
||||
|
||||
def _cleanup_thread(self):
|
||||
@@ -310,12 +417,82 @@ class MoveWidget(QWidget):
|
||||
self._worker.deleteLater()
|
||||
self._worker = None
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Called on application close — stop motion safely
|
||||
# ------------------------------------------------------------------
|
||||
def shutdown(self):
|
||||
if self._worker:
|
||||
self._worker.stop()
|
||||
if self._thread:
|
||||
self._thread.quit()
|
||||
self._thread.wait(2000) # max 2 s grace period
|
||||
|
||||
class AbsorberWidget(QWidget):
|
||||
"""
|
||||
Control of the frontend absorber (only open)
|
||||
"""
|
||||
|
||||
def __init__(self, absorber, label: str = 'Absorber'):
|
||||
super().__init__()
|
||||
self.absorber = absorber
|
||||
self.fb = False
|
||||
self.text_color = (0, 0, 0)
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
# Name
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(100)
|
||||
self.label.setContentsMargins(0, 0, 10, 0)
|
||||
self.label.setWordWrap(True)
|
||||
layout.addWidget(self.label)
|
||||
|
||||
# Blank
|
||||
self.blank_label = QLabel('')
|
||||
self.blank_label.setFixedWidth(100)
|
||||
layout.addWidget(self.blank_label)
|
||||
|
||||
# Feedback
|
||||
self.fb_label = QLabel('-')
|
||||
self.fb_label.setFixedWidth(100)
|
||||
layout.addWidget(self.fb_label)
|
||||
|
||||
# Blank icon
|
||||
self.blank_icon = QLabel('')
|
||||
self.blank_icon.setFixedWidth(30)
|
||||
self.blank_icon.setContentsMargins(0, 0, 10, 0)
|
||||
layout.addWidget(self.blank_icon)
|
||||
|
||||
# Open
|
||||
self.btn_action = QPushButton("Open")
|
||||
self.btn_action.setFixedWidth(90)
|
||||
self.btn_action.setFixedHeight(20)
|
||||
self.btn_action.clicked.connect(self._on_button_clicked)
|
||||
layout.addWidget(self.btn_action)
|
||||
|
||||
def set_feedback(self, fb: bool):
|
||||
self.fb = fb
|
||||
if fb:
|
||||
self.fb_label.setText('Open')
|
||||
self.fb_label.setStyleSheet(
|
||||
f"QLabel {{color: {get_accent_colors().success.name()}}}"
|
||||
)
|
||||
else:
|
||||
self.fb_label.setText('Closed')
|
||||
self.fb_label.setStyleSheet(
|
||||
f"QLabel {{color: {get_accent_colors().emergency.name()}}}"
|
||||
)
|
||||
|
||||
def enable_open(self, enable: bool = False):
|
||||
if enable:
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
)
|
||||
self.btn_action.setEnabled(True)
|
||||
else: # disabled
|
||||
self.btn_action.setStyleSheet(
|
||||
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
|
||||
)
|
||||
self.btn_action.setDisabled(True)
|
||||
|
||||
def _on_button_clicked(self):
|
||||
self.absorber.open()
|
||||
|
||||
@@ -8,6 +8,8 @@ from qtpy.QtWidgets import (
|
||||
from qtpy.QtGui import QFont
|
||||
from qtpy.QtCore import Qt
|
||||
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
|
||||
class Group(QGroupBox):
|
||||
def __init__(self, label, widgets):
|
||||
super().__init__(label)
|
||||
@@ -128,70 +130,40 @@ class ComboBox(QWidget):
|
||||
def setDisabled(self, disable):
|
||||
self.value.setDisabled(disable)
|
||||
|
||||
# class Mover(QWidget):
|
||||
# def __init__(self, dev, egu, prec):
|
||||
# super().__init__()
|
||||
# layout = QHBoxLayout(self)
|
||||
# layout.setContentsMargins(10, 0, 0, 0)
|
||||
# layout.setSpacing(0)
|
||||
# self.position = QLabel('-')
|
||||
# self.position.setFixedWidth(150)
|
||||
# layout.addWidget(self.position)
|
||||
# self.led = QLabel()
|
||||
# self.led.setFixedWidth(30)
|
||||
# self.led.setStyleSheet("background-color: 0, 0, 0; border: 1px solid black;")
|
||||
# layout.addWidget(self.led)
|
||||
# self.start = QPushButton('Move')
|
||||
# self.start.setStyleSheet("color: black; background-color: green;")
|
||||
# self.start.setFixedWidth(80)
|
||||
# self.stop = QPushButton('Stop')
|
||||
# self.stop.setStyleSheet("color: black; background-color: firebrick;")
|
||||
# self.stop.setFixedWidth(80)
|
||||
# layout.addWidget(self.start)
|
||||
# layout.addWidget(self.stop)
|
||||
# self.dev = dev
|
||||
# self.unit = egu
|
||||
# self.decimals = prec
|
||||
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)
|
||||
if label is not None:
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(140)
|
||||
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)
|
||||
|
||||
# def led_set_status(self, status):
|
||||
# if status in 'out':
|
||||
# self.led.setStyleSheet("background-color: 255, 0, 0; border: 1px solid black;")
|
||||
# elif status in 'moving':
|
||||
# self.led.setStyleSheet("background-color: 255, 255, 0; border: 1px solid black;")
|
||||
# elif status in 'in':
|
||||
# self.led.setStyleSheet("background-color: 0, 255, 0; border: 1px solid black;")
|
||||
def clicked_connect(self, func):
|
||||
"""Connect a function to the button press."""
|
||||
self.button.clicked.connect(func)
|
||||
|
||||
# def position_setValue(self, number):
|
||||
# text = f'{number:.{int(self.decimals)}f}'
|
||||
# if self.unit is not None:
|
||||
# text = text + ' ' + self.unit
|
||||
# self.position.setText(text)
|
||||
def enable_button(self, enable: bool = False):
|
||||
if enable:
|
||||
self.button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
self.button.setEnabled(True)
|
||||
else: # disabled
|
||||
self.button.setStyleSheet(
|
||||
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
|
||||
)
|
||||
self.button.setDisabled(True)
|
||||
|
||||
# def start_clicked_connect(self, func):
|
||||
# """Connect a function to the start button press."""
|
||||
# self.start.clicked.connect(
|
||||
# partial(func, dev=self.dev)
|
||||
# )
|
||||
|
||||
# def stop_clicked_connect(self, func):
|
||||
# """Connect a function to the stop button press."""
|
||||
# self.stop.clicked.connect(
|
||||
# partial(func, dev=self.dev)
|
||||
# )
|
||||
|
||||
# def start_setEnabled(self, enable):
|
||||
# self.start.setEnabled(enable)
|
||||
# if enable:
|
||||
# self.start.setStyleSheet("color: black; background-color: green;")
|
||||
# else:
|
||||
# self.start.setStyleSheet("color: black; background-color: grey;")
|
||||
|
||||
# def stop_setEnabled(self, enable):
|
||||
# self.stop.setEnabled(enable)
|
||||
# if enable:
|
||||
# self.stop.setStyleSheet("color: black; background-color: firebrick;")
|
||||
# else:
|
||||
# self.stop.setStyleSheet("color: black; background-color: grey;")
|
||||
def setText(self, text):
|
||||
self.button.setText(text)
|
||||
|
||||
# class TextIndicator(QWidget):
|
||||
# def __init__(self, label, unit=None, highlight=False):
|
||||
|
||||
@@ -33,15 +33,15 @@ mo1_bragg:
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
#mo1_bragg_angle:
|
||||
# readoutPriority: baseline
|
||||
# description: Positioner for the Monochromator
|
||||
# deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg_angle.Mo1BraggAngle
|
||||
# deviceConfig:
|
||||
# prefix: "X01DA-OP-MO1:BRAGG:"
|
||||
# onFailure: retry
|
||||
# enabled: true
|
||||
# softwareTrigger: false
|
||||
mo1_bragg_angle:
|
||||
readoutPriority: baseline
|
||||
description: Positioner for the Monochromator
|
||||
deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg_angle.Mo1BraggAngle
|
||||
deviceConfig:
|
||||
prefix: "X01DA-OP-MO1:BRAGG:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Remaining optics hutch
|
||||
optics_config:
|
||||
|
||||
@@ -41,8 +41,8 @@ class Absorber(PSIDeviceBase):
|
||||
USER_ACCESS = ["open", "close"]
|
||||
|
||||
request = Cpt(EpicsSignal, suffix="REQUEST", kind="config", doc="Open/Close Absorber")
|
||||
status = Cpt(EpicsSignalRO, suffix="STATUS", kind="config", doc="Absorber Status")
|
||||
status_string = Cpt(EpicsSignalRO, suffix="STATUS", kind="config", string=True, doc="Absorber Status")
|
||||
status = Cpt(EpicsSignalRO, suffix="STATUS", kind="normal", doc="Absorber Status")
|
||||
status_string = Cpt(EpicsSignalRO, suffix="STATUS", kind="normal", string=True, 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)
|
||||
|
||||
Reference in New Issue
Block a user