usefbs=0 if usefbs: from fbs_runtime.application_context.PyQt5 import ApplicationContext from PyQt5.QtCore import QRunnable,pyqtSlot,QThreadPool,QMetaObject,Qt,Q_ARG,QTimer,QTime,QProcess,QItemSelection,QItemSelectionModel,QSignalBlocker,QRectF,pyqtSignal from PyQt5.QtWidgets import (QMainWindow, QApplication, QWidget, QPushButton, QStackedLayout,QItemDelegate, QSlider, QHBoxLayout, QVBoxLayout, QGridLayout, QApplication, QAction,QAbstractItemView, QTabWidget,QLineEdit,QLabel,QComboBox,QDialogButtonBox,QPlainTextEdit,QProgressBar, QSizePolicy,QMessageBox,QTableWidget,QTableWidgetItem,QTextEdit,QHeaderView,QMenu) from PyQt5.QtGui import QPalette,QColor,QTextCursor,QPainter,QFont import csv import numpy as np import traceback import sys,os,time,struct,math,itertools,json import pyqtgraph as pg from PC410Controller import PC410Controller """ class PlotDataset(object): class PlotDataItem(GraphicsObject): """ class QCycleThread(QRunnable): # Custom threader for running cycle() in pc410controller in a thread def __init__(self, name, pc410controller): QRunnable.__init__(self) self.name=name self.pc410controller=pc410controller def run(self): try: self.pc410controller.running=1 print("Starting ct " + self.name) self.pc410controller.cycle(self.pc410controller.tstart0) print("Exiting ct " + self.name) except: print(traceback.format_exc()) finally: self.pc410controller.running=0 def start(self): QThreadPool.globalInstance().setObjectName(self.name) QThreadPool.globalInstance().start(self) def join(self): pass class QRunnableThread(QRunnable): def __init__(self, name, parent): QRunnable.__init__(self) self.name=name self.parent=parent def run(self): try: self.parent.running=1 print("Starting ct " + self.name) self.parent.cycle(self.parent.tstart0) print("Exiting rt " + self.name) except: print(traceback.format_exc()) finally: self.parent.running=0 def start(self): QThreadPool.globalInstance().setObjectName(self.name) QThreadPool.globalInstance().start(self) def join(self): pass class ProcessRunnable(QRunnable): def __init__(self, target, args=None,start=True): QRunnable.__init__(self) self.name="ProcessRunnable" self.t = target if args==None: args=() self.args = args if start: self.start() def run(self): try: self.t(*self.args) except: print(traceback.format_exc()) finally: print("exiting",self.t) def start(self): QThreadPool.globalInstance().setObjectName(self.name) QThreadPool.globalInstance().start(self) class TableView(QTableWidget): def __init__(self, headers, data, *args): QTableWidget.__init__(self, *args) self.data = data self.headers = headers self.setData() self.resizeColumnsToContents() self.resizeRowsToContents() self.cbSelectionChanged=None self.cbCellChanged=None self.cellChanged.connect(self.sCellChanged) def sCellChanged(self,row,column): if self.state()==QAbstractItemView.EditingState: txt=self.item(row,column).text() if self.cbCellChanged: self.cbCellChanged(row,column,txt) def selectionChanged(self,selected, deselected): super().selectionChanged(selected,deselected) indexes=selected.indexes() dindexes=deselected.indexes() rows=set([item.row() for item in indexes]) drows=set([item.row() for item in dindexes]) if rows: row0=min(rows) row1=max(rows) if self.cbSelectionChanged: self.cbSelectionChanged(row0,row1) def setData(self): horHeaders = [] for n, key in enumerate(self.headers): horHeaders.append(key) for m, item in enumerate(self.data[key]): newitem = QTableWidgetItem(str(item)) self.setItem(m, n, newitem) self.setHorizontalHeaderLabels(horHeaders) class ValueSelectDelegate(QItemDelegate): def __init__(self, parent=None,app=None): QItemDelegate.__init__(self, parent) self.app=app def createEditor(self, parent, option, index): model_value = index.model().data(index, Qt.DisplayRole) editor = QComboBox(parent) lines=[ x for x in model_value.split("\n") if x] editor.addItems(lines) return editor def setEditorData(self, editor, index): model_value = index.model().data(index, Qt.EditRole) # must get value from actual data and select value matching in list current_index = editor.findText(model_value) if current_index > 0: editor.setCurrentIndex(current_index) def setModelData(self, editor, model, index): editor_value = editor.currentText() parts=editor_value.split("=") if len(parts)>1: value=parts[0].strip() self.app.realTableCellChanged(index.row(),index.column(),value) #print("setModelData",index.row(),index.column(),editor_value,model) # model.setData(index, editor_value, QtCore.Qt.EditRole) class SimplifiedPlotItemMovement(pg.PlotItem): pointmoved = pyqtSignal([int,float,float],name="pointMoved") def __init__(self): pg.PlotItem.__init__(self) # Initialize the class instance variables. self.dragPoint = None self.dragIndex = -1 self.dragOffset = 0 self.plot_item_control = None def mouseDragEvent(self, ev): # Check to make sure the button is the left mouse button. If not, ignore it. # Would have to change this if porting to Mac I assume. if ev.button() != Qt.MouseButton.LeftButton: ev.ignore() return if ev.isStart(): # We are already one step into the drag. # Find the point(s) at the mouse cursor when the button was first # pressed: # pos = ev.buttonDownPos() pos = ev.buttonDownScenePos() # Switch position into local coords using viewbox local_pos = self.vb.mapSceneToView(pos) for item in self.dataItems: if hasattr(item,"scatter"): new_pts = item.scatter.pointsAt(local_pos) if len(new_pts) == 1: # Store the drag point and the index of the point for future reference. self.dragPoint = new_pts[0] self.dragIndex = item.scatter.points().tolist().index(new_pts[0]) # Find the initial offset of the drag operation from the current point. # This value should allow for the initial mismatch from cursor to point to be accounted for. self.dragOffset = new_pts[0].pos() - local_pos # If we get here, accept the event to prevent the screen from panning during the drag. ev.accept() elif ev.isFinish(): # The drag is finished. Reset the drag point and index. self.dragPoint = None self.dragIndex = -1 return else: # If we get here, this isn't the start or end. Somewhere in the middle. if self.dragPoint is None: # We aren't dragging a point so ignore the event and allow the panning to continue ev.ignore() return else: # We are dragging a point. Find the local position of the event. local_pos = self.vb.mapSceneToView(ev.scenePos()) # Update the point in the PlotDataItem using get/set data. # If we had more than one plotdataitem we would need to search/store which item # is has a point being moved. For this example we know it is the plot_item_control object. x,y = self.plot_item_control.getData() # Be sure to add in the initial drag offset to each coordinate to account for the initial mismatch. x[self.dragIndex] = local_pos.x() + self.dragOffset.x() y[self.dragIndex] = local_pos.y() + self.dragOffset.y() # Update the PlotDataItem (this will automatically update the graphics when a change is detected) #self.plot_item_control.setData(x, y) pos=local_pos.x(),local_pos.y() self.pointmoved.emit(self.dragIndex,*pos) class MainWindow(QMainWindow): def __init__(self,ctx,argv): super(MainWindow, self).__init__() self.indicatorHeight=32 self.resultHeight=36 self.buttonHeight=43 self.cursorMoved=0 self.selectedRow=0 self.fontsize=12 self.infoColumns=2 self.clockHeight=70 # 100 self.progressCompact=1 self.buttonRows=1 self.withActionLabel=0 self.running=0 self.starttime=time.time() self.lasttime=self.starttime self.errorMessage="" self.tfontsize=10 self.tfont = QFont('Courier', self.tfontsize) #, QFont.Bold) self.mintemp=20.0 self.e={} home=os.environ["HOME"] if not home: raise Exeption("HOME environment variable not set!") self.home=home user=os.environ["USER"] if not home: raise Exeption("USER environment variable not set!") self.user=user self.loadSettings() self.build() self.pc410controller=PC410Controller(QCycleThread,self.cmdlog,self.infocallback,self.statecallback,self.recordcallback) self.pc410controller.run() def loadSettings(self): path=os.path.join(os.environ["HOME"],".pc410Settings") self.settings={} try: self.settings=json.loads(open(path).read()) except: print("exception reading and decoding",path) fieldrows=self.settings.get("fieldrows",[]) if not fieldrows: fieldrows=[["Sn63Pb37", 1,85,70, 1,150,35, 1,185,50, -0.01], ["Sn965.5Ag3Cu0.5", 1,85,60, 1,140,45, 1,170,25, 1,220,50, -0.01], ] self.settings["fieldrows"]=fieldrows def saveSettings(self): rowPosition = self.table.rowCount() fieldrows=[] for i in range(0,rowPosition): name,program=self.getProgramParameters(i) values=list(itertools.chain(*program)) fieldrows+=[[name]+values] self.settings["fieldrows"]=fieldrows path=os.path.join(os.environ["HOME"],".pc410Settings") try: open(path,"w").write(json.dumps(self.settings)) except: print("exception writing settings",path) raise # PTN is which program # STEP is which step of program... def addButton(self,layout,name,fn,key=None,colour="white"): button = QPushButton(self) button.setText(name) hmargin=(self.buttonHeight-self.fontsize)//2-5 button.setSizePolicy( QSizePolicy.Preferred, QSizePolicy.Expanding) button.setContentsMargins(20,hmargin,20,hmargin) #button.setStyleSheet("background-color: %s;"%colour) #button.setStyleSheet("padding: 20px; background-color: %s;"%colour) button.setStyleSheet("padding: %dpx; background-color: %s;"%(hmargin,colour)) #button.setGeometry(200, 150, 100, 40) #button.setFont(QFont('Times', 15)) if not key: key=name self.e[key]=(name,key,fn,button) layout.addWidget(button) button.clicked.connect(fn) def saveAction(self): self.saveSettings() def stopAction(self): self.pc410controller.cmdStop() self.running=0 def runAction(self): self.pc410controller.cmdRun() self.running=1 self.clearRunRecord() self.starttime=time.time() def runningToStopped(self): self.running=0 def stoppedToRunning(self): self.running=1 self.clearRunRecord() self.starttime=time.time() def readAction(self): program=self.pc410controller.readProgram() print(program) if program: # update current row in table smodel=self.table.selectionModel() sindexes=smodel.selectedIndexes() if sindexes: rowPosition=max(set([s.row() for s in sindexes])) else: rowPosition = self.table.rowCount() self.table.insertRow(rowPosition) print(rowPosition) name="read" self.setProgramParameters(name,program,rowPosition) def writeAction(self): smodel=self.table.selectionModel() sindexes=smodel.selectedIndexes() if sindexes: rowSet=set([s.row() for s in sindexes]) if len(rowSet)!=1: # fail return rowPosition=min(rowSet) name,program=self.getProgramParameters(rowPosition) if program: result=self.pc410controller.writeProgram(program) if result!=0: print("### failed to write program!!!") def clearRunRecord(self): self.runrecord=([],[]) def addRowAction(self): self.addRow(-1) def addRow(self,rowPosition=-1): if rowPosition==-1: smodel=self.table.selectionModel() sindexes=smodel.selectedIndexes() if sindexes: rowPosition=max(set([s.row() for s in sindexes]))+1 else: rowPosition = self.table.rowCount() self.table.insertRow(rowPosition) def removeRow(self,rowPosition): if self.table.rowCount()<=1: self.addRow(-1) self.table.removeRow(rowPosition) #@pyqtSlot(int,float,float) def tableContextMenu(self,point): index = self.table.indexAt(point) if index.isValid() : # show context menu self.contextMenu = QMenu(self) taddrowabove = self.contextMenu.addAction("Insert row above") taddrowbelow = self.contextMenu.addAction("Insert row below") self.contextMenu.addSeparator() tdeleterow = self.contextMenu.addAction("Delete row") # I want to perform actions only for a single column(E.g: Context menu only for column 4 of my table # Need help here....??? action = self.contextMenu.exec_(self.table.mapToGlobal(point)) row0=index.row() if action == taddrowabove: self.addRow(row0) elif action == taddrowbelow: self.addRow(row0+1) elif action == tdeleterow: self.removeRow(row0) def getTimeOfRun(self): dt=self.lasttime-self.starttime def showTime(self): if self.running: self.lasttime=time.time() dt=self.lasttime-self.starttime h=int((dt/3600)%100) m=int((dt/60)%60) s=int(dt%60) label_time="%02d:%02d:%02d"%(h,m,s) #current_time = QTime.currentTime() #label_time = current_time.toString('hh:mm:ss') self.clocklabel.setText(label_time) def build(self): if 1: font = QFont('Arial', self.fontsize) #, QFont.Bold) self.setFont(font) toplayout = QHBoxLayout() layout = QVBoxLayout() toplayout.setContentsMargins(0,0,0,0) layout.setContentsMargins(0,0,0,0) toplayout.addLayout(layout) layoutside = QVBoxLayout() toplayout.addLayout(layoutside) self.addButton(layoutside,"Save",self.saveAction,"action-save",colour="green") self.addButton(layoutside,"Add row",self.addRowAction,"action-addrow",colour="cyan") self.addButton(layoutside,"Run",self.runAction,"action-run",colour="yellow") self.addButton(layoutside,"Stop",self.stopAction,"action-stop",colour="red") self.addButton(layoutside,"Read program",self.readAction,"action-readprogram",colour="blue") self.addButton(layoutside,"Write program",self.writeAction,"action-writeprogram",colour="green") l=QLabel("Temperature over time") layout.addWidget(l) self.plot_graph = pg.GraphicsLayoutWidget() #self.plot_graph = pg.PlotWidget() self.plot_graph.setBackground("w") self.lineplot=SimplifiedPlotItemMovement() self.plot_graph.addItem(self.lineplot) self.lineplot.pointMoved.connect(self.graphPointMoved) self.plottexts=[] if 1: self.scatter = pg.ScatterPlotItem(size=2, brush=pg.mkBrush(255, 0, 0, 0)) #plot_item = self.plot_graph.addPlot() #scatter_item = pg.ScatterPlotItem() self.lineplot.addItem(self.scatter) #self.plot_graph.addItem(self.scatter) else: self.recordplot = pg.PlotCurveItem(size=2, pen=pg.mkPen(color='#ff0000', width=1)) self.plot_graph.addItem(self.recordplot) self.clearRunRecord() self.lineplot.setTitle('Temperature vs Time') self.lineplot.setLabel("left", "Temperature (°C)") self.lineplot.setLabel("bottom", "Time (seconds)") self.timer = QTimer(self) self.timer.timeout.connect(self.showTime) self.timer.start(1000) hsize=self.clockHeight-5*2 font = QFont('Arial', hsize, QFont.Bold) self.clocklabel = QLabel() self.clocklabel.setAlignment(Qt.AlignCenter) self.clocklabel.setFont(font) self.clocklabel.setContentsMargins(5,5,5,5) layoutside.addWidget(self.clocklabel) info=QGridLayout() infoslots={} def addInfoSlot(infoslots,name,label=None): if not label: label=name row=len(infoslots) l=QLabel(label) e=QLabel() infoslots[name]=(name,l,e) info.addWidget(l,row,0) info.addWidget(e,row,1) def addInfoSlot2D(infoslots,name,label=None): if not label: label=name count=len(infoslots) row=count//2 col=count%2 l=QLabel(label) e=QLabel() infoslots[name]=(name,l,e) info.addWidget(l,row,col*2+0) info.addWidget(e,row,col*2+1) faddinfoslot=addInfoSlot if self.infoColumns==2: faddinfoslot=addInfoSlot2D for param in ["PV","OP","SP","SL","HA","LA","DA","XP","TI","TD","HB","LB","CH","CC","RG","HS","LS","BP","HO","SR","Lc","SW","XS","OS"]: # "HB", faddinfoslot(infoslots,param) layoutside.addLayout(info) self.infoslots=infoslots layout.addWidget(self.plot_graph) if 1: l=QLabel("Settings") layout.addWidget(l) if 0: hbuttons = QHBoxLayout() self.addButton(hbuttons,"Add row",self.addRow,"action-addrow",colour="cyan") layout.addLayout(hbuttons) """ Lead Sn63Pb37 step r L d 1 1 85 70 2 1 150 35 3 1 185 50 4 END Hb=230 Lead-Free Sn965.5Ag3Cu0.5 step r L d 1 1 85 60 2 1 140 45 3 1 170 25 3 1 220 50 5 END hb=230 """ fieldrows=[["Sn63Pb37", 1,85,70, 1,150,35, 1,185,50, -0.01], ["Sn965.5Ag3Cu0.5", 1,85,60, 1,140,45, 1,170,25, 1,220,50, -0.01], ] fieldrows=self.settings.get("fieldrows",fieldrows) headers=["Name"] for i in range(1,9): headers+=["r%d"%i,"l%d"%i,"t%d"%i] data={} for i in range(0,len(headers)): data[headers[i]]=[] for row in fieldrows: fieldrow={} for ki in range(0,len(headers)): k=headers[ki] if ki>=len(row): v="" else: v=row[ki] data[k]+=[v] #data[self.decoded]+=[0] table = TableView(headers,data,len(data[headers[0]]),len(headers)) self.table=table table.setContentsMargins(0,0,0,0) table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) table.setSelectionBehavior(QAbstractItemView.SelectRows); table.cbSelectionChanged=self.tableSelectionChanged table.cbCellChanged=self.tableSelectionChanged #CellChanged def tableClicked(*args): item=args[0] row=item.row() table.clicked.connect(tableClicked) # setting context menu policy on my table, "self.table" self.table.setContextMenuPolicy(Qt.CustomContextMenu) # setting context menu request by calling a function,"self.tableContextMenu" self.table.customContextMenuRequested.connect(self.tableContextMenu) table.show() table.resize(250, 150) layout.addWidget(self.table) if layout: widget = QWidget() widget.setLayout(toplayout) self.setCentralWidget(widget) @pyqtSlot(int,float,float) def graphPointMoved(self,index,x,y): if index!=0: row0=self.selectedRow time,temp=self.buildCoordinates(row0) """ moving time of point 1,3,5 etc means changing rate and adding diff to dwell time moving time of point 2,4,6 etc means changing dwell time, and changing rate of the one after """ step=(index-1)//2 stepi=1+step*3 rgi0=stepi+0 lgi0=stepi+1 tgi0=stepi+2 if (index%2)==0: # 0,2,4 etc # change rate of next to compensate for time change of this # should cap but that time is constructed from rate and delta so max x would then be # time[index]+(temp[index+1]-temp[index])/rate # cap to minx and mintemp rate0=float(self.table.item(row0,rgi0).text()) dy0=(y-temp[index-2]) minx=time[index-2] if rate0: minx+=abs(dy0)/rate0 x=max(minx,x) y=max(self.mintemp,y) if index+1