#!/usr/bin/env python # *-----------------------------------------------------------------------* # | | # | Copyright (c) 2024 by Paul Scherrer Institute (http://www.psi.ch) | # | | # | Author Thierry Zamofing (thierry.zamofing@psi.ch) | # *-----------------------------------------------------------------------* """ Furka ARES chamber visualization For simulated motor IOC: /home/zamofing_t/Documents/prj/SwissFEL/test_ioc/MotorSim/iocBoot/ARESvis/ARESvis.cmd For motor ui: caQtDM ~/Documents/prj/SwissFEL/test_ioc/MotorSim/iocBoot/ARESvis/ARESvis.ui& 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-RIXS:MOT_RY.RBV # sliding seal SATES30-ARES:MOT_JFRY.RBV # jungfrau detector angle: SATES30-ARES:MOT_2TRY.RBV # 2thetha angle: foc.mirror Diode2 Diode3 SATES30-ARES:MOT_DRY.RBV # detector angle: Diode1 Mirror SATES30-ARES:MOT_STX.RBV # sample TX SATES30-ARES:MOT_STZ.RBV # sample TZ SATES30-ARES:MOT_SRY.RBV # sample rotation SATES30-MCS001:MOT_6 # Parabola TX SATES30-ACSFM:MOT_TZ # Foc. Mirror TZ bitmask for simulation: 0x01: EPICS motors 0x02: 0x04: 0x08: 0x10: 0x20: 0x40: 0x80: """ import os.path 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, QPolygonF, QTransform, QPainterPath, \ QPixmap, QMouseEvent, QImage from PyQt5.QtCore import QPoint, QPointF, Qt,pyqtSignal,QObject #import PyQt5.QtGui as QtGui #import PyQt5.QtCore as QtCore import numpy as np import sys, logging, copy import epics if sys.version_info < (3, 6): sys.exit(f"Must be using Python 3.6 or newer. Try e.g. /opt/gfa/python-3.8/latest/bin/python {sys.argv[0]}") _log=logging.getLogger(__name__) import logging class col: d = '\033[0m' #default r = '\033[31m' #red g = '\033[32m' #green y = '\033[33m' #yellow rr= '\033[91m' #red(bright) gg= '\033[92m' #green(bright) yy= '\033[93m' #yellow(bright) b = '\033[1m' #bold u = '\033[4m' #underline R = '\033[1;31m' #bold, red G = '\033[1;32m' #bold, green Y = '\033[1;33m' #bold, yellow class logHandler(logging.StreamHandler): def __init__(self): logging.StreamHandler.__init__(self) def emit(self, record): '''override function of base class''' try: msg=self.format(record) # print(record.__dict__) if record.levelno<=10: c=col.g elif record.levelno<=20: c=col.y elif record.levelno<=30: c=col.yy elif record.levelno<=40: c=col.r else: c=col.rr+col.b msg=c+msg+col.d stream=self.stream stream.write(msg+self.terminator) self.flush() except RecursionError: raise except Exception: self.handleError(record) def adjust_transparency(pixmap, transparency_factor): "Adjust the transparency of a QPixmap." image = pixmap.toImage().convertToFormat(QImage.Format_ARGB32) if image.isNull(): return pixmap # Convert QImage to a NumPy array for efficient manipulation width = image.width() height = image.height() ptr = image.bits() ptr.setsize(image.byteCount()) # Make the sip.voidptr object aware of its size arr = np.frombuffer(ptr, dtype=np.uint8).reshape((height, width, 4)) # Modify the alpha channel, ensuring values are within the valid 0-255 range arr[:, :, 3] = (arr[:, :, 3] * transparency_factor).clip(0, 255).astype(np.uint8) # The numpy array modifies the QImage's buffer in place. Return a new QPixmap from the modified image. return QPixmap.fromImage(image) class ARESdevice(): def __init__(self,name,**kwargs): self._name=name self._paint=p={ 'ofs':(820, 350), # location of device 'rArm':int(540), # 540mm inner radius of chamber (RIXS arm) 'r2Th':int(375), # 375mm radius 2Theta platfform 'rJFr':int(270), # 270mm radius of Jungfrau 'rDet':int(207.5), # 207.5mm radius of detector table 'rTrg':int(168.5), # 168.5mm radius of target 'szArm':(20, 50), 'aArm':100, # angle RIXS arm 'aJFr':140, # angle detector 'a2Th':116, # angle 2thetha 'aDet':100, # angle detector 'txTrg':0, # tx target 'tzTrg':0, # tz target 'aTrg':10, # angle target 'txPar':0, # parabola translation x 'tyPar':50.8,# parabola translation y 'tzFM':10, # focussing mirror translation z 'sclTrf':(2**(6/2), 2**(-2/2)), # scaling transfformation [angle, distance] } p.update(kwargs) self._geo=g={ } self.setGeometry(g) self._pic=pic=dict() base=os.path.join(os.path.dirname(os.path.realpath(__file__)),'pic') _log.debug(f"Loading pictures from: {base}") pic_files = { 'ir':'ARES_2Theta_InnerRing.png', 'or':'ARES_2Theta_OuterRing.png', 'bl':'ARES_Below.png', 'df':'ARES_Diffractometer.png', 'di':'ARES_Diode.png', 'jf':'ARES_Jungfrau.png', 'lm':'ARES_LaserMirror.png', 'ma':'ARES_Master.png', 'mi':'ARES_Mirrors.png', 'pb':'ARES_Parabola.png', } for k, v in pic_files.items(): path = os.path.join(base, v) if os.path.exists(path): pic[k] = adjust_transparency(QPixmap(path), .7) else: _log.warning(f"Pixmap file not found: {path}") def setGeometry(self,geo): self._geo=geo p=self._paint def geometry2motor(self): # returns raw motor positions # offset detector plane to deflected beam: 34deg geo=self._geo def containsPoint(self,point): try: pg=self._polygon except AttributeError: return False return pg.containsPoint(point,Qt.OddEvenFill) @staticmethod def plotOrig(qp): penR=QPen(Qt.red, 2, Qt.SolidLine) penG=QPen(Qt.green, 2, 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 pic=self._pic ofs=p['ofs'] rArm=p['rArm'] r2Th=p['r2Th'] rJFr=p['rJFr'] rDet=p['rDet'] rTrg=p['rTrg'] aArm=p['aArm'] a2Th=180-p['a2Th'] #opposite direction zero at bottom aJFr=p['aJFr'] aDet=p['aDet'] txTrg=p['txTrg'] tzTrg=p['tzTrg'] aTrg=p['aTrg'] txPar=-p['txPar'] #opposite direction tyPar=p['tyPar'] tzFM =p['tzFM'] sclTrf=p['sclTrf'] #x=p.get('x',0) #y=p.get('y',0) #print(x,y) tickW,tickL=3,20 # tick px-width, tick len diW,diH=40,20 # diodes miW,miH=50,10 # mirrors tgW,tgH=80,30 # target # --- 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(-aArm) tfJFr=copy.copy(tf0) tfJFr.rotate(-aJFr) tf2Th=copy.copy(tf0) tf2Th.rotate(-a2Th) tfDet=copy.copy(tf0) tfDet.rotate(-aDet) tfTrg=copy.copy(tf0) tfTrg.rotate(-aTrg) #tfd.translate(r2,0).rotate(-cc) penBk=QPen(Qt.black, 0, Qt.SolidLine) penWt=QPen(Qt.white, 1, Qt.SolidLine) penYl=QPen(Qt.yellow, 1, Qt.SolidLine) penBl=QPen(Qt.blue, 1, Qt.SolidLine) penRd=QPen(Qt.red, 1, Qt.SolidLine) # --- visualize --- #qp.setRenderHints(QPainter.HighQualityAntialiasing) # setup and plot dragable region 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) qp.setTransform(tf0) qp.setPen(penBk) qp.setBrush(QColor(128, 128, 128, 128)) #r,g,b,a #circles of rotation #qp.drawEllipse(-rArm, -rArm, 2*rArm, 2*rArm) # ARES chamber #qp.drawEllipse(-r2Th, -r2Th, 2*r2Th, 2*r2Th) # 2theta #qp.drawEllipse(-rJFr, -rJFr, 2*rJFr, 2*rJFr) # jungfrau #qp.drawEllipse(-rDet, -rDet, 2*rDet, 2*rDet) # detector #qp.drawEllipse(-rTrg, -rTrg, 2*rTrg, 2*rTrg) # target for r1,r2,col in ( (rArm,r2Th,QColor(255, 0, 0, 32)), #2theta (r2Th,rDet,QColor( 0,255, 0, 32)), #Jungfrau #(r2Th,rJFr,QColor( 0,255, 0, 32)), #Jungfrau #(rJFr,rDet,QColor(255, 0,255, 32)), #detector (rDet,rTrg,QColor( 0, 0,255, 32)), #target ): path=QPainterPath() qp.setBrush(col) #r,g,b,a path.addEllipse(-r1, -r1, 2*r1, 2*r1) path.addEllipse(-r2, -r2, 2*r2, 2*r2) # Jungfrau qp.drawPath(path) qp.setBrush(Qt.NoBrush) qp.drawEllipse(-rJFr, -rJFr, 2*rJFr, 2*rJFr) # jungfrau qp.setBrush(QColor(0,255,255,32)) # r,g,b,a qp.drawEllipse(-rTrg, -rTrg, 2*rTrg, 2*rTrg) # target #beam arrow qp.setPen(QPen(Qt.black, 3, Qt.SolidLine)) qp.drawLine(0,+rArm+100,0,rArm) qp.drawPolygon(QPolygon([QPoint(0,rArm),QPoint(-5,rArm+20),QPoint(+5,rArm+20),])) #crosshair qp.setPen(penBk) qp.drawLine(0,-rArm,0,rArm) qp.drawLine(-rArm,0,rArm,0) #self.plotOrig(qp) #qp.setPen(penRd) #qp.drawRect(-10, -10+rArm, 20, 20) #--- pixmaps --- pm=pic['bl'] # bellow w,h=pm.width(),pm.height() qp.setTransform(tfArm);qp.translate(0, -rArm);qp.scale(.64,.64) qp.drawPixmap(QPointF(-w/2,-h),pm) pm=pic['mi'] # foccussing mirror w,h=pm.width(),pm.height() qp.setTransform(tf2Th);qp.translate(-39.6,-273.6);qp.scale(.64,.64) qp.drawPixmap(QPointF(-w/2,-h/2),pm) pm=pic['di'] #diode 2 w,h=pm.width(),pm.height() qp.setTransform(tf2Th);qp.rotate(54);qp.translate(0,-240);qp.scale(.64,.64) qp.drawPixmap(QPointF(-w/2,-h/2),pm) pm=pic['df'] # diffrantometer w,h=pm.width(),pm.height() qp.setTransform(tfTrg);qp.translate(22, 17);qp.scale(.64,.64) qp.drawPixmap(QPointF(-w/2,-h/2),pm) pm=pic['jf'] # jungfrau detector w,h=pm.width(),pm.height() qp.setTransform(tfJFr);qp.translate(-25, -225);qp.scale(.64,.64) qp.drawPixmap(QPointF(-w/2,-h/2),pm) pm=pic['lm'] # laser mirror w,h=pm.width(),pm.height() qp.setTransform(tfDet);qp.rotate(181-7);qp.translate(13, -193);qp.scale(.64,.64) qp.drawPixmap(QPointF(-w/2,-h/2),pm) pm=pic['di'] # diode det1 w,h=pm.width(),pm.height() qp.setTransform(tfDet);qp.rotate(165.4);qp.translate(-0, -205);qp.scale(.64,.64) qp.drawPixmap(QPointF(-w/2,-h/2),pm) pm=pic['di'] # diode det2 w,h=pm.width(),pm.height() qp.setTransform(tfDet);qp.rotate(-96);qp.translate(-0, -205);qp.scale(.64,.64) qp.drawPixmap(QPointF(-w/2,-h/2),pm) #--- RIXS-arm devices --- qp.setTransform(tfArm) qp.setPen(QPen(Qt.red, tickW, Qt.SolidLine)) qp.drawLine(0,-rArm,0,-rArm+tickL) #tick qp.setPen(penBk) qp.setBrush(QColor(255,0,0,128)) qp.drawRect(-60, -150-rArm, 120, 150) # tube qp.drawRect(-300, -600-rArm, 600, 450) # grating chamber #--- 2-theta devices --- qp.setTransform(tf2Th) qp.setPen(QPen(Qt.green, tickW, Qt.SolidLine)) qp.drawLine(0,-r2Th,0,-r2Th+tickL) #tick qp.setPen(penBk) qp.setBrush(QColor(0,255,0,192)) #mirror:310x30mm, 10-40mm dist, 40mm outside 2th qp.translate(20,-r2Th-40);qp.rotate(2);qp.translate(0,tzFM) qp.drawRect(0, 0, 30, 310) # foc. mirror 1 qp.setTransform(tf2Th);qp.translate(-20,-r2Th-40);qp.rotate(-2);qp.translate(0,tzFM) qp.drawRect(-30, 0, 30, 310) # foc. mirror 2 qp.setTransform(tf2Th);qp.rotate(54);qp.translate(-diW/2,-rDet-diH) #-r2Th-50 qp.drawRect(0, 0, diW, diH) # diode2 #qp.setTransform(tf2Th);qp.rotate(80);qp.translate(-diW/2,-r2Th); #qp.drawRect(0, 0, diW, diH) # diode3 #--- Jungfrau devices --- qp.setTransform(tfJFr) qp.setPen(QPen(Qt.magenta, tickW, Qt.SolidLine)) qp.drawLine(0,-rJFr,0,-rJFr+tickL) #tick qp.setPen(penBk) qp.setBrush(QColor(255,0,255,192)) jfW,jfH=80,20 # jungfrau detector qp.setTransform(tfJFr);qp.translate(-jfW/2,-rJFr); qp.drawRect(0, -jfH, jfW, jfH) # detector mount sample #--- detector devices --- qp.setTransform(tfDet) qp.setPen(QPen(Qt.blue, tickW, Qt.SolidLine)) qp.drawLine(0,-rDet,0,-rDet+tickL) #tick qp.setPen(penBk) qp.setBrush(QColor(0,0,255,192)) qp.setTransform(tfDet);qp.rotate(165.4);qp.translate(-diW/2,-rDet) qp.drawRect(0, 0, diW, diH) # diode det1 qp.setTransform(tfDet);qp.rotate(-96);qp.translate(-diW/2,-rDet) qp.drawRect(0, 0, diW, diH) # diode det2 qp.setTransform(tfDet);qp.rotate(181);qp.translate(0,-rDet+miW/2);qp.rotate(-45) qp.drawRect(-int(miW/2), 0, miW, miH) # mirror1 #--- target devices --- qp.setTransform(tfTrg) qp.setPen(QPen(Qt.cyan, tickW, Qt.SolidLine)) qp.drawLine(0,-rTrg,0,-rTrg+tickL) #tick qp.setPen(penBk) qp.setBrush(QColor(0,255,255,192)) qp.drawRect(int(-(tgW/2)-txTrg), int(-tgH+tzTrg), tgW, tgH) # target mount sample # --- parabola mirror --- qp.setTransform(tf0) qp.setPen(penBk) qp.setBrush(QColor(224,192,0,224)) #x,x0,y,y0=3*50.8,3*12,3*50.8,3*12 x,x0,y,y0=50.8,12,50.8,4 qp.translate(txPar-x/2+61, tyPar) path=QPainterPath() path.moveTo(0,0) path.cubicTo(0,y/2,x/2,y,x,y) path.lineTo(x,y+y0) path.lineTo(-x0,y+y0) path.lineTo(-x0,0) path.lineTo(0,0) qp.drawPath(path) qp.drawLine(int(x/2),int(y+y0)+20,int(x/2),0) # --- parabola beam path --- if txPar>-65 and txPar<-55: #show beam path only within +-5mm qp.setPen(penBl) qp.drawLine(0,0,int(x/2),int(-tyPar)) qp.drawLine(0,0,int(rArm+x/2-txPar),0) qp.drawLine(int(x),int(y),int(x/2),int(-tyPar)) qp.drawLine(int(x),int(y),int(rArm+x/2-txPar),int(y)) # --- print angles --- #rArm= r2Th= rJFr= rDet= rTrg= aArm= a2Th=180-p['a2Th'] # opposite direction zero at bottom aJFr= aDet= aTrg= #penBl=QPen(Qt.blue, 3, Qt.SolidLine) bg_col=QColor(255,255,255, 128) # background color fg_col=QColor( 0, 0, 0,255) # foreground color qp.setPen(penBl) qp.setTransform(QTransform()) for r,a,tf in ((rArm,aArm,tfArm),(r2Th,a2Th,tf2Th),(rDet,aDet,tfDet),(rJFr,aJFr,tfJFr),(rTrg/2,aTrg,tfTrg),): p=QPoint(*tf.map(0, int(-r+40))) txt=f'{a:.5g}°' txr=qp.boundingRect(p.x(), p.y(), 0, 0, Qt.AlignLeft, txt) txr.moveTop(int(txr.y()-txr.height()/2)) # Draw opaque background qp.setBrush(bg_col) qp.setPen(Qt.NoPen) qp.drawRect(txr) # Draw text qp.setPen(fg_col) qp.drawText(txr, Qt.AlignLeft, txt) class WndVisualize(QWidget): updateSignal = pyqtSignal(str, float, str) connectionSignal = pyqtSignal(str, bool) _pv2key={ 'SATES30-RIXS:MOT_RY.RBV' :'aArm', 'SATES30-ARES:MOT_2TRY.RBV':'a2Th', 'SATES30-ARES:MOT_JFRY.RBV':'aJFr', 'SATES30-ARES:MOT_DRY.RBV' :'aDet', 'SATES30-ARES:MOT_STZ.RBV' :'tzTrg', 'SATES30-ARES:MOT_STX.RBV' :'txTrg', 'SATES30-ARES:MOT_SRY.RBV' :'aTrg', 'SATES30-MCS001:MOT_6.RBV' :'txPar', 'SATES30-ACSFM:MOT_TZ.RBV' :'tzFM', } #updSignal=pyqtSignal(str, float, str) def __init__(self): super().__init__() self.initUI() self.updateSignal.connect(self.vis_update) self.connectionSignal.connect(self.update_connection_status) self.connectEPICS() # self.update() must be called in main thread. Else gui may block after some time # PV monitoring is done in a separate thread. Therefore functions as: # OnConnectionChange, OnValueChange MUST NOT call update, but use this Signal helper class. 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 self.labels = {} self.sliders = {} for key, rng, tk in ( ('aArm' ,( 0,360,), 30), # angle ARES sliding seal ('a2Th' ,( 0,360,), 30), # angle 2thetha ('aJFr' ,( 0,360,), 30), # angle Jungfrau ('aDet' ,( 0,360,), 30), # angle detector ('aTrg' ,( 0,360,), 30), # angle target ('txTrg',( -5, 5,), 30), # tx target ('tzTrg',(-10, 1,), 30), # tz target ('txPar',(-40, 80,), 10), # parabola translation x ('tzFM' ,(-50, 50,), 10), # focussing mirror translation z ('sclA', (-8, 8), 1), ('sclD', (-8, 8), 1), #('x', (-100, 100), 10), #('y', (-100, 100), 10), ): wLb=QLabel(key, objectName=key) self.labels[key] = wLb wSl=QSlider(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)) #elif key in ('x','y'): # v=0 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.sliders[key] = wSl lg.addWidget(wLb, row, 0) lg.addWidget(wSl, row, 1);row+=1 w=QPushButton('sync motors') w.clicked.connect(self.btnSyncMotors) lg.addWidget(w, row, 1) #self.event_update.connect(self.cb_update) self.show() def btnSyncMotors(self): # reads the epics motor and updates the vizualization _log.info('') self.liveView() def cb_update(self,*args,**kwargs): _log.debug(f'{args} {kwargs}') def connectEPICS(self): _log.info('connect PVs') self._pvDict=pvd=dict() self._pvConnected=0 for pvn in self._pv2key.keys(): pv=epics.get_pv(pvn,connection_callback=self.OnConnectionChange,callback=self.OnValueChange) pvd[pvn]=pv _log.info(f'{pv}') #epics.Motor checks the record type and will fail if the record is not online #therefore use epics.Device #epics.Device will force to connect the PV in Device.add_callback #therefore use epics.PV.add_callback to acc callback #as soon as the devices are online, they are connected #but with epics.Device creating PV is not fully flexible. epics.get_pv proviles connection and value change callbacks that is way more flexible. #therefore the lowest level of the library (only pvs is the best suited #m=epics.Motor(rec_name) #m=epics.Device(rec_name, delim='.',with_poll=False,attrs=('VAL', 'RBV', 'DESC', 'RVAL','LVIO', 'HLS', 'LLS')) #m.add_callback('RBV', self.OnChangedRBV) #pv=m.PV('RBV',connect=False) #pv.add_callback(self.OnChangedRBV) #pv.connection_callbacks #if not pv.connected: # disconnected.add(rec_name) #print(pv.connected) #devs.add(m) def OnConnectionChange(self, pvname=None, conn=None, **kws): pvc=self._pvConnected if conn: pvc+=1 else: if pvc>0: pvc-=1 _log.info(f'PV connection {pvc}/{len(self._pvDict)}: {pvname} {conn}') self._pvConnected=pvc self.connectionSignal.emit(pvname, conn) def update_connection_status(self, pvname, conn): key = self._pv2key.get(pvname) if not key: return wLb = self.labels.get(key) if not wLb: return if not conn: v=f"{key}" wLb.setText(v) else: v=f"{key}" wLb.setText(v) def OnValueChange(self, pvname, value, **kw): _log.info(f"PV val:{pvname}:{value}") try: key=self._pv2key[pvname] except KeyError as e: _log.warning(f"can't handle PV: {pvname}:{value}") return self.updateSignal.emit(key,value,'008000') def vis_update(self,key,value,col='000000'): wSl = self.sliders.get(key) if wSl: wSl.blockSignals(True) wSl.setValue(int(value)) # move the slider without emiting a signal wSl.blockSignals(False) self.sldChanged(key,value) # emit the signal wLb = self.labels.get(key) if wLb: v=f"{key}" wLb.setText(v) def liveView(self): # try to live update all PVs _log.info('') p2k=self._pv2key for pv in self._pvDict.values(): pvn=pv.pvname key=p2k[pvn] if pv.connected: value=pv.get() self.vis_update(key,value,'008000') print (pvn,key,value) else: print (pvn,key) pass 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'): 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 g[key]=val wLb = self.labels.get(key) if wLb: v=f"{key}" wLb.setText(v) 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()!=QMouseEvent.MouseButtonPress: return wGrp=self._wdGrpDraw #if wGrp.underMouse(): #draging sliders? if wGrp.geometry().contains(mousePos): self._mouseDrag={'obj':wGrp, 'start':mousePos} else: dev=app._dev if dev.containsPoint(mousePos): self._devSel=dev 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 key, wSl in self.sliders.items(): 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__': import argparse logging.basicConfig(level=logging.DEBUG, handlers=[logHandler()], format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ') def main(): epilog=__doc__ # +'\nExamples:'+''.join(map(lambda s:cmd+s, exampleCmd))+'\n' parser=argparse.ArgumentParser(epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('--mode', '-m', type=lambda x:int(x, 0), help='mode (see bitmasks) default=0x%(default)x', default=1) parser.add_argument("--sim", "-s", type=lambda x: int(x,0), help="simulate devices (see bitmasks) default=0x%(default)x", default=0x01) args=parser.parse_args() _log.info('Arguments:{}'.format(args.__dict__)) app=QApplication(sys.argv) app._args=args app._dev=dev=ARESdevice('Furka-ARES') if args.mode&0x01: app._wndVisualize=wnd=WndVisualize() wnd.show() sys.exit(app.exec_()) main()