From 3e959e6c5ddbb5fab051db9547a5bc1f62bf9897 Mon Sep 17 00:00:00 2001 From: x01da Date: Mon, 11 May 2026 10:16:42 +0200 Subject: [PATCH] wip: digital twin --- .../widgets/digital_twin/calc_positions.py | 40 ++- .../widgets/digital_twin/calc_surfaces.py | 9 +- .../widgets/digital_twin/calc_varia.py | 8 +- .../widgets/digital_twin/digital_twin.py | 218 +++++++++++----- .../widgets/digital_twin/move_widget.py | 243 +++++++++++++++--- debye_bec/bec_widgets/widgets/qt_widgets.py | 94 +++---- .../device_configs/x01da_standard_config.yaml | 18 +- debye_bec/devices/absorber.py | 4 +- 8 files changed, 452 insertions(+), 182 deletions(-) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calc_positions.py b/debye_bec/bec_widgets/widgets/digital_twin/calc_positions.py index 971b7e1..569145e 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/calc_positions.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/calc_positions.py @@ -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 diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calc_surfaces.py b/debye_bec/bec_widgets/widgets/digital_twin/calc_surfaces.py index bdaf703..7162d45 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/calc_surfaces.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/calc_surfaces.py @@ -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) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py b/debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py index 11c1c42..029be5d 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py @@ -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): 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 dea2df5..6337ca1 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py @@ -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() diff --git a/debye_bec/bec_widgets/widgets/digital_twin/move_widget.py b/debye_bec/bec_widgets/widgets/digital_twin/move_widget.py index b3b9a40..27419af 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/move_widget.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/move_widget.py @@ -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() diff --git a/debye_bec/bec_widgets/widgets/qt_widgets.py b/debye_bec/bec_widgets/widgets/qt_widgets.py index 636b4fe..201e72f 100644 --- a/debye_bec/bec_widgets/widgets/qt_widgets.py +++ b/debye_bec/bec_widgets/widgets/qt_widgets.py @@ -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): diff --git a/debye_bec/device_configs/x01da_standard_config.yaml b/debye_bec/device_configs/x01da_standard_config.yaml index 2015fef..a154a73 100644 --- a/debye_bec/device_configs/x01da_standard_config.yaml +++ b/debye_bec/device_configs/x01da_standard_config.yaml @@ -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: diff --git a/debye_bec/devices/absorber.py b/debye_bec/devices/absorber.py index e359357..484054d 100644 --- a/debye_bec/devices/absorber.py +++ b/debye_bec/devices/absorber.py @@ -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)