diff --git a/app_config.py b/app_config.py index 4398f1c..a6b6162 100644 --- a/app_config.py +++ b/app_config.py @@ -52,9 +52,10 @@ class AppCfg(QSettings): WINDOW_STATE= "window/state" DFT_POS_DET ="default_position/detector" #json + DFT_POS_GONIO ="default_position/gonio" #json DFT_POS_PST ="default_position/post_sample_tube" #json DFT_POS_COL ="default_position/collimator" #json - DFT_POS_BKLGT ="default_position/backlight" #json + DFT_POS_BKLGT ="default_position/backlight" #json DT_HOST="deltatau/host" DT_MISC="deltatau/miscellaneous" @@ -140,8 +141,8 @@ class AppCfg(QSettings): dflt.append((AppCfg.DAQ_BS_CH, ("SARES30-LSCP1-CRISTA1:CH0:1",))) #list of BS channels if AppCfg.DAQ_PV_CH not in keys: dflt.append((AppCfg.DAQ_PV_CH, () )) #list of PVs - - + if AppCfg.DFT_POS_GONIO not in keys: + dflt.append((AppCfg.DFT_POS_GONIO, {'mount':(0.,0.,0.,0.),'align':(0.,0.,0.,0.)}))#defout positions for k,v in dflt: _log.warning(f'{k} not defined. use default') @@ -163,7 +164,7 @@ class AppCfg(QSettings): elif key in (AppCfg.GBL_MISC, # for all these keys convert to json and change " to ' AppCfg.GEO_CAM_PARAM, AppCfg.GEO_FND_FID, AppCfg.GEO_AUTOFOC, - AppCfg.DFT_POS_DET,AppCfg.DFT_POS_PST,AppCfg.DFT_POS_COL,AppCfg.DFT_POS_BKLGT, + AppCfg.DFT_POS_DET,AppCfg.DFT_POS_GONIO,AppCfg.DFT_POS_PST,AppCfg.DFT_POS_COL,AppCfg.DFT_POS_BKLGT, AppCfg.DT_MISC, AppCfg.DAQ_DET,AppCfg.DAQ_LOC,AppCfg.DAQ_RUN,AppCfg.DAQ_BS_CH,AppCfg.DAQ_PV_CH ): @@ -190,7 +191,7 @@ class AppCfg(QSettings): elif key in (AppCfg.GBL_MISC, AppCfg.GEO_CAM_PARAM, AppCfg.GEO_FND_FID, AppCfg.GEO_AUTOFOC, - AppCfg.DFT_POS_DET,AppCfg.DFT_POS_PST,AppCfg.DFT_POS_COL,AppCfg.DFT_POS_BKLGT, + AppCfg.DFT_POS_DET,AppCfg.DFT_POS_GONIO,AppCfg.DFT_POS_PST,AppCfg.DFT_POS_COL,AppCfg.DFT_POS_BKLGT, AppCfg.DT_MISC, AppCfg.DAQ_DET,AppCfg.DAQ_LOC,AppCfg.DAQ_RUN,AppCfg.DAQ_BS_CH,AppCfg.DAQ_PV_CH ): @@ -243,6 +244,7 @@ class WndParameter(QMainWindow): dft_pos_col = cfg.value(AppCfg.DFT_POS_COL) dft_pos_bklgt = cfg.value(AppCfg.DFT_POS_BKLGT) dft_pos_det = cfg.value(AppCfg.DFT_POS_DET) + dft_pos_gonio = cfg.value(AppCfg.DFT_POS_GONIO) geo_cam_param = cfg.value(AppCfg.GEO_CAM_PARAM) dt_host = cfg.value(AppCfg.DT_HOST) dt_misc = cfg.value(AppCfg.DT_MISC) @@ -356,6 +358,12 @@ verbose bits: {'name':'set_in', 'title':'use current position as "in"', 'type':'action'}, {'name':'set_out', 'title':'use current position as "out"', 'type':'action'}, ]}, + {'name':AppCfg.DFT_POS_GONIO, 'title':'gonio reference positions', 'type':'group', 'expanded':False, 'children':[ + {'name':'pos_mount', 'title':'Mount position', 'value':dft_pos_gonio.get('mount'), 'type':'str' }, + {'name':'pos_align', 'title':'Align position', 'value':dft_pos_gonio.get('align'), 'type':'str' }, + {'name':'set_mount', 'title':'use current position as "mount"', 'type':'action'}, + {'name':'set_align', 'title':'use current position as "align"', 'type':'action'}, + ]}, {'name':'Delta Tau Parameters', 'type':'group','expanded':False, 'children':[ {'name':AppCfg.DT_HOST, 'title':'host name (host[:port:port_gather])','value':dt_host, 'type':'str'}, {'name':AppCfg.DT_MISC, 'title':'miscellaneous', 'type':'group', 'children':[ @@ -407,12 +415,14 @@ verbose bits: child.sigValueChanging.connect(lambda a,b: self.cb_valueChanging(a,b)) for ch2 in child.children(): ch2.sigValueChanging.connect(lambda a,b: self.cb_valueChanging(a,b)) - p.param(AppCfg.DFT_POS_PST, 'set_in' ).sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_PST, 'in')) - p.param(AppCfg.DFT_POS_PST, 'set_out').sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_PST, 'out')) - p.param(AppCfg.DFT_POS_COL, 'set_in' ).sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_COL, 'in')) - p.param(AppCfg.DFT_POS_COL, 'set_out').sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_COL, 'out')) - p.param(AppCfg.DFT_POS_DET, 'set_in' ).sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_DET, 'in')) - p.param(AppCfg.DFT_POS_DET, 'set_out').sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_DET, 'out')) + p.param(AppCfg.DFT_POS_PST, 'set_in' ).sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_PST, 'in')) + p.param(AppCfg.DFT_POS_PST, 'set_out' ).sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_PST, 'out')) + p.param(AppCfg.DFT_POS_COL, 'set_in' ).sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_COL, 'in')) + p.param(AppCfg.DFT_POS_COL, 'set_out' ).sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_COL, 'out')) + p.param(AppCfg.DFT_POS_DET, 'set_in' ).sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_DET, 'in')) + p.param(AppCfg.DFT_POS_DET, 'set_out' ).sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_DET, 'out')) + p.param(AppCfg.DFT_POS_GONIO,'set_mount').sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_GONIO,'mount')) + p.param(AppCfg.DFT_POS_GONIO,'set_align').sigActivated.connect(lambda x: self.cb_use_cur_pos_as(AppCfg.DFT_POS_GONIO,'align')) #p.param('Save/Restore functionality', 'Save State').sigActivated.connect(self.cb_save) #p.param('Save/Restore functionality', 'Restore State').sigActivated.connect(self.cb_restore) @@ -496,6 +506,10 @@ verbose bits: d=dict(map(lambda x:(x.name(),x.value()), parent.children())) for k in ('set_in','set_out'): del d[k] cfg.setValue(par_nm,d) + elif par_nm == AppCfg.DFT_POS_GONIO: + k=parent.children()[:-2] + d=dict(map(lambda x:(x.name(),tuple(map(float,x.value().strip('[]()').split(', ')))), k)) + cfg.setValue(par_nm,d) elif par_nm==AppCfg.GBL_DEV_PREFIX: v=tuple(map(lambda x:x.value(), parent.children())) cfg.setValue(par_nm, v) @@ -550,7 +564,15 @@ verbose bits: # 'xeye_x', 'xeye_y']) #p.children() cld=p.children() - if dev==AppCfg.DFT_POS_PST: + if dev==AppCfg.DFT_POS_GONIO: + if pos=='mount': + idx=0 + elif pos=='align': + idx=1 + val=tuple(map(lambda k: twk[k].get_val(),('fast_x','fast_y','base_x','base_z'))) + cld[idx].setValue(json.dumps(val, cls=MyJsonEncoder)) + return + elif dev==AppCfg.DFT_POS_PST: # k=('x_in_us','y_in_us','x_in_ds','y_in_ds','x_out_delta','y_out_delta','z_in','z_out',) if pos=='in': lut=((0,'tube_usx'),(1,'tube_usy'),(2,'tube_dsx'),(3,'tube_dsy'),(6,'tube_z')) diff --git a/app_utils.py b/app_utils.py index 8599aa4..c7d7b3a 100644 --- a/app_utils.py +++ b/app_utils.py @@ -6,7 +6,6 @@ _log=logging.getLogger(__name__) from epics.ca import pend_event - class PositionsNotReached(Exception): pass @@ -29,20 +28,22 @@ def assert_tweaker_positions(targets, timeout=60.0): count=0 summary=[] for i, m in enumerate(targets): - motor, target, tolerance=m - name=motor.short_name - pend_event() - cur=motor.get_rbv() - done=motor.is_done() + mot_tw, target, tolerance=m + name=mot_tw._motor._short_name + notSim=not type(mot_tw).__name__.startswith('Sim') + + if notSim: pend_event() + cur=mot_tw.get_rbv() + done=mot_tw.is_done() s=f"check {name} {cur:.5g} == {target:.5g} [done={done}]" _log.debug(s) summary.append(s) if done and tolerance>=abs(cur-target): count+=1 - pend_event(0.1) + if notSim: pend_event(0.1) if count==num_motors: break - pend_event(0.1) + if notSim: pend_event(0.1) if count!=num_motors: raise PositionsNotReached("failed to reach target positions: {}".format("#".join(summary))) diff --git a/epics_widgets/MotorTweak.py b/epics_widgets/MotorTweak.py index 3d91321..b5c1348 100644 --- a/epics_widgets/MotorTweak.py +++ b/epics_widgets/MotorTweak.py @@ -41,17 +41,15 @@ class MotorTweak(QWidget, Ui_MotorTweak): self._templates = {} - def connect_motor(self, motor_base, short_name=None, **kwargs): + def connect_motor(self, rec_name, short_name, **kwargs): # TODO: DO NOT USE Motor base class, but reduce to the only really needed PV: s.a. class QopticZoom(object) # TODO: have a own motor Class as class SimMotor: m = Motor(motor_base) m.get_position() self._ignore_limits = m.HLM == m.LLM # if both high/low limits are equal they are meaningless self._motor = m - if not short_name: - short_name = m.description - self.short_name = short_name - self._pvname = motor_base + m._short_name = short_name + self._rec_name = rec_name for attr in ['RTYP', 'JVEL', 'HLS', 'LLS', 'TWV', 'RBV', 'VAL', 'LVIO', 'HLM', 'LLM']: # _log.debug('connecting to field {}'.format(attr)) @@ -193,19 +191,19 @@ class MotorTweak(QWidget, Ui_MotorTweak): if field != src: return if field == 'VAL': - self.event_val.emit(self._pvname, kw) + self.event_val.emit(self._rec_name, kw) elif field == 'RBV': self.event_rbv.emit(kw['alias'], kw['value'], kw) elif field == 'LVIO': - self.event_soft_limit.emit(self._pvname, kw) + self.event_soft_limit.emit(self._rec_name, kw) elif field == 'HLS': - self.event_high_hard_limit.emit(self._pvname, kw) - self.event_axis_fault.emit(self._pvname, kw) + self.event_high_hard_limit.emit(self._rec_name, kw) + self.event_axis_fault.emit(self._rec_name, kw) elif field == 'LVIO': - self.event_low_hard_limit.emit(self._pvname, kw) - self.event_axis_fault.emit(self._pvname, kw) + self.event_low_hard_limit.emit(self._rec_name, kw) + self.event_axis_fault.emit(self._rec_name, kw) elif field == 'STAT': - self.event_axis_fault.emit(self._pvname, kw) + self.event_axis_fault.emit(self._rec_name, kw) def update_label(self, **kwargs): m = self._motor diff --git a/epics_widgets/SimMotorTweak.py b/epics_widgets/SimMotorTweak.py index 0470aad..64ed209 100644 --- a/epics_widgets/SimMotorTweak.py +++ b/epics_widgets/SimMotorTweak.py @@ -56,7 +56,7 @@ class SimMotorTweak(QWidget, Ui_MotorTweak): self._templates = {} - def connect_motor(self, rec_name, short_name=None, *args, **kwargs): + def connect_motor(self, rec_name, short_name, *args, **kwargs): self.label.setToolTip('{} => {}'.format(rec_name, short_name)) self._motor=m=SimMotor(rec_name, short_name) self.set_motor_validator() diff --git a/swissmx.py b/swissmx.py index 4f8c207..cb343dc 100755 --- a/swissmx.py +++ b/swissmx.py @@ -1531,7 +1531,8 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): lambda: self.move_post_tube("out"), lambda: app._backlight.move("out"), lambda: self.move_collimator("out"), - ] + lambda:self.move_gonio("mount"), + ] self.esc_run_steps(steps, "Transitioning to Sample Exchange","ManualSampleExchange") def cb_esc_sample_alignment(self): @@ -1543,6 +1544,7 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): lambda:self.move_post_tube("out"), lambda:app._backlight.move("in"), lambda:self.move_collimator("out"), + lambda:self.move_gonio("align"), ] #if option(CRYOJET_MOTION_ENABLED): # steps.extend([lambda: self.move_cryojet_nozzle("in")]) @@ -2195,6 +2197,45 @@ Author Thierry Zamofing (thierry.zamofing@psi.ch) break self._esc_state = esc_state + def move_gonio(self, pos): + # gonio="{'pos_mount': (0.0,0.0,0.0,0.0), 'pos_align': (0.0,0.0,0.0,0.0)}" + # positions are: fast_x,fast_y,base_x,base_z,(omega=0,det_z, no change) + + app=QApplication.instance() + cfg=app._cfg + pos_gonio = cfg.value(AppCfg.DFT_POS_GONIO) + if pos_gonio is None: + msg="gonio default positions are not configured." + _log.warning(msg) + QMessageBox.warning(self, "gonio not configured", msg) + return + + tw_fx = self.tweakers["fast_x"] + tw_fy = self.tweakers["fast_y"] + tw_cx = self.tweakers["base_x"] + tw_cz = self.tweakers["base_z"] + tw_ry = self.tweakers["omega"] + try: + p_fx, p_fy, p_cx, p_cz,=pos_gonio['pos_'+pos] + p_ry=0. + except KeyError: + raise ValueError("Goniometer position *{}* is not known!!") + _log.info(f"moving goniometer {pos} to fx:{p_fx:.5g},fy:{p_fy:.5g},cx:{p_cx:.5g},cz_{p_cz:.5g},ry:{p_ry:.5g}") + + tw_fx.move_abs(p_fx) + tw_fy.move_abs(p_fy) + tw_cx.move_abs(p_cx) + tw_cz.move_abs(p_cz) + tw_ry.move_abs(p_ry) + + app_utils.assert_tweaker_positions([ + (tw_fx, p_fx, 0.1), + (tw_fy, p_fy, 0.1), + (tw_cx, p_cx, 0.1), + (tw_cz, p_cz, 0.1), + (tw_ry, p_ry, 0.1), ], timeout=20.0, + ) + def move_post_tube(self, pos): # post_sample_tube="{'x_up': -0.2, 'y_up': 0.0, 'x_down': 0.0, 'y_down': 0.0, 'x_out_delta': 0.0, 'y_out_delta': 0.0, 'z_in': 0.0, 'z_out': 0.0}" app=QApplication.instance()