main.py 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028
  1. usefbs=0
  2. if usefbs:
  3. from fbs_runtime.application_context.PyQt5 import ApplicationContext
  4. from PyQt5.QtCore import QRunnable,pyqtSlot,QThreadPool,QMetaObject,Qt,Q_ARG,QTimer,QTime,QProcess,QItemSelection,QItemSelectionModel,QSignalBlocker,QRectF,pyqtSignal
  5. from PyQt5.QtWidgets import (QMainWindow, QApplication, QWidget, QPushButton, QStackedLayout,QItemDelegate,
  6. QSlider,
  7. QHBoxLayout, QVBoxLayout, QGridLayout, QApplication, QAction,QAbstractItemView,
  8. QTabWidget,QLineEdit,QLabel,QComboBox,QDialogButtonBox,QPlainTextEdit,QProgressBar,
  9. QSizePolicy,QMessageBox,QTableWidget,QTableWidgetItem,QTextEdit,QHeaderView,QMenu)
  10. from PyQt5.QtGui import QPalette,QColor,QTextCursor,QPainter,QFont
  11. import csv
  12. import numpy as np
  13. import traceback
  14. import sys,os,time,struct,math,itertools,json
  15. import pyqtgraph as pg
  16. from PC410Controller import PC410Controller
  17. """
  18. class PlotDataset(object):
  19. class PlotDataItem(GraphicsObject):
  20. """
  21. class QCycleThread(QRunnable):
  22. # Custom threader for running cycle() in pc410controller in a thread
  23. def __init__(self, name, pc410controller):
  24. QRunnable.__init__(self)
  25. self.name=name
  26. self.pc410controller=pc410controller
  27. def run(self):
  28. try:
  29. self.pc410controller.running=1
  30. print("Starting ct " + self.name)
  31. self.pc410controller.cycle(self.pc410controller.tstart0)
  32. print("Exiting ct " + self.name)
  33. except:
  34. print(traceback.format_exc())
  35. finally:
  36. self.pc410controller.running=0
  37. def start(self):
  38. QThreadPool.globalInstance().setObjectName(self.name)
  39. QThreadPool.globalInstance().start(self)
  40. def join(self):
  41. pass
  42. class QRunnableThread(QRunnable):
  43. def __init__(self, name, parent):
  44. QRunnable.__init__(self)
  45. self.name=name
  46. self.parent=parent
  47. def run(self):
  48. try:
  49. self.parent.running=1
  50. print("Starting ct " + self.name)
  51. self.parent.cycle(self.parent.tstart0)
  52. print("Exiting rt " + self.name)
  53. except:
  54. print(traceback.format_exc())
  55. finally:
  56. self.parent.running=0
  57. def start(self):
  58. QThreadPool.globalInstance().setObjectName(self.name)
  59. QThreadPool.globalInstance().start(self)
  60. def join(self):
  61. pass
  62. class ProcessRunnable(QRunnable):
  63. def __init__(self, target, args=None,start=True):
  64. QRunnable.__init__(self)
  65. self.name="ProcessRunnable"
  66. self.t = target
  67. if args==None: args=()
  68. self.args = args
  69. if start: self.start()
  70. def run(self):
  71. try:
  72. self.t(*self.args)
  73. except:
  74. print(traceback.format_exc())
  75. finally:
  76. print("exiting",self.t)
  77. def start(self):
  78. QThreadPool.globalInstance().setObjectName(self.name)
  79. QThreadPool.globalInstance().start(self)
  80. class TableView(QTableWidget):
  81. def __init__(self, headers, data, *args):
  82. QTableWidget.__init__(self, *args)
  83. self.data = data
  84. self.headers = headers
  85. self.setData()
  86. self.resizeColumnsToContents()
  87. self.resizeRowsToContents()
  88. self.cbSelectionChanged=None
  89. self.cbCellChanged=None
  90. self.cellChanged.connect(self.sCellChanged)
  91. def sCellChanged(self,row,column):
  92. if self.state()==QAbstractItemView.EditingState:
  93. txt=self.item(row,column).text()
  94. if self.cbCellChanged:
  95. self.cbCellChanged(row,column,txt)
  96. def selectionChanged(self,selected, deselected):
  97. super().selectionChanged(selected,deselected)
  98. indexes=selected.indexes()
  99. dindexes=deselected.indexes()
  100. rows=set([item.row() for item in indexes])
  101. drows=set([item.row() for item in dindexes])
  102. if rows:
  103. row0=min(rows)
  104. row1=max(rows)
  105. if self.cbSelectionChanged:
  106. self.cbSelectionChanged(row0,row1)
  107. def setData(self):
  108. horHeaders = []
  109. for n, key in enumerate(self.headers):
  110. horHeaders.append(key)
  111. for m, item in enumerate(self.data[key]):
  112. newitem = QTableWidgetItem(str(item))
  113. self.setItem(m, n, newitem)
  114. self.setHorizontalHeaderLabels(horHeaders)
  115. class ValueSelectDelegate(QItemDelegate):
  116. def __init__(self, parent=None,app=None):
  117. QItemDelegate.__init__(self, parent)
  118. self.app=app
  119. def createEditor(self, parent, option, index):
  120. model_value = index.model().data(index, Qt.DisplayRole)
  121. editor = QComboBox(parent)
  122. lines=[ x for x in model_value.split("\n") if x]
  123. editor.addItems(lines)
  124. return editor
  125. def setEditorData(self, editor, index):
  126. model_value = index.model().data(index, Qt.EditRole)
  127. # must get value from actual data and select value matching in list
  128. current_index = editor.findText(model_value)
  129. if current_index > 0:
  130. editor.setCurrentIndex(current_index)
  131. def setModelData(self, editor, model, index):
  132. editor_value = editor.currentText()
  133. parts=editor_value.split("=")
  134. if len(parts)>1:
  135. value=parts[0].strip()
  136. self.app.realTableCellChanged(index.row(),index.column(),value)
  137. #print("setModelData",index.row(),index.column(),editor_value,model)
  138. # model.setData(index, editor_value, QtCore.Qt.EditRole)
  139. class SimplifiedPlotItemMovement(pg.PlotItem):
  140. pointmoved = pyqtSignal([int,float,float],name="pointMoved")
  141. def __init__(self):
  142. pg.PlotItem.__init__(self)
  143. # Initialize the class instance variables.
  144. self.dragPoint = None
  145. self.dragIndex = -1
  146. self.dragOffset = 0
  147. self.plot_item_control = None
  148. def mouseDragEvent(self, ev):
  149. # Check to make sure the button is the left mouse button. If not, ignore it.
  150. # Would have to change this if porting to Mac I assume.
  151. if ev.button() != Qt.MouseButton.LeftButton:
  152. ev.ignore()
  153. return
  154. if ev.isStart():
  155. # We are already one step into the drag.
  156. # Find the point(s) at the mouse cursor when the button was first
  157. # pressed:
  158. # pos = ev.buttonDownPos()
  159. pos = ev.buttonDownScenePos()
  160. # Switch position into local coords using viewbox
  161. local_pos = self.vb.mapSceneToView(pos)
  162. for item in self.dataItems:
  163. if hasattr(item,"scatter"):
  164. new_pts = item.scatter.pointsAt(local_pos)
  165. if len(new_pts) == 1:
  166. # Store the drag point and the index of the point for future reference.
  167. self.dragPoint = new_pts[0]
  168. self.dragIndex = item.scatter.points().tolist().index(new_pts[0])
  169. # Find the initial offset of the drag operation from the current point.
  170. # This value should allow for the initial mismatch from cursor to point to be accounted for.
  171. self.dragOffset = new_pts[0].pos() - local_pos
  172. # If we get here, accept the event to prevent the screen from panning during the drag.
  173. ev.accept()
  174. elif ev.isFinish():
  175. # The drag is finished. Reset the drag point and index.
  176. self.dragPoint = None
  177. self.dragIndex = -1
  178. return
  179. else:
  180. # If we get here, this isn't the start or end. Somewhere in the middle.
  181. if self.dragPoint is None:
  182. # We aren't dragging a point so ignore the event and allow the panning to continue
  183. ev.ignore()
  184. return
  185. else:
  186. # We are dragging a point. Find the local position of the event.
  187. local_pos = self.vb.mapSceneToView(ev.scenePos())
  188. # Update the point in the PlotDataItem using get/set data.
  189. # If we had more than one plotdataitem we would need to search/store which item
  190. # is has a point being moved. For this example we know it is the plot_item_control object.
  191. x,y = self.plot_item_control.getData()
  192. # Be sure to add in the initial drag offset to each coordinate to account for the initial mismatch.
  193. x[self.dragIndex] = local_pos.x() + self.dragOffset.x()
  194. y[self.dragIndex] = local_pos.y() + self.dragOffset.y()
  195. # Update the PlotDataItem (this will automatically update the graphics when a change is detected)
  196. #self.plot_item_control.setData(x, y)
  197. pos=local_pos.x(),local_pos.y()
  198. self.pointmoved.emit(self.dragIndex,*pos)
  199. class MainWindow(QMainWindow):
  200. def __init__(self,ctx,argv):
  201. super(MainWindow, self).__init__()
  202. self.indicatorHeight=32
  203. self.resultHeight=36
  204. self.buttonHeight=43
  205. self.cursorMoved=0
  206. self.selectedRow=0
  207. self.fontsize=12
  208. self.infoColumns=2
  209. self.clockHeight=70 # 100
  210. self.progressCompact=1
  211. self.buttonRows=1
  212. self.withActionLabel=0
  213. self.running=0
  214. self.starttime=time.time()
  215. self.lasttime=self.starttime
  216. self.errorMessage=""
  217. self.tfontsize=10
  218. self.tfont = QFont('Courier', self.tfontsize) #, QFont.Bold)
  219. self.mintemp=20.0
  220. self.e={}
  221. home=os.environ["HOME"]
  222. if not home: raise Exeption("HOME environment variable not set!")
  223. self.home=home
  224. user=os.environ["USER"]
  225. if not home: raise Exeption("USER environment variable not set!")
  226. self.user=user
  227. self.loadSettings()
  228. self.build()
  229. self.pc410controller=PC410Controller(QCycleThread,self.cmdlog,self.infocallback,self.statecallback,self.recordcallback)
  230. self.pc410controller.run()
  231. def loadSettings(self):
  232. path=os.path.join(os.environ["HOME"],".pc410Settings")
  233. self.settings={}
  234. try:
  235. self.settings=json.loads(open(path).read())
  236. except:
  237. print("exception reading and decoding",path)
  238. fieldrows=self.settings.get("fieldrows",[])
  239. if not fieldrows:
  240. fieldrows=[["Sn63Pb37", 1,85,70, 1,150,35, 1,185,50, -0.01],
  241. ["Sn965.5Ag3Cu0.5", 1,85,60, 1,140,45, 1,170,25, 1,220,50, -0.01],
  242. ]
  243. self.settings["fieldrows"]=fieldrows
  244. def saveSettings(self):
  245. rowPosition = self.table.rowCount()
  246. fieldrows=[]
  247. for i in range(0,rowPosition):
  248. name,program=self.getProgramParameters(i)
  249. values=list(itertools.chain(*program))
  250. fieldrows+=[[name]+values]
  251. self.settings["fieldrows"]=fieldrows
  252. path=os.path.join(os.environ["HOME"],".pc410Settings")
  253. try:
  254. open(path,"w").write(json.dumps(self.settings))
  255. except:
  256. print("exception writing settings",path)
  257. raise
  258. # PTN is which program
  259. # STEP is which step of program...
  260. def addButton(self,layout,name,fn,key=None,colour="white"):
  261. button = QPushButton(self)
  262. button.setText(name)
  263. hmargin=(self.buttonHeight-self.fontsize)//2-5
  264. button.setSizePolicy(
  265. QSizePolicy.Preferred,
  266. QSizePolicy.Expanding)
  267. button.setContentsMargins(20,hmargin,20,hmargin)
  268. #button.setStyleSheet("background-color: %s;"%colour)
  269. #button.setStyleSheet("padding: 20px; background-color: %s;"%colour)
  270. button.setStyleSheet("padding: %dpx; background-color: %s;"%(hmargin,colour))
  271. #button.setGeometry(200, 150, 100, 40)
  272. #button.setFont(QFont('Times', 15))
  273. if not key: key=name
  274. self.e[key]=(name,key,fn,button)
  275. layout.addWidget(button)
  276. button.clicked.connect(fn)
  277. def saveAction(self):
  278. self.saveSettings()
  279. def stopAction(self):
  280. self.pc410controller.cmdStop()
  281. self.running=0
  282. def runAction(self):
  283. self.pc410controller.cmdRun()
  284. self.running=1
  285. self.clearRunRecord()
  286. self.starttime=time.time()
  287. def runningToStopped(self):
  288. self.running=0
  289. def stoppedToRunning(self):
  290. self.running=1
  291. self.clearRunRecord()
  292. self.starttime=time.time()
  293. def readAction(self):
  294. program=self.pc410controller.readProgram()
  295. print(program)
  296. if program:
  297. # update current row in table
  298. smodel=self.table.selectionModel()
  299. sindexes=smodel.selectedIndexes()
  300. if sindexes:
  301. rowPosition=max(set([s.row() for s in sindexes]))
  302. else:
  303. rowPosition = self.table.rowCount()
  304. self.table.insertRow(rowPosition)
  305. print(rowPosition)
  306. name="read"
  307. self.setProgramParameters(name,program,rowPosition)
  308. def writeAction(self):
  309. smodel=self.table.selectionModel()
  310. sindexes=smodel.selectedIndexes()
  311. if sindexes:
  312. rowSet=set([s.row() for s in sindexes])
  313. if len(rowSet)!=1:
  314. # fail
  315. return
  316. rowPosition=min(rowSet)
  317. name,program=self.getProgramParameters(rowPosition)
  318. if program:
  319. result=self.pc410controller.writeProgram(program)
  320. if result!=0:
  321. print("### failed to write program!!!")
  322. def clearRunRecord(self):
  323. self.runrecord=([],[])
  324. def addRowAction(self):
  325. self.addRow(-1)
  326. def addRow(self,rowPosition=-1):
  327. if rowPosition==-1:
  328. smodel=self.table.selectionModel()
  329. sindexes=smodel.selectedIndexes()
  330. if sindexes:
  331. rowPosition=max(set([s.row() for s in sindexes]))+1
  332. else:
  333. rowPosition = self.table.rowCount()
  334. self.table.insertRow(rowPosition)
  335. def removeRow(self,rowPosition):
  336. if self.table.rowCount()<=1:
  337. self.addRow(-1)
  338. self.table.removeRow(rowPosition)
  339. #@pyqtSlot(int,float,float)
  340. def tableContextMenu(self,point):
  341. index = self.table.indexAt(point)
  342. if index.isValid() :
  343. # show context menu
  344. self.contextMenu = QMenu(self)
  345. taddrowabove = self.contextMenu.addAction("Insert row above")
  346. taddrowbelow = self.contextMenu.addAction("Insert row below")
  347. self.contextMenu.addSeparator()
  348. tdeleterow = self.contextMenu.addAction("Delete row")
  349. # I want to perform actions only for a single column(E.g: Context menu only for column 4 of my table
  350. # Need help here....???
  351. action = self.contextMenu.exec_(self.table.mapToGlobal(point))
  352. row0=index.row()
  353. if action == taddrowabove:
  354. self.addRow(row0)
  355. elif action == taddrowbelow:
  356. self.addRow(row0+1)
  357. elif action == tdeleterow:
  358. self.removeRow(row0)
  359. def getTimeOfRun(self):
  360. dt=self.lasttime-self.starttime
  361. def showTime(self):
  362. if self.running:
  363. self.lasttime=time.time()
  364. dt=self.lasttime-self.starttime
  365. h=int((dt/3600)%100)
  366. m=int((dt/60)%60)
  367. s=int(dt%60)
  368. label_time="%02d:%02d:%02d"%(h,m,s)
  369. #current_time = QTime.currentTime()
  370. #label_time = current_time.toString('hh:mm:ss')
  371. self.clocklabel.setText(label_time)
  372. def build(self):
  373. if 1:
  374. font = QFont('Arial', self.fontsize) #, QFont.Bold)
  375. self.setFont(font)
  376. toplayout = QHBoxLayout()
  377. layout = QVBoxLayout()
  378. toplayout.setContentsMargins(0,0,0,0)
  379. layout.setContentsMargins(0,0,0,0)
  380. toplayout.addLayout(layout)
  381. layoutside = QVBoxLayout()
  382. toplayout.addLayout(layoutside)
  383. self.addButton(layoutside,"Save",self.saveAction,"action-save",colour="green")
  384. self.addButton(layoutside,"Add row",self.addRowAction,"action-addrow",colour="cyan")
  385. self.addButton(layoutside,"Run",self.runAction,"action-run",colour="yellow")
  386. self.addButton(layoutside,"Stop",self.stopAction,"action-stop",colour="red")
  387. self.addButton(layoutside,"Read program",self.readAction,"action-readprogram",colour="blue")
  388. self.addButton(layoutside,"Write program",self.writeAction,"action-writeprogram",colour="green")
  389. l=QLabel("Temperature over time")
  390. layout.addWidget(l)
  391. self.plot_graph = pg.GraphicsLayoutWidget()
  392. #self.plot_graph = pg.PlotWidget()
  393. self.plot_graph.setBackground("w")
  394. self.lineplot=SimplifiedPlotItemMovement()
  395. self.plot_graph.addItem(self.lineplot)
  396. self.lineplot.pointMoved.connect(self.graphPointMoved)
  397. self.plottexts=[]
  398. if 1:
  399. self.scatter = pg.ScatterPlotItem(size=2, brush=pg.mkBrush(255, 0, 0, 0))
  400. #plot_item = self.plot_graph.addPlot()
  401. #scatter_item = pg.ScatterPlotItem()
  402. self.lineplot.addItem(self.scatter)
  403. #self.plot_graph.addItem(self.scatter)
  404. else:
  405. self.recordplot = pg.PlotCurveItem(size=2, pen=pg.mkPen(color='#ff0000', width=1))
  406. self.plot_graph.addItem(self.recordplot)
  407. self.clearRunRecord()
  408. self.lineplot.setTitle('<span style="color: blue; font-size: 20pt">Temperature vs Time</span>')
  409. self.lineplot.setLabel("left", "Temperature (°C)")
  410. self.lineplot.setLabel("bottom", "Time (seconds)")
  411. self.timer = QTimer(self)
  412. self.timer.timeout.connect(self.showTime)
  413. self.timer.start(1000)
  414. hsize=self.clockHeight-5*2
  415. font = QFont('Arial', hsize, QFont.Bold)
  416. self.clocklabel = QLabel()
  417. self.clocklabel.setAlignment(Qt.AlignCenter)
  418. self.clocklabel.setFont(font)
  419. self.clocklabel.setContentsMargins(5,5,5,5)
  420. layoutside.addWidget(self.clocklabel)
  421. info=QGridLayout()
  422. infoslots={}
  423. def addInfoSlot(infoslots,name,label=None):
  424. if not label: label=name
  425. row=len(infoslots)
  426. l=QLabel(label)
  427. e=QLabel()
  428. infoslots[name]=(name,l,e)
  429. info.addWidget(l,row,0)
  430. info.addWidget(e,row,1)
  431. def addInfoSlot2D(infoslots,name,label=None):
  432. if not label: label=name
  433. count=len(infoslots)
  434. row=count//2
  435. col=count%2
  436. l=QLabel(label)
  437. e=QLabel()
  438. infoslots[name]=(name,l,e)
  439. info.addWidget(l,row,col*2+0)
  440. info.addWidget(e,row,col*2+1)
  441. faddinfoslot=addInfoSlot
  442. if self.infoColumns==2: faddinfoslot=addInfoSlot2D
  443. 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"]:
  444. # "HB",
  445. faddinfoslot(infoslots,param)
  446. layoutside.addLayout(info)
  447. self.infoslots=infoslots
  448. layout.addWidget(self.plot_graph)
  449. if 1:
  450. l=QLabel("Settings")
  451. layout.addWidget(l)
  452. if 0:
  453. hbuttons = QHBoxLayout()
  454. self.addButton(hbuttons,"Add row",self.addRow,"action-addrow",colour="cyan")
  455. layout.addLayout(hbuttons)
  456. """
  457. Lead Sn63Pb37
  458. step r L d
  459. 1 1 85 70
  460. 2 1 150 35
  461. 3 1 185 50
  462. 4 END Hb=230
  463. Lead-Free Sn965.5Ag3Cu0.5
  464. step r L d
  465. 1 1 85 60
  466. 2 1 140 45
  467. 3 1 170 25
  468. 3 1 220 50
  469. 5 END hb=230
  470. """
  471. fieldrows=[["Sn63Pb37", 1,85,70, 1,150,35, 1,185,50, -0.01],
  472. ["Sn965.5Ag3Cu0.5", 1,85,60, 1,140,45, 1,170,25, 1,220,50, -0.01],
  473. ]
  474. fieldrows=self.settings.get("fieldrows",fieldrows)
  475. headers=["Name"]
  476. for i in range(1,9):
  477. headers+=["r%d"%i,"l%d"%i,"t%d"%i]
  478. data={}
  479. for i in range(0,len(headers)):
  480. data[headers[i]]=[]
  481. for row in fieldrows:
  482. fieldrow={}
  483. for ki in range(0,len(headers)):
  484. k=headers[ki]
  485. if ki>=len(row): v=""
  486. else: v=row[ki]
  487. data[k]+=[v]
  488. #data[self.decoded]+=[0]
  489. table = TableView(headers,data,len(data[headers[0]]),len(headers))
  490. self.table=table
  491. table.setContentsMargins(0,0,0,0)
  492. table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
  493. table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
  494. table.setSelectionBehavior(QAbstractItemView.SelectRows);
  495. table.cbSelectionChanged=self.tableSelectionChanged
  496. table.cbCellChanged=self.tableSelectionChanged #CellChanged
  497. def tableClicked(*args):
  498. item=args[0]
  499. row=item.row()
  500. table.clicked.connect(tableClicked)
  501. # setting context menu policy on my table, "self.table"
  502. self.table.setContextMenuPolicy(Qt.CustomContextMenu)
  503. # setting context menu request by calling a function,"self.tableContextMenu"
  504. self.table.customContextMenuRequested.connect(self.tableContextMenu)
  505. table.show()
  506. table.resize(250, 150)
  507. layout.addWidget(self.table)
  508. if layout:
  509. widget = QWidget()
  510. widget.setLayout(toplayout)
  511. self.setCentralWidget(widget)
  512. @pyqtSlot(int,float,float)
  513. def graphPointMoved(self,index,x,y):
  514. if index!=0:
  515. row0=self.selectedRow
  516. time,temp=self.buildCoordinates(row0)
  517. """
  518. moving time of point 1,3,5 etc means changing rate and adding diff to dwell time
  519. moving time of point 2,4,6 etc means changing dwell time, and changing rate of the one after
  520. """
  521. step=(index-1)//2
  522. stepi=1+step*3
  523. rgi0=stepi+0
  524. lgi0=stepi+1
  525. tgi0=stepi+2
  526. if (index%2)==0:
  527. # 0,2,4 etc
  528. # change rate of next to compensate for time change of this
  529. # should cap but that time is constructed from rate and delta so max x would then be
  530. # time[index]+(temp[index+1]-temp[index])/rate
  531. # cap to minx and mintemp
  532. rate0=float(self.table.item(row0,rgi0).text())
  533. dy0=(y-temp[index-2])
  534. minx=time[index-2]
  535. if rate0: minx+=abs(dy0)/rate0
  536. x=max(minx,x)
  537. y=max(self.mintemp,y)
  538. if index+1<len(temp):
  539. rgi1=rgi0+3
  540. rg=float(self.table.item(row0,rgi1).text())
  541. # cap to maxx and maxy
  542. dx0=0
  543. oldrate=rg
  544. if oldrate: dx0=abs((temp[index+1]-temp[index]))/oldrate
  545. maxx=time[index]+dx0
  546. x=min(x,maxx)
  547. # dwell time must not be negative so y might not change more than rate can fit until next level?
  548. #maxy=temp[index-2]+(temp[index
  549. if rate0:
  550. maxy=temp[index-2]+(maxx-time[index-2])*rate0
  551. y=min(maxy,y)
  552. # change rate
  553. dy1=temp[index+1]-y
  554. dx1=time[index+1]-x
  555. newrate=0.0
  556. if dx1: newrate=abs(dy1/dx1)
  557. else: newrate=9999.0
  558. rg=newrate
  559. newitem = QTableWidgetItem(str(rg))
  560. self.table.setItem(row0,rgi1,newitem)
  561. # compute dtime and dtemp with capped x and y
  562. dtime=x-time[index]
  563. dtemp=y-temp[index]
  564. # moving time means change dwell time
  565. dy0=y-temp[index-1]
  566. dt0=dy0/rate0
  567. tg=float(self.table.item(row0,tgi0).text())
  568. tg+=dtime-dt0
  569. newitem = QTableWidgetItem(str(tg))
  570. self.table.setItem(row0,tgi0,newitem)
  571. # change level
  572. lg=float(self.table.item(row0,lgi0).text())
  573. lg+=dtemp
  574. newitem = QTableWidgetItem(str(lg))
  575. self.table.setItem(row0,lgi0,newitem)
  576. else:
  577. # 1,3,5 etc
  578. # change level of edited point and change rate of rise to compensate
  579. # cap x and y
  580. x=max(x,time[index-1])
  581. x=min(x,time[index+1])
  582. y=max(self.mintemp,y)
  583. dtime=x-time[index]
  584. dtemp=y-temp[index]
  585. dy1=y-temp[index-1]
  586. dx1=x-time[index-1]
  587. newrate=0.0
  588. if dx1: newrate=abs(dy1/dx1)
  589. #newrate=max(0.01,newrate)
  590. # set new rate
  591. rg=float(self.table.item(row0,rgi0).text())
  592. rg=newrate
  593. newitem = QTableWidgetItem(str(rg))
  594. self.table.setItem(row0,rgi0,newitem)
  595. # set new level
  596. lg=float(self.table.item(row0,lgi0).text())
  597. lg+=dtemp
  598. newitem = QTableWidgetItem(str(lg))
  599. self.table.setItem(row0,lgi0,newitem)
  600. # change dwell time to compensate for move of editied point and also change next rate to compensate
  601. tg=float(self.table.item(row0,tgi0).text())
  602. tg-=dtime
  603. newitem = QTableWidgetItem(str(tg))
  604. self.table.setItem(row0,tgi0,newitem)
  605. if index+2<len(temp):
  606. dy1=temp[index+2]-y
  607. dx1=time[index+2]-time[index+1]
  608. newrate=0.0
  609. if dx1: newrate=abs(dy1/dx1)
  610. else: newrate=9999.0
  611. # set new next rate?
  612. rgi1=rgi0+3
  613. rg=float(self.table.item(row0,rgi1).text())
  614. rg=newrate
  615. newitem = QTableWidgetItem(str(rg))
  616. self.table.setItem(row0,rgi1,newitem)
  617. time,temp=self.buildCoordinates(row0)
  618. newdata=np.array([time,temp])
  619. self.lineplot.plot_item_control.setData(time, temp)
  620. self.updateLinePlotTexts(time,temp)
  621. def setProgramParameters(self,name,program,row0):
  622. for g in range(1,9):
  623. rn="r%d"%g
  624. ln="l%d"%g
  625. tn="t%d"%g
  626. rgi=1+(g-1)*3+0
  627. lgi=1+(g-1)*3+1
  628. tgi=1+(g-1)*3+2
  629. rg,lg,tg=-0.01,0.0,0.0
  630. newitem = QTableWidgetItem(name)
  631. self.table.setItem(row0,0,newitem)
  632. if g-1<len(program):
  633. rg,lg,tg=program[g-1]
  634. newitem = QTableWidgetItem(str(rg))
  635. self.table.setItem(row0,rgi,newitem)
  636. newitem = QTableWidgetItem(str(lg))
  637. self.table.setItem(row0,lgi,newitem)
  638. newitem = QTableWidgetItem(str(tg))
  639. self.table.setItem(row0,tgi,newitem)
  640. def getProgramParameters(self,row0):
  641. # r1,L1,d1
  642. # r2,L2,d2
  643. # ...
  644. program=[]
  645. name="program"
  646. try:
  647. name=self.table.item(row0,0).text()
  648. except:
  649. raise
  650. for g in range(1,9):
  651. rn="r%d"%g
  652. ln="l%d"%g
  653. tn="t%d"%g
  654. rgi=1+(g-1)*3+0
  655. lgi=1+(g-1)*3+1
  656. tgi=1+(g-1)*3+2
  657. rg,lg,tg=0.0,0.0,0.0
  658. try:
  659. rg=float(self.table.item(row0,rgi).text())
  660. except: pass
  661. try:
  662. lg=float(self.table.item(row0,lgi).text())
  663. except: pass
  664. try:
  665. tg=float(self.table.item(row0,tgi).text())
  666. except: pass
  667. program+=[(rg,lg,tg)]
  668. # -0.01 as end of program
  669. # 0.0 is step ie skip slope and go straight to dwell
  670. if rg<0: break
  671. return name,program
  672. def buildCoordinates(self,row0):
  673. time=[]
  674. temp=[]
  675. t0=0.0
  676. templ=self.mintemp
  677. temp0=self.mintemp
  678. time+=[t0]
  679. temp+=[temp0]
  680. for g in range(1,9):
  681. rn="r%d"%g
  682. ln="l%d"%g
  683. tn="t%d"%g
  684. rgi=1+(g-1)*3+0
  685. lgi=1+(g-1)*3+1
  686. tgi=1+(g-1)*3+2
  687. rg,lg,tg=0.0,0.0,0.0
  688. try:
  689. rg=float(self.table.item(row0,rgi).text())
  690. except: pass
  691. try:
  692. lg=float(self.table.item(row0,lgi).text())
  693. except: pass
  694. try:
  695. tg=float(self.table.item(row0,tgi).text())
  696. except: pass
  697. if rg<0: break
  698. dtemp=abs(lg-templ)
  699. if rg==0: dttemp=0
  700. else: dttemp=abs(dtemp/rg)
  701. #print(repr((rgi,lgi,tgi,rg,lg,tg,t0,dtemp,dttemp)))
  702. t0+=dttemp
  703. time+=[t0]
  704. temp+=[lg]
  705. t0+=tg
  706. time+=[t0]
  707. temp+=[lg]
  708. templ=lg
  709. return time,temp
  710. def updateLinePlotTexts(self,time,temp):
  711. if 1:
  712. for t in self.plottexts:
  713. self.lineplot.removeItem(t)
  714. del t
  715. self.plottexts=[]
  716. else:
  717. self.lineplot.clear()
  718. pen = pg.mkPen(color=(0, 0, 0))
  719. if 1:
  720. if self.lineplot.plot_item_control==None:
  721. self.lineplot.plot_item_control = self.lineplot.plot(time,temp, symbolBrush=(255,0,0), symbolPen='w')
  722. else:
  723. self.lineplot.plot_item_control.setData(time, temp)
  724. for i in range(1,len(time)):
  725. t0,t1=time[i-1],time[i]
  726. k0,k1=temp[i-1],temp[i]
  727. s=""
  728. rotateAxis=None
  729. if k0==k1:
  730. # dwell time at tempature
  731. s="%4.1fs at %4.1fC"%(t1-t0,k0)
  732. pos=(t0+t1)/2,k0
  733. angle=0
  734. elif t0!=t1:
  735. s="%.1fC/s"%((k1-k0)/(t1-t0))
  736. pos=(t0+t1)/2,(k0+k1)/2
  737. # angle
  738. # won't work since graph is not scale same in x and y
  739. angle=0 # math.atan2(pos[1],pos[0])*180/math.pi
  740. rotateAxis=(t1-t0,k1-k0)
  741. else:
  742. # same time so just skip
  743. pass
  744. if s:
  745. text = pg.TextItem(s,color=(0,0,0),anchor=(0.5,1),angle=angle,rotateAxis=rotateAxis)
  746. text.setFont(self.tfont)
  747. self.lineplot.addItem(text)
  748. text.setPos(*pos)
  749. self.plottexts+=[text]
  750. @pyqtSlot(int,int)
  751. def tableSelectionChanged(self,row0,row1,text=None):
  752. #print("selection changed",row0,row1,self.cursorMoved)
  753. if self.cursorMoved: return
  754. # time = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  755. # temperature = [30, 32, 34, 32, 33, 31, 29, 32, 35, 45]
  756. self.selectedRow=row0
  757. time,temp=self.buildCoordinates(row0)
  758. self.updateLinePlotTexts(time,temp)
  759. @pyqtSlot(int,int)
  760. def tableCellChanged(self,row,col,text):
  761. return self.realTableCellChanged(row,col,text)
  762. def realTableCellChanged(self,row,col,text):
  763. pass
  764. @pyqtSlot(str,int,int)
  765. def updateProgress(self,s,cmdp,p):
  766. # update action etc
  767. pass
  768. @pyqtSlot(dict)
  769. def updateState(self,state):
  770. # update enablement and such
  771. self.state=state
  772. for k,v in state.items():
  773. if k in self.infoslots:
  774. name,l,e=self.infoslots[k]
  775. # v needs to be processed and appenededlike C/s etc?
  776. e.setText(str(v))
  777. _os=state.get("OS","")
  778. if self.running and "program stopped" in _os:
  779. self.runningToStopped()
  780. if not self.running and "program running" in _os:
  781. self.stoppedToRunning()
  782. @pyqtSlot(dict)
  783. def updateRecord(self,state):
  784. # update enablement and such
  785. dt=state["dt"]
  786. value=state["value"]
  787. # feed graph and update it!!!
  788. tlist,vlist=self.runrecord
  789. tlist+=[dt]
  790. vlist+=[value]
  791. self.runrecord=(tlist,vlist)
  792. #print(self.runrecord)
  793. points=np.array([tlist,vlist])
  794. #print(points)
  795. self.scatter.setData(*points)
  796. self.scatter.updateSpots()
  797. self.scatter.invalidate()
  798. @pyqtSlot(str)
  799. def updateInfo(self,infopath):
  800. # update info about device
  801. pass
  802. def cmdlog(self,key,returncode,elements):
  803. print("key",repr(key),"returncode",returncode,"elem",elements)
  804. def statecallback(self,state):
  805. QMetaObject.invokeMethod(self,"updateState", Qt.QueuedConnection,Q_ARG(dict, state))
  806. def infocallback(self,infopath):
  807. QMetaObject.invokeMethod(self,"updateInfo", Qt.QueuedConnection,Q_ARG(str, infopath))
  808. def recordcallback(self,dt,value):
  809. QMetaObject.invokeMethod(self,"updateRecord", Qt.QueuedConnection,Q_ARG(dict, {"dt":dt,"value":value}))
  810. if __name__ == '__main__':
  811. if usefbs:
  812. # run with fbs ie fbs run etc
  813. appctxt = ApplicationContext() # 1. Instantiate ApplicationContext
  814. app=appctxt.app
  815. else:
  816. # run as plain python
  817. appctxt = QApplication(sys.argv)
  818. app=appctxt
  819. window = MainWindow(appctxt,sys.argv)
  820. window.resize(800, 600)
  821. window.show()
  822. exit_code = app.exec() # 2. Invoke appctxt.app.exec()
  823. sys.exit(exit_code)