From 2dba6d5bac0ed9deca498f92687653e9b635bd64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Thu, 15 Nov 2012 00:25:01 +0100 Subject: [PATCH] started qt frontend with solder type xml storage --- qtplot.py | 312 +++++++++++++++++++++++------- solder_types/lead_noclean.xml | 35 ++++ solder_types/leadfree_noclean.xml | 35 ++++ 3 files changed, 312 insertions(+), 70 deletions(-) create mode 100644 solder_types/lead_noclean.xml create mode 100644 solder_types/leadfree_noclean.xml 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +