diff --git a/ModuleFixTarget.py b/ModuleFixTarget.py index 3dfadf2..6a9fa9c 100644 --- a/ModuleFixTarget.py +++ b/ModuleFixTarget.py @@ -276,7 +276,7 @@ class WndFixTarget(QWidget): else: grp=wnd._goTracked go=grp.childItems()[path[0]] - go._param=self.get_param() + go._param=wnd.fix_motion_param(self.get_param()) data=grp.childItems() self._tree.setData(data) diff --git a/psi_device.py b/psi_device.py index c53f453..ffa6136 100644 --- a/psi_device.py +++ b/psi_device.py @@ -16,7 +16,8 @@ def motion_delay_pulses(mode, param): mode 1,3,4: 0 (same pulse) mode 5: twait//tmove mode 6: ssz[0]*ssz[1] (nominal: probe pass in the same aperture order as the pump pass) - mode 7: ssz[0]*ssz[1]*(twait//tmove+1)+twait//tmove (nominal, as mode 6) + mode 7: ssz[0]*ssz[1]*(twait//tmove+1)+twait//tmove + (tmove, twait in ms for mode 5 and 7: shapepath uses round((tmove+twait)/fel_per), its 'shots' doc of mode 7 is wrong) raises KeyError/ValueError/TypeError if a needed parameter is missing or invalid ''' mode=int(mode) @@ -33,8 +34,11 @@ def motion_delay_pulses(mode, param): return 0 def motion_tmove(mode, fel_per_ms): - 'move time, fixed to one FEL pulse (the CTA sequences assume it): ms for mode 5, shots for mode 7' - return {5:fel_per_ms, 7:1}.get(int(mode)) + 'move time in ms for mode 5 and 7, fixed to one FEL pulse (the CTA sequences assume it), else None' + try: + return fel_per_ms if int(mode) in (5, 7) else None + except (TypeError, ValueError): + return None def motion_run_pulses(mode, param, count): ''' diff --git a/swissmx.py b/swissmx.py index 34cc7b9..2438a5c 100755 --- a/swissmx.py +++ b/swissmx.py @@ -1522,7 +1522,7 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): # --- chip type / motion on the main page (experimental parameters) ----------------------------- # The Fix Target parameter text box stays the single source: these fields read and rewrite only - # their keys in it (mode, twait, ssz). tmove is fixed to one FEL pulse (set at the collection). + # their keys in it (mode, tmove, twait, ssz). tmove is fixed to one FEL pulse (fix_motion_param). # Text box and chip type are kept in the settings. MOTION_MODES=((4,'4 continuous'), (5,'5 stop-and-go'), (6,'6 hit-and-return'), (7,'7 stop-and-go hit-and-return')) MOTION_ALLOWED=tuple(m for m,_ in MOTION_MODES) @@ -1552,12 +1552,15 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): cb.setToolTip('motion program (mode) of the Fix Target parameters') cb.currentIndexChanged.connect(self.cb_motion_field_changed) - self._sb_twait=w=QSpinBox() # in pulses: twait is in ms for mode 5 and in shots for mode 7 - w.setRange(0, 10000) - w.setValue(1) - w.setSuffix(' pulses') - w.setToolTip('wait at each aperture in FEL pulses (twait: ms for mode 5, shots for mode 7)\n' - 'the move time is fixed to one FEL pulse') + fel_per=int(cfg.value(AppCfg.DT_MISC)['fel_per']) + self._sb_twait=w=QSpinBox() # ms, snapped to a multiple of fel_per (motion_values) + w.setRange(fel_per, 100000) + w.setSingleStep(fel_per) + w.setValue(fel_per) + w.setSuffix(' ms') + w.setKeyboardTracking(False) # valueChanged on enter/focus out, not at each typed digit + w.setToolTip('twait: wait at each aperture in ms, a multiple of the FEL period\n' + 'tmove is fixed to one FEL period') w.valueChanged.connect(self.cb_motion_field_changed) def spin(pfx): w=QSpinBox() @@ -1604,21 +1607,30 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): mft._cbType.setCurrentIndex(i) mft._txtParam.setText(d.get('param', '')) self.cb_motion_text_changed() - if self._motion_param is not None and 'mode' not in self._motion_param: # make the (allowed) mode explicit + p=self._motion_param + if p is not None and ('mode' not in p or self.fix_motion_param(dict(p))!=p): # explicit mode, fixed tmove self.cb_motion_field_changed() self._tmr_delay=t=QTimer(self) # polls the monitored PV value in the GUI thread (fel_per may change too) t.timeout.connect(self.update_motion_delay) t.start(1000) + def fix_motion_param(self, param): + 'tmove fixed to one FEL pulse for mode 5/7: at add obj, update param and the collection' + tmove=psi_device.motion_tmove(param.get('mode', 1), QApplication.instance()._cfg.value(AppCfg.DT_MISC)['fel_per']) + if tmove is not None: + if param.get('tmove', tmove)!=tmove: + _log.warning(f"tmove {param['tmove']} replaced by the fixed {tmove} ms") + param['tmove']=tmove + return param + def motion_values(self): - 'mode and its motion parameters as shown in the fields (without the fixed tmove)' + 'mode and its motion parameters as shown in the fields (with the fixed tmove)' mode=self._cb_mode.currentData() p={'mode':mode} - if mode==5: - p['twait']=self._sb_twait.value()*QApplication.instance()._cfg.value(AppCfg.DT_MISC)['fel_per'] # ms - elif mode==7: - p['twait']=self._sb_twait.value() # shots + if mode in (5, 7): + p['tmove']=QApplication.instance()._cfg.value(AppCfg.DT_MISC)['fel_per'] + p['twait']=self._sb_twait.value() # ms if mode in (6, 7): p['ssz']=[w.value() for w in self._sb_ssz] return p @@ -1636,9 +1648,7 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): self._cb_mode.setCurrentIndex(self._cb_mode.findData(p.get('mode', 1))) # 1: default of get_scan_param, not allowed -> blank twait=p.get('twait') if isinstance(twait, (int, float)) and p.get('mode') in (5, 7): - if p['mode']==5: - twait/=QApplication.instance()._cfg.value(AppCfg.DT_MISC)['fel_per'] # ms -> pulses - self._sb_twait.setValue(int(twait)) # CTA: twait//tmove + self._sb_twait.setValue(int(twait)) ssz=p.get('ssz') if isinstance(ssz, list) and len(ssz)==2 and all(isinstance(v, int) for v in ssz): for w,v in zip(self._sb_ssz, ssz): @@ -1662,6 +1672,11 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): p.pop(k, None) if self._cb_mode.currentIndex()<0: self._cb_mode.setCurrentIndex(0) + w=self._sb_twait # only multiples of fel_per + fel_per=QApplication.instance()._cfg.value(AppCfg.DT_MISC)['fel_per'] + w.blockSignals(True) + w.setValue(int(max(1, int(w.value()/fel_per+.5))*fel_per)) + w.blockSignals(False) p.update(self.motion_values()) txt.setText(ModuleFixTarget.format_param(p)) # -> cb_motion_text_changed @@ -1710,13 +1725,15 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): show(self._le_runtime, 'n/a', warn, []) return p=self.motion_values() - tmove=psi_device.motion_tmove(mode, fel_per) - if tmove is not None: - p['tmove']=tmove warn=[] - twait=(self._motion_param or {}).get('twait') # as typed in the text box - if mode in (5, 7) and isinstance(twait, (int, float)) and twait%tmove: - warn.append(f'twait {twait:g} is not a multiple of {tmove:g}: the CTA uses twait//tmove = {int(twait//tmove)} pulses') + if mode in (5, 7): # as typed in the text box + tmove=p['tmove'] + txt=self._motion_param or {} + if txt.get('tmove')!=tmove: + warn.append(f'tmove {txt.get("tmove")} in the text: it is fixed to {tmove:g} ms (set when the object is added)') + twait=txt.get('twait') + if isinstance(twait, (int, float)) and twait%tmove: + warn.append(f'twait {twait:g} is not a multiple of {tmove:g}: the CTA uses twait//tmove = {int(twait//tmove)} pulses') n=psi_device.motion_delay_pulses(mode, p) coarse=f'{n} x {fel_per:g} ms' if laser is None: @@ -1820,11 +1837,7 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): QMessageBox.warning(self, "data collection", f"Motion mode {mode} of {param['chip_type']} is not allowed " f"(only {', '.join(map(str, self.MOTION_ALLOWED))}).\nUpdate the object parameters (mode:...).") return - tmove=psi_device.motion_tmove(mode, app._cfg.value(AppCfg.DT_MISC)['fel_per']) - if tmove is not None: - if param.get('tmove', tmove)!=tmove: - _log.warning(f"tmove {param['tmove']} of {param['chip_type']} replaced by the fixed {tmove}") - param['tmove']=tmove # fixed to one FEL pulse + self.fix_motion_param(param) vb=self.vb grp=self._goTracked mft=self._moduleFixTarget @@ -2451,7 +2464,7 @@ object settings: bm_pos=self._goBeamMarker.pos() bm_sz=self._goBeamMarker.size() idx=mft._cbType.currentIndex() - param=mft.get_param() + param=self.fix_motion_param(mft.get_param()) if idx==0: #go=UsrGO.Fiducial(bm_pos+bm_sz/2-(20, 20), (40, 40),(fx,fy,bz)) l=.120