From 2e0600128ce5629fb9e0f2c81d7043ddf1d13c98 Mon Sep 17 00:00:00 2001 From: Thierry Zamofing Date: Wed, 28 Aug 2024 18:13:15 +0200 Subject: [PATCH] total rework of ARESvis --- ARESvis/ARESvis.py | 554 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 423 insertions(+), 131 deletions(-) diff --git a/ARESvis/ARESvis.py b/ARESvis/ARESvis.py index af12257..f8d52ce 100755 --- a/ARESvis/ARESvis.py +++ b/ARESvis/ARESvis.py @@ -7,7 +7,15 @@ # *-----------------------------------------------------------------------* """ -SwissFEL Furka ARES visualization +Furka ARES chamber visualization + +self.pv_angles = [epics.PV("SATES30-ARES:MOT_SRY.RBV"), epics.PV("SATES30-ARES:MOT_DRY.RBV"), epics.PV("SATES30-ARES:MOT_2TRY.RBV")] + +SATES30-ARES:MOT_2TRY.RBV # 2thetha angle +SATES30-ARES:MOT_DRY.RBV # detector angle +SATES30-RIXS:MOT_RY.RBV # sliding seal +SATES30-ARES:MOT_SRY.RBV # sample rotation + bitmask for simulation: 0x01: EPICS motors @@ -21,152 +29,437 @@ bitmask for simulation: """ -import sys, logging + +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 + +import sys, logging, copy import epics _log=logging.getLogger(__name__) -from PyQt5.QtWidgets import QApplication, QWidget, QGroupBox, QVBoxLayout, QSlider -from PyQt5.QtGui import QPainter, QColor, QPen -#import PyQt5.QtGui as QtGui -import PyQt5.QtCore as QtCore -import numpy as np +class ARESdevice(): + _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), + ) -class WndVisualizeARES(QWidget): - _lutColor={0:(255,0,0),1:(0,255,0),2:(255,255,0)} + def __init__(self,name,**kwargs): + self._name=name + self._paint=p={ + 'ofs':(500, 500), # location of device + 'rArm':150, # radius of chamber (RIXS arm) + 'r2Th':120, # radius 2Theta platfform + 'rDet':80, # radius of detector + 'rTrg':10, # radius of target + 'szArm':(20, 50), + 'aaArm':100, # angle RIXS arm + 'aa2Th':110, # angle 2thetha + 'aaDet':120, # angle detector + 'aaTrg':130, # angle target + + 'mode':4, #difraction beam paint mode + 'szG':(200, 5), # size VLS grating + 'szD':(150, 5), # size detector + 'sclTrf':(2**(6/2), 2**(0/2)), # scaling transfformation [angle, distance] + } + # 2thetha angle SATES30-ARES:MOT_2TRY + # detector angle SATES30-ARES:MOT_DRY + # sliding seal SATES30-RIXS:MOT_RY + + 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, 2, QtCore.Qt.SolidLine) + penG=QPen(QtCore.Qt.green, 2, 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 + ofs=p['ofs'] + + rArm=p['rArm'] + r2Th=p['r2Th'] + rDet=p['rDet'] + rTrg=p['rTrg'] + aaArm=p['aaArm'] + aa2Th=p['aa2Th'] + aaDet=p['aaDet'] + aaTrg=p['aaTrg'] + sclTrf=p['sclTrf'] + # --- prepare transformations --- + # tf0: target not rotated + # tfArm: target center rotated angle aaArm + # tf2Th: target center rotated angle tf2Th + # tfDet: target center rotated angle aaDet + # tfTrg: target center rotated angle aaTrg + + tf0=QTransform() + tf0.translate(ofs[0], ofs[1]) + tf0.scale(sclTrf[1],sclTrf[1]) + tfArm=copy.copy(tf0) # center + tfArm.rotate(aaArm) + tf2Th=copy.copy(tf0) # center + tf2Th.rotate(aa2Th) + tfDet=copy.copy(tf0) + tfDet.rotate(aaDet) + tfTrg=copy.copy(tf0) + tfTrg.rotate(aaTrg) + #tfd.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) + penBl=QPen(QtCore.Qt.blue, 1, QtCore.Qt.SolidLine) + penRd=QPen(QtCore.Qt.red, 1, QtCore.Qt.SolidLine) + + # --- visualize --- + #qp.setRenderHints(QPainter.HighQualityAntialiasing) + + # setup and plot dragable region + #pl=[QPoint(*tf0.map (-rArm , -rArm)),QPoint(*tf0.map (-rArm , +rArm+50)),QPoint(*tf0.map (+rArm+50+aaArm, +rArm+50+aa2Th)),QPoint(*tf0.map(+rArm+50+aaDet , -rArm-50)),] + self._polygon=QPolygon([ + QPoint(*tf0.map(-rArm ,-rArm)), + QPoint(*tf0.map(-rArm ,+rArm+100)), + QPoint(*tf0.map(+rArm,+rArm+100)), + QPoint(*tf0.map(+rArm,-rArm)),]) + qp.setBrush(QColor(0, 0, 0,64)) + qp.drawPolygon(self._polygon) + + # plot beam path + qp.setTransform(tf0) + qp.setPen(penBk) + qp.setBrush(QColor(128, 128, 128, 128)) #r,g,b,a + + #circles of rtation + qp.drawEllipse(-rArm, -rArm, 2*rArm, 2*rArm) # ARES chamber + qp.drawEllipse(-r2Th, -r2Th, 2*r2Th, 2*r2Th) # 2theta + qp.drawEllipse(-rDet, -rDet, 2*rDet, 2*rDet) # detector + qp.drawEllipse(-rTrg, -rTrg, 2*rTrg, 2*rTrg) # target + + #beam arrow + qp.setPen(QPen(QtCore.Qt.black, 3, QtCore.Qt.SolidLine)) + qp.drawLine(0,+rArm+100,0,rArm) + qp.drawPolygon(QPolygon([QPoint(0,rArm),QPoint(-5,rArm+20),QPoint(+5,rArm+20),])) + + #self.plotOrig(qp) + #qp.setPen(penRd) + #qp.drawRect(-10, -10+rArm, 20, 20) + + #RIXS-arm devices + qp.setTransform(tfArm) + qp.setPen(QPen(QtCore.Qt.red, 3, QtCore.Qt.SolidLine)) + qp.drawLine(0,rArm,0,rArm+10) + qp.setPen(penBk) + qp.setBrush(QColor(255,0,0,128)) + qp.drawRect(-15, -20+rArm, 5, 50) # foc. mirror 1 + qp.drawRect(+10, -20+rArm, 5, 50) # foc. mirror 2 + + #2-theta devices + qp.setTransform(tf2Th) + qp.setPen(QPen(QtCore.Qt.green, 3, QtCore.Qt.SolidLine)) + qp.drawLine(0,r2Th,0,r2Th+10) + qp.setPen(penBk) + qp.setBrush(QColor(0,255,0,128)) + qp.drawRect(-20, -20+r2Th, 15, 15) # 2th mount sample + + + #detector devices + qp.setTransform(tfDet) + qp.setPen(QPen(QtCore.Qt.blue, 3, QtCore.Qt.SolidLine)) + qp.drawLine(0,rDet,0,rDet+10) + qp.setPen(penBk) + qp.setBrush(QColor(0,0,255,128)) + qp.drawRect(+20, -20+rDet, 10, 15) # detector mount sample + + #target devices + qp.setTransform(tfTrg) + qp.setPen(QPen(QtCore.Qt.cyan, 3, QtCore.Qt.SolidLine)) + qp.drawLine(0,rTrg,0,rTrg+10) + qp.setPen(penBk) + qp.setBrush(QColor(0,255,255,128)) + qp.drawRect(+20, +20+rTrg, 10, 10) # target mount sample + + #qp.setPen(penBk) + #qp.drawRect(-10, -10+rArm, 20, 20) + #self.plotOrig(qp) + #qp.setTransform(tf2Th) + #qp.setPen(penYl) + #qp.drawRect(-10, -10+r2Th, 20, 20) + #self.plotOrig(qp) + #qp.setTransform(tfDet) + #qp.setPen(penBl) + #qp.drawRect(-10, -10+r2Th+20, 20, 20) + #qp.setTransform(tfTrg) + #qp.setPen(penBl) + #qp.drawRect(-10, -10+r2Th+20, 20, 20) + #self.plotOrig(qp) + #qp.setCompositionMode(QtGui.QPainter.CompositionMode_Lighten) + #qp.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver) + #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._param={'ctr':(200, 320), # location of big chamber - 'rCmb':150, # radius of chamber - 'rTrg':10, # radius of target - 'r2Th':100, # radius 2Theta platfform - 'szSeal': (20,50), - 'szArm': (80,250), - '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) - } - # 2thetha angle SATES30-ARES:MOT_2TRY - # detector angle SATES30-ARES:MOT_DRY - # sliding seal SATES30-RIXS:MOT_RY - self.initUI() + super().__init__() + self.initUI() def initUI(self): - self.setGeometry(300, 300, 850, 800) - self.setWindowTitle('ARES visualize') - #label = QLabel('Python', self) - #label.move(50,50) - #b1 = QPushButton("Button1",self) - #b1.move(400,150) - self._wdGrpDraw=w=QGroupBox("ARES chamber",self) - w.move(10,10) - l=QVBoxLayout(w) + 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 ( + ('aaArm',(0,360,), 30), # angle ARES sliding seal + ('aa2Th',(0,360,), 30), # angle 2thetha + ('aaDet',(0,360,), 30), # angle detector + ('aaTrg',(0,360,), 30), # angle target + ('sclA', ( -8, 8), 1), + ('sclD', (-8, 8), 1),): - sld={} - for key,rng,tk in (('aaSeal',(0,180),5),('aaArm',(0,180),5),('airPads',(0,2),1),('defComp',(0,2),1),): - sl=QSlider(QtCore.Qt.Horizontal,objectName=key) - sl.setFixedWidth(200);sl.setMinimum(rng[0]);sl.setMaximum(rng[1]) - sl.setValue(self._param[key]) - sl.setTickPosition(QSlider.TicksBelow);sl.setTickInterval(tk) - l.addWidget(sl) - sl.valueChanged.connect(lambda val,key=key: self.sldChanged(key,val)) - sld[key]=sl + 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)) - self.show() + 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): - p=self._param - #print(key,val) - if key=='aaArm': - p['aaSeal']=v=p['aaSeal']-p['aaArm']+val - wSl=self._wdGrpDraw.findChild(QSlider, 'aaSeal') - wSl.blockSignals(True) - wSl.setValue(int(v)) - wSl.blockSignals(False) - sl=QSlider(QtCore.Qt.Horizontal,objectName=key) + 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)) + print(p['sclTrf']) + 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() - p[key]=val + 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): - p=self._param - ctr=p['ctr'] - aaSeal=p['aaSeal'] - aaArm=p['aaArm'] - rCmb=p['rCmb'] - r2Th=p['r2Th'] - 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(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(-r2Th, -r2Th, 2*r2Th, 2*r2Th) # 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=WndVisualizeARES._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=WndVisualizeARES._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.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 OnEvent(self,*args,**kwargs): + #test event + print(f'OnEvent: {args} ,{kwargs}') if __name__ == '__main__': @@ -184,12 +477,11 @@ if __name__ == '__main__': app=QApplication(sys.argv) app._args=args - #devUS=RIXSdevice('RIXS us', ofs=(750, 200), g=-100, s=-1500, sy=-20, cy=70) - #devDS=RIXSdevice('RIXS ds', ofs=(930, 630)) + app._dev=dev=ARESdevice('Furka-ARES') if args.mode&0x01: - app._wndVis=WndVisualizeARES() - #if args.mode&0x02: + app._wndVisualize=wnd=WndVisualize() + wnd.show() sys.exit(app.exec_()) main() \ No newline at end of file