diff --git a/qtplot.py b/qtplot.py
index 7c99886..68ff9cf 100644
--- a/qtplot.py
+++ b/qtplot.py
@@ -1,9 +1,16 @@
+# -*- coding: utf-8 -*-
+
import sys, os, random
+
+import xml.etree.ElementTree as etree
+
+import pylab
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.figure import Figure
+from matplotlib.lines import Line2D
from matplotlib.path import Path
import matplotlib.patches as patches
@@ -13,106 +20,257 @@ progname = os.path.basename(sys.argv[0])
progversion = "0.1"
-class MyMplCanvas(FigureCanvas):
- """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
+class State(object):
+ 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):
- fig = Figure(figsize=(width, height), dpi=dpi)
- self.axes = fig.add_subplot(111)
+ self.fig = Figure(figsize=(width, height), dpi=dpi)
+ self.axes = self.fig.add_subplot(111)
# 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)
- #
- FigureCanvas.__init__(self, fig)
+ super(MyDynamicMplCanvas, self).__init__(self.fig)
self.setParent(parent)
+ self.solder = Solder.unpack('/home/hotshelf/dev/reflow/solder_types/leadfree_noclean.xml')
+
+ self.compute_initial_figure()
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
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)
+
+ self.counter = list()
QtCore.QObject.connect(timer, QtCore.SIGNAL("timeout()"), self.update_figure)
timer.start(1000)
+
def compute_initial_figure(self):
- x1_min = 150-20.
- y1_min = 150.
- x2_min = x1_min + 100.
- y2_min = 200.
+ """test foo bar"""
- x3_min = x2_min + 20.
- y3_min = 220.
+ #start_rate = 1
+ #start_temp = 25
+ #start_period = None
- x4_min = x3_min + 249. - y3_min
- y4_min = 249.
+ ## warmup
+ #warmup_rate = 1
+ #warmup_temp = 155
+ #preheat_period = None
- x5_min = x4_min + (y4_min - y3_min) / 3 * 2
- y5_min = y3_min
+ ## preheat start
+ #preheat_start_rate = 1
+ #preheat_start_temp = 155
+ #preheat_start_period = 100
- x6_min = x5_min + y5_min-20.
- y6_min = 20.
+ ## preheat end
+ #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])
- p1y = array([20., y1_min, y2_min, y3_min, y4_min, y5_min, y6_min])
+ #tal_rate = 1
+ #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)
- print "len p1x", len(p1x)
- print "xy", zip(p1x, p1y)
- print "ynew", ynew
+ #dtp_min = 99999.
+ #dtp_max = 0.
+ #dtpi = -1
- dtp_min = 99999.
- dtp_max = 0.
- dtpi = -1
+ #dtn_min = -99999.
+ #dtn_max = 0.
+ #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.
- 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.plot_data, = self.axes.plot([], linewidth=1.0, color=(0,0,1))
#self.axes.plot(p1x, p1y, 'r-o')
def update_figure(self):
# 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')
- #self.draw()
- pass
+ lines = list()
+ legend = list()
+
+ 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):
@@ -124,6 +282,8 @@ class ApplicationWindow(QtGui.QMainWindow):
self.file_menu = QtGui.QMenu('&File', self)
self.file_menu.addAction('&Quit', self.fileQuit,
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.help_menu = QtGui.QMenu('&Help', self)
@@ -134,17 +294,29 @@ class ApplicationWindow(QtGui.QMainWindow):
self.main_widget = QtGui.QWidget(self)
+ self.dpi = 100
+
+ #pl = QtGui.QVBoxLayout(self.main_widget)
+ #self.p
+
l = QtGui.QVBoxLayout(self.main_widget)
#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(dc)
+ l.addWidget(self.dc)
self.main_widget.setFocus()
self.setCentralWidget(self.main_widget)
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):
self.close()
diff --git a/solder_types/lead_noclean.xml b/solder_types/lead_noclean.xml
new file mode 100644
index 0000000..e9a9a8b
--- /dev/null
+++ b/solder_types/lead_noclean.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/solder_types/leadfree_noclean.xml b/solder_types/leadfree_noclean.xml
new file mode 100644
index 0000000..ff3f65d
--- /dev/null
+++ b/solder_types/leadfree_noclean.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+