diff --git a/RIXSconfig/ESF_RIXS.py b/RIXSconfig/ESF_RIXS.py new file mode 100755 index 0000000..25645ea --- /dev/null +++ b/RIXSconfig/ESF_RIXS.py @@ -0,0 +1,1033 @@ +#!/usr/bin/env python +# *-----------------------------------------------------------------------* +# | | +# | Copyright (c) 2024 by Paul Scherrer Institute (http://www.psi.ch) | +# | | +# | Author Thierry Zamofing (thierry.zamofing@psi.ch) | +# *-----------------------------------------------------------------------* + +""" +SwissFEL Athos spectrometer + +bitmask for simulation: + 0x01: EPICS motors + 0x02: + 0x04: + 0x08: + 0x10: + 0x20: + 0x40: + 0x80: + +""" + + +from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QSlider, QLineEdit,\ + QCheckBox, QHBoxLayout, QVBoxLayout, QGroupBox, QGridLayout, QComboBox +from PyQt5.QtGui import QPainter, QColor, QPen, QBrush, QPolygon, QTransform +from PyQt5.QtCore import QPoint, QPointF, Qt + +from pyqtgraph.Qt import QtCore, QtGui +import PyQt5.QtGui as QtGui +import PyQt5.QtCore as QtCore +import PyQt5.QtWidgets as QtW +from PyQt5.uic import loadUiType +import numpy as np +from ESF_RIXSgeometry import VLSgrating +from ESF_RIXSconfig import Config +from ESF_RIXSgirder import WndGirderRIXS + +import sys, logging, copy +import epics + +_log=logging.getLogger(__name__) + +def obj_info(obj,p=''): + print(f"{p}obj_info:{obj}") + try: + pos=obj.pos() + print(f"{p}pos:({pos.x():.6g},{pos.y():.6g})") # in coordinate value on the scene (no change by zooming) + except AttributeError: + pass + try: + sz=obj.size() + print(f"{p}size:({sz.x():.6g},{sz.y():.6g})") # in coordinate value on the scene (no change by zooming) + except AttributeError: + pass + try: + for k, v in (('Viewport', obj.viewport()), ('Window', obj.window())): + print( + f"{p}{k} (x,y)(w,h):({v.x():.6g},{v.y():.6g})({v.width():.6g},{v.height():.6g})") # in coordinate value on the scene (no change by zooming) + except AttributeError: + pass + try: + scnPos=obj.scenePos() + print(f"{p}scenePos:({scnPos.x():.6g},{scnPos.y():.6g})") # in pixel on the scene (changes by zooming) + except AttributeError: + pass + try: + if type(obj)==QTransform: + t=obj + else: + t=obj.transform() + print(f"{p}QTransform:{t.m11():8.5g} {t.m12():8.5g} {t.m13():8.5g}") + print(f"{p} {t.m21():8.5g} {t.m22():8.5g} {t.m23():8.5g}") + print(f"{p} {t.m31():8.5g} {t.m32():8.5g} {t.m33():8.5g}") + except AttributeError: + pass + + + + +#Ui_MainWindow, QMainWindow = loadUiType("graphEx1.ui") +#class RIXSgrating(QMainWindow, Ui_MainWindow): +# def __init__(self, ): +# super(RIXSgrating, self).__init__() +# self.setupUi(self) + + +class WndMainRIXS(QWidget): + def __init__(self): + super().__init__() + + self._param={ + 'ofs':(500, 500), # location of drawings + 'difrBeamPaint':4, # mode to plot the difracted beam + } + self.initUI() + + def initUI(self): + self.setGeometry(100, 100, 350, 350) + app=QApplication.instance() + dev=app._dev + self.setWindowTitle(f'{dev._name} main') + self._wDevs=w=QWidget(self) + w.move(10, 10) + lv=QVBoxLayout(w) + lv.addWidget(w) + + for pb in ('Visualize','Vars'): + w=QPushButton(pb) + #w.clicked.connect(self.OnOpenChildWnd + w.clicked.connect(lambda dummy,pb=pb: self.OnOpenChildWnd(pb)) + lv.addWidget(w) + + w=QGroupBox(dev._name, self) + lv.addWidget(w) + lg=QGridLayout(w) + row=0 + + t='energy' + w=QLabel(t); + lg.addWidget(w, row, 0) + w=QLineEdit(t, objectName=t); + lg.addWidget(w, row, 1) + w.returnPressed.connect(lambda w=w:self.OnEnergyChanged(w)) + w=QLabel('out of\nspecs',objectName='oos');w.setStyleSheet("background-color:yellow;color:red;font-weight: bold;font-size: small;") + #https://stackoverflow.com/questions/10794532/how-to-make-a-qt-widget-invisible-without-changing-the-position-of-the-other-qt + sp=w.sizePolicy();sp.setRetainSizeWhenHidden(True);w.setSizePolicy(sp)#avoid resizing if not visible + w.setVisible(False) + lg.addWidget(w, row, 2) + + row+=1; + key, rng, tk, pos=('energy', (200, 2000), 50, 60) + w=QSlider(QtCore.Qt.Horizontal, objectName=key) + w.setFixedWidth(200); + w.setMinimum(rng[0]); + w.setMaximum(rng[1]) + w.setTickPosition(QSlider.TicksBelow); + w.setTickInterval(tk) + #lv.addWidget(w) + lg.addWidget(w, row, 0, 1, 2) + w.valueChanged.connect(lambda val, w=w:self.OnEnergyChanged(w, val)) + + row+=1 + t='probe' + w=QLabel(t); + lg.addWidget(w, row, 0) + w=QComboBox(objectName=t); + lg.addWidget(w, row, 1) + for i, tx in enumerate(tuple(dev._probes.keys())): + w.insertItem(i, tx) + w.currentIndexChanged.connect(lambda val, w=w:self.OnEnergyChanged(w,val)) + w=QCheckBox('fix', objectName=t) + w.clicked.connect(lambda val, w=w:self.OnEnergyChanged(w, val)) + lg.addWidget(w, row, 2) + row+=1 + self.show() + + def OnOpenChildWnd(self,wnd): + app=QApplication.instance() + if wnd=='Visualize': + try: + wnd=app._wndVisualize + except AttributeError as e: + app._wndVisualize=WndVisualize() + else: + wnd.show() + elif wnd=='Vars': + try: + wnd=app._wndVars + except AttributeError as e: + app._wndVars=WndVars() + else: + wnd.show() + else: + _log.error(f'unknown window{wnd}') + + def OnEnergyChanged(self,w,*args): + #_log.debug(f'OnEnergyChanged: {w} {args}') + wGrp=w.parent() + app=QApplication.instance() + #wCb=self._wdGrpEnergy.findChild(QComboBox, 'Grating') + wLeEnergy=wGrp.findChild(QLineEdit, 'energy') + wSlEnergy=wGrp.findChild(QSlider, 'energy') + wFix=wGrp.findChild(QCheckBox, 'probe') + t=type(w) + if t==QLineEdit: + try: + e=float(w.text()) + except ValueError as e: + return + wSlEnergy.blockSignals(True) + wSlEnergy.setValue(int(e)) + wSlEnergy.blockSignals(False) + elif t==QComboBox: + _log.info(f'cb:{args}') + app=QApplication.instance() + try: + e=float(wLeEnergy.text()) + except ValueError as e: + return + elif t==QSlider: + e=args[0] + wLeEnergy.setText(f'{e:.4g}') + elif t==QCheckBox: + _log.info(f'fix:{args}') + else: + raise(TypeError(f'wrong type: {t}')) + try: e # is there an energy to set? + except UnboundLocalError as er: pass + else: + self.setEnergy(e,wFix.isChecked(),wGrp) + + + def setEnergy(self,energy,fix,wGrp): + app=QApplication.instance() + dev=app._dev + vlsg=app._vlsg + + wcbProbe=wGrp.findChild(QComboBox, 'probe') + wLbOOS=wGrp.findChild(QLabel, 'oos') + k=wcbProbe.currentText() + probe=dev._probes[k] + try: + eMin,eMax=Config._lutProbes[probe['probe']]['e'] + except KeyError as e: + eMin=eMax=0 + if fix: + wLbOOS.setVisible(energyeMax) + else: + if energyeMax: + probe=None + _log.info('try to find a better probe') + for idx,p in enumerate(dev._probes.values()): + try: + eMin, eMax=Config._lutProbes[p['probe']]['e'] + except KeyError as e: + #_log.error(f'no energy range for probe: {i}') + continue + if energy>=eMin and energy<=eMax: + wcbProbe.blockSignals(True) + wcbProbe.setCurrentIndex(idx) + wcbProbe.blockSignals(False) + probe=p + break + if probe is None: + wLbOOS.setVisible(True) + _log.warning(f'found no probe for energy {energy}') + return + else: + wLbOOS.setVisible(False) + try: + vlsg.setup(probe['probe']) + except KeyError as e: + _log.error(f'can not find probe: {repr(e)}') + return + geo=vlsg.energy2geometry(energy,probe) + dev.setGeometry(geo) + try: + dev.geometry2motor() + except ValueError as e: + _log.warning(f'{e}') + + + try: + wndCld=app._wndVisualize + except AttributeError as e: + pass + else: + wndCld.updateDevice(dev) + try: + wndCld=app._wndVars + except AttributeError as e: + pass + else: + wndCld.updateDevice(dev) + + +class RIXSdevice(): + _lutDifrBeamPaint=( # (number of difr beam,draw mode,alpha,width) + (4, 2, 190, 0), + (4, 1, 200, 0), + (32, 1, 120, 0), + (8, 1, 196, 0), + (32, 0, 120, 3), + (32, 2, 255, 0), + ) + + def __init__(self,name,**kwargs): + self._name=name + self._paint=p={ + 'mode':4, #difraction beam paint mode + 'ofs':(500, 500), # location of vlsg + 'szG':(200, 5), # size VLS grating + 'szD':(150, 5), # size detector + 'sclTrf':(2**(6/2), 2**(-6/2)), # scaling transfformation [angle, distance] + } + p.update(kwargs) + self._geo=g={ + 'r1':2000, # distance probe grating + 'r2':3500, # distance grating detector + 'aa':88, # grating angle + 'bb':87, # reflection angle + 'cc':22, # detector angle + } + self.setGeometry(g) + + + def setGeometry(self,geo): + self._geo=geo + p=self._paint + sclA,sclD=p['sclTrf'] + p.update({ + 'r1':int(geo['r1']*sclD), + 'r2':int(geo['r2']*sclD), + 'aa':int((90-geo['aa'])*sclA), + 'bb':int((90-geo['bb'])*sclA), + 'cc':int(geo['cc']), + }) + + def geometry2motor(self): + # returns raw motor positions + # offset detector plane to deflected beam: 34deg + + geo=self._geo + r1,r2,aa,bb,cc=geo['r1'],geo['r2'],geo['aa'],geo['bb'],geo['cc'] + mt=gtz=gty1=gty2=grx=gtx=dtz=dty1=dty2=drx=None + degArm=90-aa+90-bb + radArm=np.deg2rad(degArm) + gtz=r1 + grx=90-aa + dtz=np.cos(radArm)*r2 + dty1=dty2=np.sin(radArm)*r2 + drx=90-aa+90-bb+cc-34 + dd=cc-34 # angle of bellow to detector + + geo.update({ + 'mt':mt, + 'gtz':gtz, + 'gty1':gty1, + 'gty2': gty2, + 'grx':grx, + 'gtx':gtx, + 'dtz':dtz, + 'dty1':dty1, + 'dty2':dty2, + 'drx':drx}) + + if degArm>10: + raise(ValueError('angle arm > 10°')) + elif degArm<1: + raise(ValueError('angle arm < 1°')) + elif abs(dd)>15: + raise(ValueError('angle bellow to detector > 15°')) + + def containsPoint(self,point): + try: + pg=self._polygon + except AttributeError: + return False + return pg.containsPoint(point,Qt.OddEvenFill) + + @staticmethod + def plotOrig(qp): + penR=QPen(QtCore.Qt.red, 5, QtCore.Qt.SolidLine) + penG=QPen(QtCore.Qt.green, 5, QtCore.Qt.SolidLine) + pOrig=qp.pen() + qp.setPen(penR) + qp.drawLine(-20, 0, 20, 0) + qp.drawLine( 20, 0, 16, 2) + qp.setPen(penG) + qp.drawLine(0, -20, 0, 20) + qp.drawLine(0, 20, 2, 16) + qp.setPen(pOrig) + + def paint(self,qp): + # qp QPainter to paint on + # ofs caanter to draw + # scl scaling for x and y translation of coordinate systems + # paintMode: mode how to paint the diffraction beam + p=self._paint + r1=p['r1'] + r2=p['r2'] + aa=p['aa'] + bb=p['bb'] + cc=p['cc'] + szG=p['szG'] + szD=p['szD'] + ofs=p['ofs'] + + w=int(max(szG[0], szD[0])/2) + ofs=(max(ofs[0], w+r1), max(w+r2*np.sin((aa+bb)*np.pi/180), ofs[1])) + + # --- prepare transformations --- + # tf0: grating center not rotated + # tfa: grating center rotated angle -aa + # tfab: grating center rotated angle -aa-bb + # tfc: detector center rotated angle -cc + + tf0=QTransform() + tf0.translate(ofs[0], ofs[1]) + tfa=copy.copy(tf0) # center + tfa.rotate(-aa) + tfab=copy.copy(tf0) # center + tfab.rotate(-aa-bb) + tfc=copy.copy(tfab) + tfc.translate(r2,0).rotate(-cc) + + penBk=QPen(QtCore.Qt.black, 0, QtCore.Qt.SolidLine) + penWt=QPen(QtCore.Qt.white, 1, QtCore.Qt.SolidLine) + penYl=QPen(QtCore.Qt.yellow, 1, QtCore.Qt.SolidLine) + penRd=QPen(QtCore.Qt.red, 1, QtCore.Qt.SolidLine) + + # --- visualize --- + #qp.setRenderHints(QPainter.HighQualityAntialiasing) + + pl=[ + QPoint(*tf0.map (-r1-w, -w)), + QPoint(*tf0.map (-r1-w, +w)), + QPoint(*tf0.map ( 0, +w)), + QPoint(*tfab.map( 0, +w)), + QPoint(*tfab.map( r2+w, +w)), + QPoint(*tfab.map( r2+w, -w)), + QPoint(*tf0.map ( -w,-w)), + ] + self._polygon=QPolygon(pl) + + qp.setBrush(QColor(0, 0, 0)) + # plot black background + qp.setTransform(tf0) + qp.drawRect(-r1-w, -w, r1+w, 2*w) + qp.drawEllipse(-w, -w, 2*w, 2*w) + qp.setTransform(tfab) + qp.drawRect(0, -w, r2+w, 2*w) + + # plot beam path + qp.setTransform(tf0) + qp.setCompositionMode(QtGui.QPainter.CompositionMode_Lighten) + qp.setPen(penWt) + qp.setBrush(QColor(255, 80, 0, 128)) + + qp.drawEllipse(-r1-20, -10, 20, 20) # target + qp.drawLine(-r1, 0, 0, 0) # central beam1 + qp.rotate(-aa) + tf1=qp.transform() + qp.drawRect(int(-szG[0]/2), 0, szG[0], szG[1]) # grating + qp.rotate(-bb) + tf2=qp.transform() + qp.drawLine(0, 0, int(r2+szD[0]/2), 0) # central beam2 + qp.translate(r2, 0) + qp.rotate(-cc) + tf3=qp.transform() + qp.drawRect(int(-szD[0]/2), 0, szD[0], szD[1]) # detector + + # beam target to vlsg + qp.setTransform(QtGui.QTransform()) + p0=QPointF(*tf0.map(-r1-10, 10)) + p1=QPointF(*tf1.map(-szG[0]/2, 0)) + qp.drawLine(p0, p1) + p0=QPointF(*tf0.map(-r1-10, -10)) + p1=QPointF(*tf1.map(szG[0]/2, 0)) + qp.drawLine(p0, p1) + + # beam vlsg to detector + # n=32;mode=0;alpha=120;width=3 + n, mode, alpha, width=self._lutDifrBeamPaint[p['mode']] + scl=256/n + p0=QPointF(*tf1.map(-szG[0]/2, 0)) + p1=QPointF(*tf1.map(szG[0]/2, 0)) + + if mode==0: + pen=QtGui.QPen(QtCore.Qt.black, width, QtCore.Qt.SolidLine) + col=QtGui.QColor() + for i in range(n): + p2=QPointF(*tf3.map(szD[0]/2-szD[0]*i/(n-1), 0)) + col.setHsv(int(i*scl), 255, 255, alpha) + pen.setColor(col) + qp.setPen(pen) + qp.drawLine(p0, p2) + qp.drawLine(p1, p2) + if mode==1: + col=QtGui.QColor() + qp.setPen(penBk) + for i in range(n): + p2=QPointF(*tf3.map(szD[0]/2-szD[0]*i/(n-1), 0)) + col.setHsv(int(i*scl), 255, 255, alpha) + qp.setBrush(col) + qp.drawPolygon(p0, p1, p2) + if mode==2: + col=QtGui.QColor() + qp.setPen(penBk) + for i in range(n): + p2=QPointF(*tf3.map(szD[0]/2-szD[0]*(i+1)/n, 0)) + p3=QPointF(*tf3.map(szD[0]/2-szD[0]*i/n, 0)) + col.setHsv(int(i*scl), 255, 255, alpha) + qp.setBrush(col) + qp.drawPolygon(p0, p1, p2, p3) + + qp.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver) + + # draw central beam (if needed) + if mode!=0: + qp.setTransform(tf0) + qp.setPen(QtGui.QPen(QtGui.QColor(0x757780), 2, QtCore.Qt.SolidLine)) + qp.drawLine(-r1, 0, 0, 0) # central beam1 + qp.setTransform(tf2) + qp.drawLine(0, 0, int(r2+szD[0]/2), 0) # central beam2 + + # draw dimensions + g=self._geo + vr1=g['r1'] + vr2=g['r2'] + vaa=90-g['aa'] + vbb=90-g['bb'] + vcc=g['cc'] + + qp.setTransform(QtGui.QTransform()) + qp.setPen(penRd) + p0=QPointF(*tf0.map(-r1-10, +10)) + p1=QPointF(*tf0.map(-r1-10, 0+w)) + qp.drawLine(p0, p1) + p2=QPointF(*tf0.map(0, 0)) + p3=QPointF(*tf0.map(0, 0+w)) + qp.drawLine(p2, p3) + p4=(p1+p3)/2 # text pos + p5=QPointF(*tf2.map(0, 0+w)) + qp.drawLine(p2, p5) + p6=QPointF(*tf2.map(r2, 0)) + p7=QPointF(*tf2.map(r2, 0+w)) + qp.drawLine(p6, p7) + p8=(p5+p7)/2 # text pos + + p9=QPointF(*tf1.map(0, -w)) + qp.drawLine(p2, p9) + s=50 + qp.drawArc(int(p2.x())-s, int(p2.y())-s, 2*s, 2*s, 180*16, int(aa*16)) + s1=s*np.cos(aa*np.pi/180); + s2=s*np.sin(aa*np.pi/180) + qp.drawArc(int(p2.x())-s, int(p2.y())-s, 2*s, 2*s, int(aa*16), int(bb*16)) + + qp.drawArc(int(p6.x())-s, int(p6.y())-s, 2*s, 2*s, int((aa+bb)*16), int(cc*16)) + p10=QPointF(*tf2.map(r2+20, 0)) # text pos + + qp.setPen(penYl) + sclTrf=p['sclTrf'] + qp.drawText(p0+QPointF(20-w, 10-w), f'scl(ang;{sclTrf[0]:0.2g},dst:{sclTrf[1]:0.2g})') + + qp.drawText(p4+QPointF(-40, -10), f'R1={vr1:.5g}mm') + # qp.setTransform(QtGui.QTransform()) + qp.translate(p8); + qp.rotate(-aa-bb) + qp.drawText(-40, -10, f'R2={vr2:.5g}mm') + qp.setTransform(QtGui.QTransform()) + qp.translate(p2); + qp.rotate(-aa) + qp.drawText(20-int(szG[0]/2), 20, f'aa={vaa:.5g}°') + qp.drawText(20, 20, f'bb={vbb:.5g}°') + qp.setTransform(QtGui.QTransform()) + qp.translate(p10); + qp.rotate(-aa-bb) + qp.drawText(0, 20, f'cc={vcc:.5g}°') + + #mouse move polygon + #qp.setTransform(QTransform()) + #qp.setPen(penRd) + #qp.drawPolygon(self._polygon) + #origin crosses + #for tf in (tf0,tfa,tfab,tfc):#,tfg,tfs):#,tfc,tfs): + # qp.setTransform(tf);self.plotOrig(qp) + +class WndVisualize(QWidget): + def __init__(self): + super().__init__() + self.initUI() + + def initUI(self): + self.setGeometry(560, 100, 1300, 800) + self.setWindowTitle('Visualize') + app=QApplication.instance() + dev=app._dev + self._wdGrpDraw=w=QGroupBox(dev._name,self) + w.move(10,10) + row=0 + lg=QGridLayout(w) + pDev=dev._paint + for key, rng, tk in ( + ('r1', (50, 800), 50), + ('r2', (50, 800), 50), + ('aa', (0, 90), 5), + ('bb', (0, 90), 5), + ('cc', (0, 180), 5), + ('mode',(0,len(RIXSdevice._lutDifrBeamPaint)-1),1), + ('sclA', ( -8, 8), 1), + ('sclD', (-8, 8), 1),): + + wLb=QLabel(key) + wSl=QSlider(QtCore.Qt.Horizontal,objectName=key) + wSl.setFixedWidth(200);wSl.setMinimum(rng[0]);wSl.setMaximum(rng[1]) + if key.startswith('scl'): + if key[-1]=='A': + v=pDev['sclTrf'][0] + else: + v=pDev['sclTrf'][1] + v=int(round(np.log2(v)*2)) + else: + v=pDev[key] + wSl.setValue(v) + wSl.setTickPosition(QSlider.TicksBelow);wSl.setTickInterval(tk) + wSl.valueChanged.connect(lambda val,key=key: self.sldChanged(key,val)) + + lg.addWidget(wLb, row, 0) + lg.addWidget(wSl, row, 1);row+=1 + self.show() + + def destroy(self, destroyWindow, destroySubWindows): #overloaded function + _log.info('destroy') + + def closeEvent(self, event): #overloaded function + _log.info('closeEvent') + + def sldChanged(self,key,val,*args,**kwargs): + app=QApplication.instance() + dev=app._dev + p=dev._paint + if key.startswith('scl'): + wGrp=self._wdGrpDraw + if key[-1]=='A': + p['sclTrf']=(2**(val/2),p['sclTrf'][1]) + else: + p['sclTrf']=(p['sclTrf'][0],2**(val/2)) + dev.setGeometry(dev._geo) + else: + p[key]=val + g=dev._geo + if key in ('r1','r2'): + sclD=p['sclTrf'][1] + g[key]=val/sclD + elif key in ('aa', 'bb'): + sclA=p['sclTrf'][0] + g[key]=90-val/sclA + else: + g[key]=val + self.update() + + def mouseReleaseEvent(self, a0): + try: + del self._mouseDrag + except AttributeError: + pass + + def mousePressEvent(self, a0): + app=QApplication.instance() + mousePos=a0.pos() + print(a0.type) + if a0.type()!=QtGui.QMouseEvent.MouseButtonPress: + return + wGrp=self._wdGrpDraw + #if wGrp.underMouse(): #draging sliders? + if wGrp.geometry().contains(mousePos): + self._mouseDrag={'obj':wGrp, 'start':mousePos} + return + dev=app._dev + if dev.containsPoint(mousePos): + self._devSel=dev + wGrp=self._wdGrpDraw + wGrp.setTitle(dev._name) + devP=dev._paint + for wSl in wGrp.findChildren(QSlider): + #_log.info(wSl) + key=wSl.objectName() + if key.startswith('scl'): + if key[-1]=='A': + v=devP['sclTrf'][0] + else: + v=devP['sclTrf'][1] + v=int(round(np.log2(v)*2)) + else: + v=devP[key] + wSl.setValue(v) + + self._mouseDrag={'obj':dev,'start':(mousePos,dev._paint['ofs'])} + try: + _log.info(f'{self._mouseDrag}') + except AttributeError: + _log.info(f'no object to drag') + + + def mouseMoveEvent(self, a0): + try: + md=self._mouseDrag + except AttributeError: + return + obj=md['obj'] + s=md['start'] + if obj==self._wdGrpDraw: + p=a0.pos() + md['start']=p + p=obj.geometry().topLeft()+p-s + obj.move(p) + return + p=a0.pos() + ofs=QPoint(*s[1])+p-s[0] + _log.info(f'{p} {ofs}') + obj._paint['ofs']=(ofs.x(),ofs.y()) + self.update() + + def paintEvent(self, e): + qp = QPainter() + qp.begin(self) + qp.setRenderHints(QPainter.HighQualityAntialiasing) + app=QApplication.instance() + dev=app._dev + app._dev.paint(qp) + qp.end() + + def updateDevice(self, dev): + self._devSel=dev + devP=dev._paint + wGrp=self._wdGrpDraw + wGrp.setTitle(dev._name) + for wSl in wGrp.findChildren(QSlider): + # _log.info(wSl) + key=wSl.objectName() + if key.startswith('scl'): + if key[-1]=='A': + v=devP['sclTrf'][0] + else: + v=devP['sclTrf'][1] + v=int(round(np.log2(v)*2)) + else: + v=devP[key] + wSl.blockSignals(True) + wSl.setValue(v) + wSl.blockSignals(False) + self.update() + + def OnEnergyChanged(self,w,*args): + #_log.debug(f'OnEnergyChanged: {w} {args}') + app=QApplication.instance() + #wCb=self._wdGrpEnergy.findChild(QComboBox, 'Grating') + wLe=self._wdGrpEnergy.findChild(QLineEdit, 'Energy') + wSl=self._wdGrpEnergy.findChild(QSlider, 'energy') + t=type(w) + if t==QLineEdit: + try: + e=float(w.text()) + except ValueError as e: + return + wSl.blockSignals(True) + wSl.setValue(int(e)) + wSl.blockSignals(False) + elif t==QComboBox: + app=QApplication.instance() + vlsg=app._vlsg + vlsg.setup(w.currentText()) + try: + e=float(wLe.text()) + except ValueError as e: + return + elif t==QSlider: + e=args[0] + wLe.setText(f'{e:.4g}') + else: + raise(TypeError(f'wrong type: {t}')) + self.setEnergy(e) + + def OnEvent(self,*args,**kwargs): + #test event + print(f'OnEvent: {args} ,{kwargs}') + + def setEnergy(self,val): + app=QApplication.instance() + wGrating=self._wdGrpEnergy.findChild(QComboBox, 'Grating') + wEnergy=self._wdGrpEnergy.findChild(QLineEdit, 'Energy') + wEnergy.setText(f'{val:.4g}') + + vlsg=app._vlsg + vlsg.setup(wGrating.currentText()) + self._vlsg_geo=geo=vlsg.energy2geometry(val) + + grp=self._wdGrpGeometry + for k,v in geo.items(): + w=grp.findChild(QLineEdit, k) + w.setText(f'{v:.6g}') + + self._vlsg_raw=raw,err=vlsg.geometry2raw(**geo) + if err is not None: + _log.error(str(err)) + self._vlsg_raw=raw + grp=self._wdGrpRaw + for k,v in raw.items(): + w=grp.findChild(QLineEdit, k) + if v is None: + w.setText('(null)') + else: + w.setText(f'{v:.6g}') + self.update() + + +class WndVars(QWidget): + def __init__(self): + super().__init__() + self.initUI() + + def initUI(self): + app=QApplication.instance() + self.setGeometry(100, 500, 550, 550) + self.setWindowTitle('Vars') + + self._wdGrpGeometry=w=QGroupBox("Geometry",self) + w.move(10,10) + l=QGridLayout(w) + lut={'aa':'\u03B1', 'bb':'\u03B2', 'cc':'\u03B3'} + for i,t in enumerate(('r1','r2','aa','bb','cc',)): + tl=lut.get(t,t) + w=QLabel(tl) + l.addWidget(w, i,0) + w=QLineEdit(t,objectName=t) + l.addWidget(w, i,1) + + self._wdGrpMotor=w=QGroupBox("Motors",self) + w.move(200,10) + #w.setFixedSize(400,42+32*14) + w.setFixedWidth(330) + l=QGridLayout(w) + + prefix='SATES30-RIXS:' + motors=( + ('MT' , 'Mask translation'), + ('GTZ' , 'Grating translation along Z-axis (along beam)'), + ('GTY1', 'Grating translation along Y-axis (height) Wedge leveller 1'), + ('GTY2', 'Grating translation along Y-axis (height) Wedge leveller 2'), + ('GRX' , 'Grating rotation around X-axis'), + ('GTX' , 'Grating translation along X-axis'), + ('DTZ' , 'Detector Translation along Z-axis'), + ('DTY1', 'Detector Translation along Y-axis'), + ('DTY2', 'Detector Translation along Y-axis'), + ('DRX' , 'Detector Rotation around X-axis (ɣ) '), + + ) + self._epicsDevDct=eDevDct=dict() + simMot=app._args.sim&0x01 + for i,m in enumerate(motors): + tl,inf=m + t=tl.lower() + wCb=QCheckBox(t,objectName=t) + l.addWidget(wCb, i,0) + w=QLineEdit(t,objectName=t) + w.setToolTip(inf) + l.addWidget(w, i,1) + + v=f"{t}" + w=QLabel(v,objectName=t) + l.addWidget(w, i, 2) + + rcNm=prefix+'MOT_'+tl+'.' + if simMot: + eDevDct[t]=eDev=SimEpicsDevice(rcNm, attrs=('VAL', 'RBV', 'DESC')) + else: + eDevDct[t]=eDev=epics.Device(rcNm, attrs=('VAL', 'RBV', 'DESC'), timeout=.5) # for safty: removed 'VAL', + eDev.add_callback('RBV', self.update_label) + + w=QPushButton('move motors') + w.clicked.connect(self.btnMoveMotors) + l.addWidget(w, i+1, 1) + self.show() + + def updateDevice(self, dev): + grpGeo=self._wdGrpGeometry + grpMot=self._wdGrpMotor + + geo=dev._geo + for k, v in geo.items(): + wLe=grpGeo.findChild(QLineEdit, k) + if wLe is not None: + wLe.setText(f'{v:.6g}') + else: + self.update_label(key=k,val=v) + self.update() + + def update_label(self, **kwargs): + #_log.info(f'update_label...{kwargs}') + try: + k=kwargs['pvname'].split(':',1)[1].split('.',1)[0].split('_',1)[1].lower() + except KeyError as e: + k=kwargs['key'] + v=kwargs['val'] + else: + _log.debug(f'monitoring: {k}') + app=QApplication.instance() + v=None + dev=app._dev + try: + v=dev._geo[k] + except (KeyError,AttributeError) as e: + pass + grpMot=self._wdGrpMotor + wLe=grpMot.findChild(QLineEdit, k) + if wLe is not None: + _log.debug(f'{k}:{v}') + wCb=grpMot.findChild(QCheckBox, k) + wLb=grpMot.findChild(QLabel, k) + eDev=self._epicsDevDct[k] + rbv=eDev.RBV + if v is None: + v=f"{rbv:.5g}" + else: + wLe.setText(f'{v:.3f}') # {v:.6g} + d=abs(v-rbv) + b=bool(d>0.001) + wCb.setChecked(b) + if b: + c='ff8080' + else: + c='60a060' + v=f"{rbv:.5g} ({d:+.5g})" + wLb.setText(v) + + def btnMoveMotors(self): + grpMot=self._wdGrpMotor + lg=grpMot.layout() + col=lg.columnCount() + eDevs=self._epicsDevDct + + for k,eDev in eDevs.items(): + wCb=grpMot.findChild(QCheckBox, k) + if wCb.isChecked(): + wLe=grpMot.findChild(QLineEdit, k) + v=float(wLe.text()) + if eDev is not None: + _log.info(f'move motor {k} to {v}') + if type(eDev)==SimEpicsDevice: + eDev.VAL=v # move motor #comment out for safety + self.update_label(key=k,val=v) + else: + #eDev.VAL=v # move motor #comment out for safety + pass + +# ->>>> https://pyepics.github.io/pyepics/devices.html >>> USE THIS +#[satesf-cons-03 ~]$ /opt/gfa/python-3.8/latest/bin/ipython3 +#import CaChannel +#import epics + +#capv=CaChannel.CaChannel('SATOP11-PSAS079:MOT_DDF_X.VAL') -> THIS IS REALLY BASIC... +#capv.state() +#m=epics.Motor('SATOP11-PSAS079:MOT_DDF_X') +#pv=pvm.PV('RBV') +#dev=epics.Device('SATOP11-PSAS079:MOT_DDF_X.', attrs=('VAL', 'RBV', 'DESC')) +#dev.RBV + +class SimEpicsDevice(): #behaves similar to epics.Device + def __init__(self, prefix='', attrs=None, + nonpvs=None, delim='', timeout=None, + mutable=True, aliases=None, with_poll=True): + self._prefix=prefix + self._pvs={} + for k in attrs: + self._pvs[k]=0 + + def __getattr__(self, attr): + #if self._pvs: + if '_pvs' in self.__dict__: + return self._pvs[attr] + else: + _log.debug(attr) + raise AttributeError + + def __setattr__(self, attr, val): + if attr in ('_pvs','_prefix'): + self.__dict__[attr]=val + elif attr in self._pvs: + self._pvs[attr]= val + if attr=='VAL' and 'RBV' in self._pvs: + self._pvs['RBV']=val + else: + self.__dict__[attr]=val + + def __repr__(self): + "string representation" + attr=[] + for k,v in self._pvs.items(): + attr.append(f'{k}: {v}') + s=f"':{ + # 'R': + # 'a0':,'a1':,'a2':,'a3': # polynome for line density of the mirror + # 'eqs'{ # + # 'k': , + # 'lut': [# lookup table for starting guess values at a given energy for equation solver + # ( , , , ), + # ( ... ), + # ], + # }, + # }, + # '':{ + # ... + # } + #} + + _lutProbes={ + 'g80_1':{ # grating-key { radius of the grating mirror, polynome for line density of the mirror + 'R':65144.054, 'a0':1160.759,'a1':0.3409,'a2':-3.74E-5,'a3':1.98E-7, + 'e':(300,1600), #optional energy range min max + 'esp':{'k': 1, # equation solving parameters { deflection order + 'lut': [ # lookup table for starting guess values at a given energy for equation solver + (300, 1193, 4127, np.deg2rad(88)), # energy(eV), r1(mm), r2(mm), alpha(rad), + ],},}, + 'g80_2':{ + 'R':65144.054, 'a0':1160.759,'a1':0.3409,'a2':-3.74E-5,'a3':1.98E-7, + 'esp':{'k': 2, + 'lut': [ + (350, 1045, 4557, np.deg2rad(88)), + (500, 1398, 3763, np.deg2rad(88)), + ],},}, + 'g80_3':{ + 'R':65144.054, 'a0':1160.759,'a1':0.3409,'a2':-3.74E-5,'a3':1.98E-7, + 'esp':{'k': 3, + 'lut': [ + (400, 988, 4778, np.deg2rad(88)), + (800, 1156, 4218, np.deg2rad(88)), + ],},}, + 'gtst_1':{ + 'R':65144.054, 'a0':1360.759,'a1':0.3409,'a2':-3.74E-5,'a3':1.98E-7, + 'e':(1500,1900), #optional energy range min max + 'esp':{'k': 1, + 'lut': [ + (300, 1133, 4468, np.deg2rad(88)), + ],},}, + } + + _spectrometer={ + 'vis':{'ofs':(500, 500),}, + 'probes':{ # upstream 2x6 + '80meV' :{'probe':'g80_1','txy':(0., 0.),}, # txy='sample motor positions for probe' + '80meV_k=2' :{'probe':'g80_2','txy':(0., 1.),}, # ofs= offsets for various motors + '80meV_k=3' :{'probe':'g80_3','txy':(0., 2.),}, + 'gratingTest1':{'probe':'gtst_1','txy':(0., 3.),}, + }, + } + diff --git a/RIXSconfig/ESF_RIXSgeometry.py b/RIXSconfig/ESF_RIXSgeometry.py new file mode 100644 index 0000000..4c25598 --- /dev/null +++ b/RIXSconfig/ESF_RIXSgeometry.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python +# *-----------------------------------------------------------------------* +# | | +# | Copyright (c) 2022 by Paul Scherrer Institute (http://www.psi.ch) | +# | | +# | Author Thierry Zamofing (thierry.zamofing@psi.ch) | +# *-----------------------------------------------------------------------* +''' +coordinate systems, optical center, xray axis, pixel sizes etc. + +ppm= pixel per mm +update_pix2pos + +modes: + 0x01: update_pix2pos + 0x02: update_optical_center +''' +import logging +_log=logging.getLogger(__name__) + +import numpy as np +from scipy.optimize import fsolve +from ESF_RIXSconfig import Config + + + +class VLSgrating: #Gratings with Variable Line Density + def __init__(self): + pass + + def setup(self,key): + self._param=Config._lutProbes[key] + + # ---------- eugenio calculation functions ---------- + def solve_focus_equ(self, eV): + esp=self._param['esp'] + p0=esp['lut'][0][1:] # fsolve starting values for [r1, r2, alpha] + esp['En']=eV # Update the dictionary with the energy at which I want to calculate the positions + esp['Lam_nm']=1239.842/eV # Convert energy to wavelength + pn=fsolve(self.equations, p0, maxfev=100000000) + #es['r1'], es['r2'], es['alpha']=pn + return pn + + def equations(self, pn): # system of 3 equations for 3 parameters + return (self.Eq1(*pn), self.Eq2(*pn), self.Eq4(*pn)) + + def Eq1(self, r1, r2, alpha): + p=self._param;esp=p['esp'] + a1, R, a0, k, Lam_nm=p['a1'], p['R'], p['a0'], esp['k'], esp['Lam_nm'] + beta=self.beta(r1, r2, alpha) + return np.cos(alpha)**2/r1+np.cos(beta)**2/r2-\ + (np.cos(alpha)+np.cos(beta))/R-a1*k*Lam_nm/1e6 + + def Eq2(self, r1, r2, alpha): + p=self._param;esp=p['esp'] + a0, a1, a2, R, k, Lam_nm=p['a0'], p['a1'], p['a2'], p['R'], esp['k'], esp['Lam_nm'] + beta=self.beta(r1, r2, alpha) + return np.sin(alpha)/r1*(np.cos(alpha)**2/r1-np.cos(alpha)/R)-\ + np.sin(beta)/r2*( + np.cos(beta)**2/r2-np.cos(beta)/R)+2*a2*k*Lam_nm/1e6/3 + + def Eq4(self, r1, r2, alpha): + p=self._param;esp=p['esp'] + a3, R, k, Lam_nm, a0=p['a3'], p['R'], esp['k'], esp['Lam_nm'], p['a0'] + beta=self.beta(r1, r2, alpha) + return 4*np.sin(alpha)**2/r1**2*(np.cos(alpha)**2/r1-np.cos(alpha)/R)\ + -1/r1*(np.cos(alpha)**2/r1-np.cos(alpha)/R)**2+1/R**2*(1/r1-np.cos(alpha)/R)\ + +4*np.sin(beta)**2/r2**2*( + np.cos(beta)**2/r2-np.cos(beta)/R)\ + -1/r2*(np.cos(beta)**2/r2-np.cos(beta)/R)**2\ + +1/R**2*(1/r2-np.cos(beta)/R)-2*a3*k*Lam_nm/1e6 + + def beta(self, r1, r2, alpha): # calculate beta as a function of alpha, energy, and diffraction order (k) + p=self._param;esp=p['esp'] + a0, k, Lam_nm=p['a0'], esp['k'], esp['Lam_nm'] + return np.arcsin(np.sin(alpha)-a0*k*Lam_nm/1e6) + + def gamma(self, r1, r2, alpha): # compute detector lean angle in respect of the r2 arm + p=self._param;esp=p['esp'] + R, a0, a1=p['R'], p['a0'], p['a1'] + beta=self.beta(r1, r2, alpha) + return np.arctan(np.cos(beta)/(2*np.sin(beta)-r2*(np.tan(beta)/R+a1/a0))) + + + def energy2geometry(self, energy,probe): + # mode raytrace or interpolate lut + # prec precision requitements (precision iteration) + # returns R1,R2,aa,bb,cc + pn=self.solve_focus_equ(energy) + r1, r2, alpha=pn + beta=self.beta(*pn) + gamma=self.gamma(*pn) + geo={'r1':r1,'r2':r2,'aa':np.rad2deg(alpha), 'bb':np.rad2deg(beta), 'cc':np.rad2deg(gamma)} + return geo + +if __name__=="__main__": + import argparse + + logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ') + + #(h, t)=os.path.split(sys.argv[0]);cmd='\n '+(t if len(h)>20 else sys.argv[0])+' ' + #exampleCmd=('', '-m0xf -v0') + epilog=__doc__ #+'\nExamples:'+''.join(map(lambda s:cmd+s, exampleCmd))+'\n' + parser=argparse.ArgumentParser(epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("-m", "--mode", type=lambda x: int(x,0), help="mode (see bitmasks) default=0x%(default)x", default=0xff) + + args=parser.parse_args() + _log.info('Arguments:{}'.format(args.__dict__)) + + np.set_printoptions(suppress=True) + np.set_printoptions(linewidth=196) + + if args.mode&0x01: + + + vlsg=VLSgrating() + print(f'{"grating":>7s} | {"energy(eV)":>10s} | {"r1(mm)":>8s} | {"r2(mm)":>8s} | {"aa(°)":>5s} | {"bb(°)":>5s} | {"cc(°)":>5s}') + for gr,eV in ( + ('g80_1',500), + ('g80_2',500), + ('g80_3',500), + ('gtst_1',500), + ): + vlsg.setup(gr) + geo=vlsg.energy2geometry(eV) + print('{gr:>7s} | {eV:10.4g} | {r1:8.2f} | {r2:8.2f} | {aa:5.2f} | {bb:5.2f} | {cc:5.2f}'.format(gr=gr,eV=eV,**geo)) + #print(g, vlsg.energy2geometry(e)) + + vlsg.setup('g80_1') + Es=np.arange(250, 1550, 50) + print('') + print(f'{"energy(eV)":>10s} | {"r1(mm)":>8s} | {"r2(mm)":>8s} | {"aa(°)":>5s} | {"bb(°)":>5s} | {"cc(°)":>5s}') + for eV in Es: + geo=vlsg.energy2geometry(eV) + print('{eV:10.4g} | {r1:8.2f} | {r2:8.2f} | {aa:5.2f} | {bb:5.2f} | {cc:5.2f}'.format(eV=eV,**geo)) + #pn=vlsg.solve_focus_equ(eV) + #r1, r2, alpha=pn + #beta=vlsg.beta(*pn) + #gamma=vlsg.gamma(*pn) + #print(f'{eV:10.4g} | {r1:8.2f} | {r2:8.2f} | {np.rad2deg(alpha):5.2f} | {np.rad2deg(beta):5.2f} | {np.rad2deg(gamma):5.2f}') + + +#vlsg.setup('80meV') +#[[ 250. 1137.32196015 4269.702359 88.0898184 83.55888668 36.27529716] +# [ 300. 1193.65841126 4127.21279026 88.10217759 84.07300885 31.7396222 ] +# [ 350. 1247.36978781 4010.83457789 88.10391338 84.46686259 28.55391769] +# [ 400. 1299.07861098 3914.08632128 88.0986373 84.77926418 26.23765458] +# [ 450. 1349.21078537 3832.65392519 88.08846973 85.03343956 24.51153466] +# [ 500. 1398.0721341 3763.52172859 88.0747408 85.24424965 23.20495827] +# [ 550. 1445.89064959 3704.49942068 88.05832818 85.42170416 22.20901965] +# [ 600. 1492.84122971 3653.94623698 88.03983478 85.57282519 21.45157952] +# [ 650. 1539.06120728 3610.60137571 88.01968853 85.70270455 20.88341654] +# [ 700. 1584.66050296 3573.4750186 87.9982015 85.8151371 20.47018758] +# [ 750. 1629.72855701 3541.77572852 87.9756065 85.91301734 20.18757719] +# [ 800. 1674.33907781 3514.86055917 87.95208076 85.99859726 20.01827307] +# [ 850. 1718.5535488 3492.19987783 87.92776146 86.07365932 19.95002982] +# [ 900. 1762.42379391 3473.35198496 87.90275638 86.13963571 19.97440536] +# [ 950. 1805.9938836 3457.94443997 87.87715139 86.19769259 20.08592919] +# [1000. 1849.30170875 3445.66007701 87.85101563 86.24879078 20.28155755] +# [1050. 1892.37950222 3436.22635374 87.82440571 86.29373053 20.5603321 ] +# [1100. 1935.25594973 3429.40718162 87.79736786 86.33318446 20.92317777] +# [1150. 1977.95569753 3424.99649949 87.76994068 86.36772314 21.37282654] +# [1200. 2020.5004814 3422.81321829 87.74215652 86.39783427 21.91383066] +# [1250. 2062.90951189 3422.6972001 87.71404265 86.42393782 22.55267071] +# [1300. 2105.19981239 3424.50599743 87.6856223 86.44639783 23.29795403] +# [1350. 2147.38653997 3428.11221941 87.6569154 86.46553179 24.16071358] +# [1400. 2189.48325951 3433.40137902 87.62793911 86.4816182 25.15482162] +# [1450. 2231.50212122 3440.27009676 87.59870838 86.49490264 26.29753817] +# [1500. 2273.45406713 3448.62463224 87.56923627 86.50560273 27.61022401]] \ No newline at end of file diff --git a/RIXSconfig/ESF_RIXSgirder.py b/RIXSconfig/ESF_RIXSgirder.py new file mode 100644 index 0000000..a7d418f --- /dev/null +++ b/RIXSconfig/ESF_RIXSgirder.py @@ -0,0 +1,138 @@ + +from PyQt5.QtWidgets import QApplication, QWidget +from PyQt5.QtGui import QPainter, QColor +import PyQt5.QtGui as QtGui +import PyQt5.QtCore as QtCore +import numpy as np + +class WndGirderRIXS(QWidget): + _lutColor={0:(255,0,0),1:(0,255,0),2:(255,255,0)} + def __init__(self): + super().__init__() + self._param={'ctr':(200, 300), # location of big chamber + 'rCmb':80, # radius of chamber + 'rTrg':10, # radius of target + 'szSeal': (20,50), + 'szArm': (80,350), + 'aaSeal':70, # RIXS sliding angle + 'aaArm':70, # RIXS arm angle + 'airPads':0, # air pads (off=0 on=1 undef=2) + 'defComp':0, # deformation compensation (off=0 on=1 undef=2) + } + + self.initUI() + + def initUI(self): + self.setGeometry(300, 300, 850, 800) + self.setWindowTitle('RIXS girder') + #label = QLabel('Python', self) + #label.move(50,50) + #b1 = QPushButton("Button1",self) + #b1.move(400,150) + self._wdGrpDraw=w=QtGui.QGroupBox("Drawing",self) + w.move(10,10) + l=QtGui.QVBoxLayout(w) + + sld={} + for key,rng,tk in (('aaSeal',(0,180),5),('aaArm',(0,180),5),('airPads',(0,2),1),('defComp',(0,2),1),): + sl=QtGui.QSlider(QtCore.Qt.Horizontal,objectName=key) + sl.setFixedWidth(200);sl.setMinimum(rng[0]);sl.setMaximum(rng[1]) + sl.setValue(self._param[key]) + sl.setTickPosition(QtGui.QSlider.TicksBelow);sl.setTickInterval(tk) + l.addWidget(sl) + sl.valueChanged.connect(lambda val,key=key: self.sldChanged(key,val)) + sld[key]=sl + + self.show() + + def sldChanged(self,key,val,*args,**kwargs): + p=self._param + #print(key,val) + if key=='aaArm': + p['aaSeal']=v=p['aaSeal']-p['aaArm']+val + wSl=self._wdGrpDraw.findChild(QtGui.QSlider, 'aaSeal') + wSl.blockSignals(True) + wSl.setValue(int(v)) + wSl.blockSignals(False) + sl=QtGui.QSlider(QtCore.Qt.Horizontal,objectName=key) + + p[key]=val + self.update() + + def paintEvent(self, e): + p=self._param + ctr=p['ctr'] + aaSeal=p['aaSeal'] + aaArm=p['aaArm'] + rCmb=p['rCmb'] + rTrg=p['rTrg'] + xa,ya=p['szArm'] + xs,ys=p['szSeal'] + ap=p['airPads'] + dc=p['defComp'] + + + qp = QPainter() + qp.begin(self) + qp.translate(ctr[0],ctr[1]) + tf0=qp.transform() + qp.setPen(QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine)) + qp.setBrush(QColor(155, 155, 155, 128)) + br1=qp.brush() + + + qp.drawEllipse(-rCmb, -rCmb, 2*rCmb, 2*rCmb) # big chamber + qp.drawEllipse(-rTrg, -rTrg, 2*rTrg, 2*rTrg) # target + qp.drawLine(0,-rCmb-50,0,-rTrg) #beam + qp.rotate(-aaSeal) + qp.translate(0,rCmb) + tf1=qp.transform() + xs2=xs//2 + xa2=xa//2 + ya2=ya//2 + d=int(abs(aaSeal-aaArm)*20) + r=min(155+d,255) + gb=max(155-d,0) + qp.setBrush(QColor(r, gb, gb, 255)) + qp.drawRect(-xs2, 0, xs, ys) # seal bellow + + qp.setBrush(br1) + + qp.setTransform(tf0) + qp.rotate(-aaArm) + qp.translate(0,rCmb+ys) + tf2=qp.transform() + qp.drawRect(-xa2, 0, xa, ya) # girder + #qp.drawEllipse(-ya2, 100+150, 20, 20) #pusher left + #qp.drawEllipse( ya2-r, 100+150, 20, 20) #pusher right + + #air pad + r,g,b=WndGirderRIXS._lutColor[ap] + rd=13 + r3=int(rd*np.sqrt(3)) + qp.setBrush(QColor(r, g, b, 255)) + qp.drawEllipse(-xa2-rd, ya-4*rd, 2*rd, 2*rd) #left + qp.drawEllipse(-xa2-rd+r3,ya-3*rd, 2*rd, 2*rd) #left + qp.drawEllipse(-xa2-rd+r3,ya-5*rd, 2*rd, 2*rd) #left + + qp.drawEllipse( xa2-rd, ya-4*rd, 2*rd, 2*rd) #right + qp.drawEllipse( xa2-rd-r3,ya-3*rd, 2*rd, 2*rd) #right + qp.drawEllipse( xa2-rd-r3,ya-5*rd, 2*rd, 2*rd) #right + + qp.drawEllipse( -rd, 3*rd, 2*rd, 2*rd) #grating + qp.drawEllipse( 0, 3*rd-r3, 2*rd, 2*rd) #grating + qp.drawEllipse( -2*rd, 3*rd-r3, 2*rd, 2*rd) #grating + + qp.setBrush(br1) + + #deformation compensation + r,g,b=WndGirderRIXS._lutColor[dc] + rd=14 + qp.setBrush(QColor(r, g, b, 255)) + qp.drawEllipse(-xa2, ya2, 2*rd, 2*rd) #air pad left + qp.drawEllipse( xa2-2*rd, ya2, 2*rd, 2*rd) #air pad right + #qp.drawEllipse( -rd, rd, 2*rd, 2*rd) #air pad grating + qp.setBrush(br1) + qp.end() + +