started qt frontend with solder type xml storage
This commit is contained in:
parent
44485eac01
commit
2dba6d5bac
312
qtplot.py
312
qtplot.py
|
@ -1,9 +1,16 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import sys, os, random
|
import sys, os, random
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
|
import pylab
|
||||||
from PyQt4 import QtGui, QtCore
|
from PyQt4 import QtGui, QtCore
|
||||||
|
|
||||||
from numpy import arange, sin, pi, array, linspace
|
from numpy import arange, sin, pi, array, linspace, arange
|
||||||
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
|
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
|
||||||
from matplotlib.figure import Figure
|
from matplotlib.figure import Figure
|
||||||
|
from matplotlib.lines import Line2D
|
||||||
from matplotlib.path import Path
|
from matplotlib.path import Path
|
||||||
import matplotlib.patches as patches
|
import matplotlib.patches as patches
|
||||||
|
|
||||||
|
@ -13,106 +20,257 @@ progname = os.path.basename(sys.argv[0])
|
||||||
progversion = "0.1"
|
progversion = "0.1"
|
||||||
|
|
||||||
|
|
||||||
class MyMplCanvas(FigureCanvas):
|
class State(object):
|
||||||
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
|
def __init__(self, name, temp):
|
||||||
|
self.name = name
|
||||||
|
self.temp = temp
|
||||||
|
|
||||||
|
|
||||||
|
class Solder(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.psteps = []
|
||||||
|
self.durations = dict()
|
||||||
|
self.rates = dict()
|
||||||
|
|
||||||
|
#start = self.add_state("start", 25)
|
||||||
|
#ps = self.add_state("preheat start", 150)
|
||||||
|
#pe = self.add_state("preheat end", 185)
|
||||||
|
#tal = self.add_state("tal", 220)
|
||||||
|
#peak = self.add_state("peak", 250)
|
||||||
|
#end = self.add_state("end", 25)
|
||||||
|
|
||||||
|
#self.add_duration((ps, pe), 100)
|
||||||
|
#self.add_duration((tal, peak, tal), 100)
|
||||||
|
|
||||||
|
#self.add_rate((start, ps), 1)
|
||||||
|
#self.add_rate((ps, pe), 1)
|
||||||
|
#self.add_rate((pe, tal), 1)
|
||||||
|
#self.add_rate((tal, end), -2)
|
||||||
|
|
||||||
|
|
||||||
|
def add_state(self, name, temp):
|
||||||
|
s = State(name, temp)
|
||||||
|
self.psteps.append(s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def add_rate(self, states, rate):
|
||||||
|
self.rates[states] = rate
|
||||||
|
|
||||||
|
def add_duration(self, states, duration):
|
||||||
|
self.durations[states] = duration
|
||||||
|
|
||||||
|
def get_state(self, name):
|
||||||
|
for i in self.psteps:
|
||||||
|
if i.name == name:
|
||||||
|
return i
|
||||||
|
return None
|
||||||
|
|
||||||
|
def calc_profile(self):
|
||||||
|
|
||||||
|
x = list()
|
||||||
|
y = list()
|
||||||
|
self.time = 0
|
||||||
|
used_steps = set()
|
||||||
|
for pstep in self.psteps:
|
||||||
|
#print "-- ", repr(pstep.name), pstep.temp, pstep.used, self.time, x, y
|
||||||
|
|
||||||
|
if pstep != self.psteps[0] and pstep not in used_steps:
|
||||||
|
ix = self.psteps.index(pstep)
|
||||||
|
raise Exception("step %r not connected to step %r or step %r" % (pstep.name, self.psteps[ix-1].name, self.psteps[ix+1].name))
|
||||||
|
|
||||||
|
psteps = None
|
||||||
|
duration = None
|
||||||
|
for sts, dur in self.durations.iteritems():
|
||||||
|
if sts[0] == pstep:
|
||||||
|
duration = dur
|
||||||
|
psteps = sts
|
||||||
|
break
|
||||||
|
|
||||||
|
if pstep not in used_steps:
|
||||||
|
used_steps.add(pstep)
|
||||||
|
x.append(self.time)
|
||||||
|
y.append(pstep.temp)
|
||||||
|
|
||||||
|
if duration is not None:
|
||||||
|
if len(psteps) == 3:
|
||||||
|
used_steps.add(psteps[1])
|
||||||
|
used_steps.add(psteps[2])
|
||||||
|
|
||||||
|
self.time += duration / 2
|
||||||
|
x.append(self.time)
|
||||||
|
y.append(psteps[1].temp)
|
||||||
|
#print "3er duration", (self.time, psteps[1].temp)
|
||||||
|
|
||||||
|
self.time += duration / 2
|
||||||
|
x.append(self.time)
|
||||||
|
y.append(psteps[2].temp)
|
||||||
|
|
||||||
|
#print "3er duration", (self.time, psteps[2].temp)
|
||||||
|
else:
|
||||||
|
y.append(psteps[1].temp)
|
||||||
|
used_steps.add(psteps[1])
|
||||||
|
self.time += duration
|
||||||
|
x.append(self.time)
|
||||||
|
#print "2er duration", (self.time, psteps[1].temp)
|
||||||
|
else:
|
||||||
|
for sts, rate in self.rates.iteritems():
|
||||||
|
if sts[0] == pstep:
|
||||||
|
used_steps.add(sts[1])
|
||||||
|
duration = (sts[1].temp - pstep.temp) / rate
|
||||||
|
self.time += duration
|
||||||
|
x.append(self.time)
|
||||||
|
y.append(sts[1].temp)
|
||||||
|
#print "rate", (self.time, sts[1].temp)
|
||||||
|
|
||||||
|
|
||||||
|
return array(map(float, x)), array(map(float, y)), max(x), max(y)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unpack(filename):
|
||||||
|
xmltree = etree.parse(filename)
|
||||||
|
root = xmltree.getroot()
|
||||||
|
s = Solder()
|
||||||
|
for state in root[0].findall("state"):
|
||||||
|
s.add_state(state.attrib["name"], int(state.attrib["temperature"]))
|
||||||
|
for duration in root[0].findall("duration"):
|
||||||
|
states = list()
|
||||||
|
for state in duration:
|
||||||
|
states.append(s.get_state(state.attrib["name"]))
|
||||||
|
s.add_duration(tuple(states), int(duration.attrib["value"]))
|
||||||
|
for rate in root[0].findall("rate"):
|
||||||
|
#print rate
|
||||||
|
states = list()
|
||||||
|
for state in rate:
|
||||||
|
states.append(s.get_state(state.attrib["name"]))
|
||||||
|
s.add_rate(tuple(states), int(rate.attrib["value"]))
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MyDynamicMplCanvas(FigureCanvas):
|
||||||
|
"""A canvas that updates itself every second with a new plot."""
|
||||||
def __init__(self, parent=None, width=5, height=4, dpi=100):
|
def __init__(self, parent=None, width=5, height=4, dpi=100):
|
||||||
fig = Figure(figsize=(width, height), dpi=dpi)
|
self.fig = Figure(figsize=(width, height), dpi=dpi)
|
||||||
self.axes = fig.add_subplot(111)
|
self.axes = self.fig.add_subplot(111)
|
||||||
# We want the axes cleared every time plot() is called
|
# We want the axes cleared every time plot() is called
|
||||||
self.axes.hold(False)
|
self.axes.set_axis_bgcolor('black')
|
||||||
|
self.axes.set_title(u'reflow profile', size=12)
|
||||||
|
self.axes.set_xlabel(u'time (seconds)', size=12)
|
||||||
|
self.axes.set_ylabel(u'temperature (°C)', size=12)
|
||||||
|
|
||||||
self.compute_initial_figure()
|
pylab.setp(self.axes.get_xticklabels(), fontsize=8)
|
||||||
|
pylab.setp(self.axes.get_yticklabels(), fontsize=8)
|
||||||
|
|
||||||
#
|
super(MyDynamicMplCanvas, self).__init__(self.fig)
|
||||||
FigureCanvas.__init__(self, fig)
|
|
||||||
self.setParent(parent)
|
self.setParent(parent)
|
||||||
|
|
||||||
|
self.solder = Solder.unpack('/home/hotshelf/dev/reflow/solder_types/leadfree_noclean.xml')
|
||||||
|
|
||||||
|
self.compute_initial_figure()
|
||||||
FigureCanvas.setSizePolicy(self,
|
FigureCanvas.setSizePolicy(self,
|
||||||
QtGui.QSizePolicy.Expanding,
|
QtGui.QSizePolicy.Expanding,
|
||||||
QtGui.QSizePolicy.Expanding)
|
QtGui.QSizePolicy.Expanding)
|
||||||
FigureCanvas.updateGeometry(self)
|
FigureCanvas.updateGeometry(self)
|
||||||
|
|
||||||
def compute_initial_figure(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MyDynamicMplCanvas(MyMplCanvas):
|
|
||||||
"""A canvas that updates itself every second with a new plot."""
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
MyMplCanvas.__init__(self, *args, **kwargs)
|
|
||||||
timer = QtCore.QTimer(self)
|
timer = QtCore.QTimer(self)
|
||||||
|
|
||||||
|
self.counter = list()
|
||||||
QtCore.QObject.connect(timer, QtCore.SIGNAL("timeout()"), self.update_figure)
|
QtCore.QObject.connect(timer, QtCore.SIGNAL("timeout()"), self.update_figure)
|
||||||
timer.start(1000)
|
timer.start(1000)
|
||||||
|
|
||||||
|
|
||||||
def compute_initial_figure(self):
|
def compute_initial_figure(self):
|
||||||
x1_min = 150-20.
|
|
||||||
y1_min = 150.
|
|
||||||
|
|
||||||
x2_min = x1_min + 100.
|
"""test foo bar"""
|
||||||
y2_min = 200.
|
|
||||||
|
|
||||||
x3_min = x2_min + 20.
|
#start_rate = 1
|
||||||
y3_min = 220.
|
#start_temp = 25
|
||||||
|
#start_period = None
|
||||||
|
|
||||||
x4_min = x3_min + 249. - y3_min
|
## warmup
|
||||||
y4_min = 249.
|
#warmup_rate = 1
|
||||||
|
#warmup_temp = 155
|
||||||
|
#preheat_period = None
|
||||||
|
|
||||||
x5_min = x4_min + (y4_min - y3_min) / 3 * 2
|
## preheat start
|
||||||
y5_min = y3_min
|
#preheat_start_rate = 1
|
||||||
|
#preheat_start_temp = 155
|
||||||
|
#preheat_start_period = 100
|
||||||
|
|
||||||
x6_min = x5_min + y5_min-20.
|
## preheat end
|
||||||
y6_min = 20.
|
#preheat_end_rate = 1
|
||||||
|
#preheat_end_temp = 185
|
||||||
|
#preheat_end_period = None
|
||||||
|
|
||||||
p1x = array([0., x1_min, x2_min, x3_min, x4_min, x5_min, x6_min])
|
#tal_rate = 1
|
||||||
p1y = array([20., y1_min, y2_min, y3_min, y4_min, y5_min, y6_min])
|
#tal_temp = 220
|
||||||
|
#tal_duration = 60
|
||||||
|
|
||||||
interp = pchip(p1x, p1y)
|
#peak_temp = 250
|
||||||
|
#peak_rate = 1
|
||||||
|
|
||||||
xx = linspace(0., x6_min, x6_min)
|
#x, y = self.solder.calc_profile()
|
||||||
|
|
||||||
ynew = interp(xx)
|
#print "x", repr(x)
|
||||||
|
#print "y", repr(y)
|
||||||
|
|
||||||
print "len xx", len(xx)
|
#dtp_min = 99999.
|
||||||
print "len p1x", len(p1x)
|
#dtp_max = 0.
|
||||||
print "xy", zip(p1x, p1y)
|
#dtpi = -1
|
||||||
print "ynew", ynew
|
|
||||||
|
|
||||||
dtp_min = 99999.
|
#dtn_min = -99999.
|
||||||
dtp_max = 0.
|
#dtn_max = 0.
|
||||||
dtpi = -1
|
#dtni = -1
|
||||||
|
#for i in xrange(1, len(y)):
|
||||||
|
#tmp = (y[i] - y[i-1]) / (x[i] - x[i-1])
|
||||||
|
#if tmp > 0:
|
||||||
|
#if tmp < dtp_min:
|
||||||
|
#dtp_min = tmp
|
||||||
|
#dtpi = i
|
||||||
|
#elif tmp > dtp_max:
|
||||||
|
#dtp_max = tmp
|
||||||
|
#dtpi = i
|
||||||
|
#elif tmp < 0:
|
||||||
|
#if tmp > dtn_min:
|
||||||
|
#dtn_min = tmp
|
||||||
|
#dtni = i
|
||||||
|
#elif tmp < dtn_max:
|
||||||
|
#dtn_max = tmp
|
||||||
|
#dtni = i
|
||||||
|
#print "max negative", dtn_min, dtn_max, dtni
|
||||||
|
#print "max positive", dtp_min, dtp_max, dtpi
|
||||||
|
|
||||||
dtn_min = -99999.
|
self.plot_data, = self.axes.plot([], linewidth=1.0, color=(0,0,1))
|
||||||
dtn_max = 0.
|
|
||||||
dtni = -1
|
|
||||||
for i in xrange(1, len(ynew)):
|
|
||||||
tmp = ynew[i] - ynew[i-1]
|
|
||||||
if tmp > 0:
|
|
||||||
if tmp < dtp_min:
|
|
||||||
dtp_min = tmp
|
|
||||||
dtpi = i
|
|
||||||
elif tmp > dtp_max:
|
|
||||||
dtp_max = tmp
|
|
||||||
dtpi = i
|
|
||||||
elif tmp < 0:
|
|
||||||
if tmp > dtn_min:
|
|
||||||
dtn_min = tmp
|
|
||||||
dtni = i
|
|
||||||
elif tmp < dtn_max:
|
|
||||||
dtn_max = tmp
|
|
||||||
dtni = i
|
|
||||||
print "max negative", dtn_min, dtn_max, dtni
|
|
||||||
print "max positive", dtp_min, dtp_max, dtpi
|
|
||||||
|
|
||||||
self.axes.plot(p1x, p1y, "bo", xx, ynew, "r-")
|
|
||||||
#self.axes.plot(p1x, p1y, 'r-o')
|
#self.axes.plot(p1x, p1y, 'r-o')
|
||||||
|
|
||||||
def update_figure(self):
|
def update_figure(self):
|
||||||
# Build a list of 4 random integers between 0 and 10 (both inclusive)
|
# Build a list of 4 random integers between 0 and 10 (both inclusive)
|
||||||
#l = [ random.randint(0, 10) for i in xrange(4) ]
|
x, y, xmax, ymax = self.solder.calc_profile()
|
||||||
|
|
||||||
#self.axes.plot([0, 1, 2, 3], l, 'r')
|
lines = list()
|
||||||
#self.draw()
|
legend = list()
|
||||||
pass
|
|
||||||
|
cols = ("lightgreen", "yellow", "orange", "red")
|
||||||
|
for ix, i in enumerate(self.solder.psteps[1:-1]):
|
||||||
|
line = Line2D([0, xmax + 20], [i.temp, i.temp],
|
||||||
|
transform=self.axes.transData, figure=self.fig, color=cols[ix], label="name")
|
||||||
|
lines.append(line)
|
||||||
|
|
||||||
|
self.fig.lines = lines
|
||||||
|
|
||||||
|
self.axes.set_xbound(lower=0, upper=xmax + 20)
|
||||||
|
self.axes.set_ybound(lower=0, upper=ymax + 20)
|
||||||
|
|
||||||
|
#self.axes.grid(True, color='gray')
|
||||||
|
|
||||||
|
pylab.setp(self.axes.get_xticklabels(), visible=True)
|
||||||
|
|
||||||
|
self.plot_data.set_xdata(x)
|
||||||
|
self.plot_data.set_ydata(y)
|
||||||
|
self.axes.legend(("Estimated profile",))
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
|
||||||
class ApplicationWindow(QtGui.QMainWindow):
|
class ApplicationWindow(QtGui.QMainWindow):
|
||||||
|
@ -124,6 +282,8 @@ class ApplicationWindow(QtGui.QMainWindow):
|
||||||
self.file_menu = QtGui.QMenu('&File', self)
|
self.file_menu = QtGui.QMenu('&File', self)
|
||||||
self.file_menu.addAction('&Quit', self.fileQuit,
|
self.file_menu.addAction('&Quit', self.fileQuit,
|
||||||
QtCore.Qt.CTRL + QtCore.Qt.Key_Q)
|
QtCore.Qt.CTRL + QtCore.Qt.Key_Q)
|
||||||
|
self.file_menu.addAction('&Save plot', self.save_plot,
|
||||||
|
QtCore.Qt.CTRL + QtCore.Qt.Key_S)
|
||||||
self.menuBar().addMenu(self.file_menu)
|
self.menuBar().addMenu(self.file_menu)
|
||||||
|
|
||||||
self.help_menu = QtGui.QMenu('&Help', self)
|
self.help_menu = QtGui.QMenu('&Help', self)
|
||||||
|
@ -134,17 +294,29 @@ class ApplicationWindow(QtGui.QMainWindow):
|
||||||
|
|
||||||
self.main_widget = QtGui.QWidget(self)
|
self.main_widget = QtGui.QWidget(self)
|
||||||
|
|
||||||
|
self.dpi = 100
|
||||||
|
|
||||||
|
#pl = QtGui.QVBoxLayout(self.main_widget)
|
||||||
|
#self.p
|
||||||
|
|
||||||
l = QtGui.QVBoxLayout(self.main_widget)
|
l = QtGui.QVBoxLayout(self.main_widget)
|
||||||
#sc = MyStaticMplCanvas(self.main_widget, width=5, height=4, dpi=100)
|
#sc = MyStaticMplCanvas(self.main_widget, width=5, height=4, dpi=100)
|
||||||
dc = MyDynamicMplCanvas(self.main_widget, width=5, height=4, dpi=100)
|
self.dc = MyDynamicMplCanvas(self.main_widget, width=5, height=4, dpi=self.dpi)
|
||||||
#l.addWidget(sc)
|
#l.addWidget(sc)
|
||||||
l.addWidget(dc)
|
l.addWidget(self.dc)
|
||||||
|
|
||||||
self.main_widget.setFocus()
|
self.main_widget.setFocus()
|
||||||
self.setCentralWidget(self.main_widget)
|
self.setCentralWidget(self.main_widget)
|
||||||
|
|
||||||
self.statusBar().showMessage("All hail matplotlib!", 2000)
|
self.statusBar().showMessage("All hail matplotlib!", 2000)
|
||||||
|
|
||||||
|
def save_plot(self):
|
||||||
|
file_choices = "PNG (*.png)|*.png"
|
||||||
|
|
||||||
|
filename = QtGui.QFileDialog.getSaveFileName(self, 'Save File', 'qtplot.png')
|
||||||
|
print type(filename), dir(filename)
|
||||||
|
self.dc.print_figure(str(filename), dpi=self.dpi)
|
||||||
|
|
||||||
def fileQuit(self):
|
def fileQuit(self):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<xml>
|
||||||
|
<solder_type name="lead noclean" description="">
|
||||||
|
<state name="start" temperature="25" />
|
||||||
|
<state name="preheat start" temperature="150" />
|
||||||
|
<state name="preheat end" temperature="185" />
|
||||||
|
<state name="tal" temperature="220" />
|
||||||
|
<state name="peak" temperature="260" />
|
||||||
|
<state name="end" temperature="25" />
|
||||||
|
<duration value="100" >
|
||||||
|
<state name="preheat start" />
|
||||||
|
<state name="preheat end" />
|
||||||
|
</duration>
|
||||||
|
<duration value="100" >
|
||||||
|
<state name="tal" />
|
||||||
|
<state name="peak" />
|
||||||
|
<state name="tal" />
|
||||||
|
</duration>
|
||||||
|
<rate value="1">
|
||||||
|
<state name="start" />
|
||||||
|
<state name="preheat start" />
|
||||||
|
</rate>
|
||||||
|
<rate value="1">
|
||||||
|
<state name="preheat start" />
|
||||||
|
<state name="preheat end" />
|
||||||
|
</rate>
|
||||||
|
<rate value="1">
|
||||||
|
<state name="preheat end" />
|
||||||
|
<state name="tal" />
|
||||||
|
</rate>
|
||||||
|
<rate value="-2">
|
||||||
|
<state name="peak" />
|
||||||
|
<state name="end" />
|
||||||
|
</rate>
|
||||||
|
</solder_type>
|
||||||
|
</xml>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<xml>
|
||||||
|
<solder_type name="leadfree noclean" description="">
|
||||||
|
<state name="start" temperature="25" />
|
||||||
|
<state name="preheat start" temperature="150" />
|
||||||
|
<state name="preheat end" temperature="185" />
|
||||||
|
<state name="tal" temperature="220" />
|
||||||
|
<state name="peak" temperature="250" />
|
||||||
|
<state name="end" temperature="25" />
|
||||||
|
<duration value="100" >
|
||||||
|
<state name="preheat start" />
|
||||||
|
<state name="preheat end" />
|
||||||
|
</duration>
|
||||||
|
<duration value="100" >
|
||||||
|
<state name="tal" />
|
||||||
|
<state name="peak" />
|
||||||
|
<state name="tal" />
|
||||||
|
</duration>
|
||||||
|
<rate value="1">
|
||||||
|
<state name="start" />
|
||||||
|
<state name="preheat start" />
|
||||||
|
</rate>
|
||||||
|
<rate value="1">
|
||||||
|
<state name="preheat start" />
|
||||||
|
<state name="preheat end" />
|
||||||
|
</rate>
|
||||||
|
<rate value="1">
|
||||||
|
<state name="preheat end" />
|
||||||
|
<state name="tal" />
|
||||||
|
</rate>
|
||||||
|
<rate value="-2">
|
||||||
|
<state name="peak" />
|
||||||
|
<state name="end" />
|
||||||
|
</rate>
|
||||||
|
</solder_type>
|
||||||
|
</xml>
|
Loading…
Reference in New Issue