From f662ce3b524513635a8dc6a35081997ce6960308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Sun, 18 Nov 2012 15:14:17 +0100 Subject: [PATCH] install script, good progress with constraint listings and editing --- main.cpp => firmware/main.cpp | 0 oven_control.cpp => firmware/oven_control.cpp | 0 profile.cpp => firmware/profile.cpp | 0 .../profile_control.cpp | 0 .../Arduino_Monitor.py | 0 reflowctl/__init__.py | 0 plot.py => reflowctl/plot.py | 0 reflowctl/reflowctl_gui.py | 589 ++++++++++++++++++ .../solder_types}/lead_noclean.xml | 0 .../solder_types}/leadfree_noclean.xml | 0 reflowctl_gui.py | 413 ------------ setup.py | 51 ++ 12 files changed, 640 insertions(+), 413 deletions(-) rename main.cpp => firmware/main.cpp (100%) rename oven_control.cpp => firmware/oven_control.cpp (100%) rename profile.cpp => firmware/profile.cpp (100%) rename profile_control.cpp => firmware/profile_control.cpp (100%) rename Arduino_Monitor.py => reflowctl/Arduino_Monitor.py (100%) create mode 100644 reflowctl/__init__.py rename plot.py => reflowctl/plot.py (100%) create mode 100755 reflowctl/reflowctl_gui.py rename {solder_types => reflowctl/solder_types}/lead_noclean.xml (100%) rename {solder_types => reflowctl/solder_types}/leadfree_noclean.xml (100%) delete mode 100755 reflowctl_gui.py create mode 100644 setup.py diff --git a/main.cpp b/firmware/main.cpp similarity index 100% rename from main.cpp rename to firmware/main.cpp diff --git a/oven_control.cpp b/firmware/oven_control.cpp similarity index 100% rename from oven_control.cpp rename to firmware/oven_control.cpp diff --git a/profile.cpp b/firmware/profile.cpp similarity index 100% rename from profile.cpp rename to firmware/profile.cpp diff --git a/profile_control.cpp b/firmware/profile_control.cpp similarity index 100% rename from profile_control.cpp rename to firmware/profile_control.cpp diff --git a/Arduino_Monitor.py b/reflowctl/Arduino_Monitor.py similarity index 100% rename from Arduino_Monitor.py rename to reflowctl/Arduino_Monitor.py diff --git a/reflowctl/__init__.py b/reflowctl/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plot.py b/reflowctl/plot.py similarity index 100% rename from plot.py rename to reflowctl/plot.py diff --git a/reflowctl/reflowctl_gui.py b/reflowctl/reflowctl_gui.py new file mode 100755 index 0000000..fb15a86 --- /dev/null +++ b/reflowctl/reflowctl_gui.py @@ -0,0 +1,589 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import sys, os, random, copy + +from operator import attrgetter + +import xml.etree.ElementTree as etree + +import pylab +from PyQt4 import QtGui, QtCore + +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 +#from mpltools import annotation + + +progname = os.path.basename(sys.argv[0]) +progversion = "0.1" + +PSTEP_COLORS = ("lightgreen", "yellow", "orange", "red") + + +class State(object): + def __init__(self, name, temp): + self.name = name + self.temp = temp + + +class Solder(object): + def __init__(self): + self.psteps = [] + self.durations = list() + self.rates = list() + self.name = None + + #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 __unicode__(self): + return unicode(self.name) + + def __str__(self): + return self.name + + def color(self, index): + return PSTEP_COLORS[index] + + def add_state(self, name, temp): + s = State(name, temp) + self.psteps.append(s) + return s + + def add_rate(self, states, rate): + self.rates.append((states, rate)) + + def add_duration(self, states, duration): + self.durations.append((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() + duration_points = dict() + rate_points = dict() + self.time = 0 + used_steps = set() + for ix, pstep in enumerate(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: + 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) + + duration_points[ix] = (x[-3:], y[-3:]) + + #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) + duration_points[ix] = (x[-2:], y[-2:]) + #print "2er duration", (self.time, psteps[1].temp) + else: + for ex, (sts, rate) in enumerate(self.rates): + 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) + rate_points[ex] = (x[-2:], y[-2:]) + + + return array(map(float, x)), array(map(float, y)), max(x), max(y), duration_points, rate_points + + + @staticmethod + def unpack(filename): + xmltree = etree.parse(filename) + root = xmltree.getroot() + s = Solder() + s.name = root[0].attrib["name"] + 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(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(states, int(rate.attrib["value"])) + + return s + + def serialize(self, pstep_list): + return ", ".join(map(attrgetter("name"), pstep_list)) + + +class SolderListModel(QtCore.QAbstractListModel): + def __init__(self, parent=None, *args): + """ datain: a list where each item is a row + """ + super(SolderListModel, self).__init__(parent, *args) + + dirname = os.path.join(os.path.dirname(__file__), "solder_types") + dirlisting = os.listdir(dirname) + self.listdata = [] + for p in dirlisting: + #try: + self.listdata.append(Solder.unpack(os.path.join(dirname, p))) + #except Exception: + #pass + + def rowCount(self, parent=QtCore.QModelIndex()): + return len(self.listdata) + + + def headerData(self, col, orientation, role): + if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: + return QtCore.QVariant("Solder Paste") + return QtCore.QVariant() + + def data(self, index, role): + if index.isValid() and role == QtCore.Qt.DisplayRole: + return QtCore.QVariant(self.listdata[index.row()].name) + else: + return QtCore.QVariant() + + +class PStepModel(QtCore.QAbstractTableModel): + def __init__(self, parent=None, *args): + super(PStepModel, self).__init__(parent, *args) + self.psteps = list() + self.headerdata = [u"Name", u"Temperature (°C)"] + + def headerData(self, col, orientation, role): + if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: + return QtCore.QVariant(self.headerdata[col]) + return QtCore.QVariant() + + def rowCount(self, parent): + return len(self.psteps) + + def columnCount(self, parent): + return 2 + + def data(self, index, role): + if not index.isValid(): + return QtCore.QVariant() + + if role == QtCore.Qt.DisplayRole: + col = index.column() + if col == 0: + return QtCore.QVariant(self.psteps[index.row()].name) + else: + return QtCore.QVariant(self.psteps[index.row()].temp) + + if index.column() == 0 and role == QtCore.Qt.DecorationRole: + p = QtGui.QPixmap(10,10) + cr = row = index.row() + color = index.row() in (0, len(self.psteps)-1) and QtGui.QColor("black") or QtGui.QColor(PSTEP_COLORS[cr-1]) + p.fill(color) + return p + + return QtCore.QVariant() + + def remove_pstep(self, index): + del self.psteps[index] + self.reset() + + def add_pstep(self, index, pstep): + self.psteps.insert(index, pstep) + self.reset() + + def setSteps(self, steps): + assert isinstance(steps, list) + self.psteps = steps + self.reset() + + + +class MyDynamicMplCanvas(FigureCanvas): + """A canvas that updates itself every second with a new plot.""" + def __init__(self, parent, myapp, width, height, dpi): + self.fig = Figure(figsize=(width, height), dpi=dpi) + super(MyDynamicMplCanvas, self).__init__(self.fig) + self.axes = self.fig.add_subplot(111) + + ## We want the axes cleared every time plot() is called + 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) + + #pylab.setp(self.axes.get_xticklabels(), fontsize=8) + #pylab.setp(self.axes.get_yticklabels(), fontsize=8) + + self.setParent(parent) + self.myapp = myapp + + self.solder = None + + self.plot_data, = self.axes.plot([], linewidth=1.0, color=(0,0,1), zorder=10) + self.selection_data, = self.axes.plot([], linewidth=1.0, color=(1,1,1), zorder=5) + + FigureCanvas.setSizePolicy(self, + QtGui.QSizePolicy.Expanding, + QtGui.QSizePolicy.Expanding) + FigureCanvas.updateGeometry(self) + + timer = QtCore.QTimer(self) + + self.counter = list() + QtCore.QObject.connect(timer, QtCore.SIGNAL("timeout()"), self.update_figure) + timer.start(1000) + + + def update_figure(self): + #Build a list of 4 random integers between 0 and 10 (both inclusive) + x, y, xmax, ymax, duration_points, rate_points = self.solder.calc_profile() + + lines = list() + legend = list() + + #for states, value in self.durations.iteritems(): + #annotation.slope_marker((states[0]) + + self.fig.lines = lines + + self.axes.set_xbound(lower=0, upper=xmax + 20) + self.axes.set_ybound(lower=0, upper=ymax + 20) + self.axes.set_ymargin(0) + self.axes.set_xmargin(0) + + self.axes.set_yticks([state.temp for state in self.solder.psteps]) + self.axes.set_xticks(x) + + self.plot_data.set_xdata(x) + self.plot_data.set_ydata(y) + self.plot_data.set_zorder(20) + + duration_widget = self.myapp.duration_widget + + #self.selection_data.set_xdata(array(da)) + #self.selection_data.set_ydata(array(db)) + + 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=self.solder.color(ix), label="name", zorder=1) + lines.append(line) + + self.axes.legend(("Estimated profile",)) + self.draw() + + +# contraint_list | label - checkboxes +# label - value + + +class ConstraintWidget(QtGui.QWidget): + def __init__(self, name): + super(ConstraintWidget, self).__init__() + self.name = name + self.solder = None + + self.value = QtGui.QSpinBox(self) + self.value.setRange(-300, 400) + self.constraint_model = QtGui.QStringListModel(self) # constraint selection + self.all_psteps = PStepModel(self) # pstep selection pool + self.selected_psteps = PStepModel(self) # selected psteps + + #self.all_psteps.setSteps(self.solder.psteps) + + self.add_button = QtGui.QPushButton("Add", self) + self.remove_button = QtGui.QPushButton("Remove", self) + + bg = QtGui.QWidget(self) + gbl = QtGui.QVBoxLayout(bg) + + gbl.addWidget(self.add_button) + gbl.addWidget(self.remove_button) + gbl.addStretch(5) + gbl.addWidget(self.value) + + + self.constraint_list_view = QtGui.QListView(self) + self.constraint_list_view.setModel(self.constraint_model) + + self.all_psteps_view = QtGui.QListView(self) + self.all_psteps_view.setModel(self.all_psteps) + + self.selected_psteps_view = QtGui.QListView(self) + self.selected_psteps_view.setModel(self.selected_psteps) + + #self.left = QtGui.QWidget(self) + #gl = QtGui.QGridLayout(self.left) + #gl.addWidget(QtGui.QLabel(u"Steps"), 0,0) + #gl.addWidget(QtGui.QLabel(name), 1,0) + #gl.addWidget(self.checkboxes_group, 0, 1) + #gl.addWidget(self.value, 1, 1) + #self.gl = gl + + + h = QtGui.QHBoxLayout() + h.addWidget(self.constraint_list_view) + h.addWidget(self.all_psteps_view) + h.addWidget(bg) + h.addWidget(self.selected_psteps_view) + self.setLayout(h) + + self.connect( + self.constraint_list_view, + QtCore.SIGNAL("clicked(QModelIndex)"), + self.constraint_clicked) + + self.connect(self.add_button, QtCore.SIGNAL("clicked()"), self.add_constraint) + self.connect(self.remove_button, QtCore.SIGNAL("clicked()"), self.remove_constraint) + + def add_constraint(self): + raise NotImplementedError() + + def remove_constraint(self): + raise NotImplementedError() + + def setData(self, solder): + self.solder = solder + #for k,v in self.checkboxes.iteritems(): + #self.cl.removeWidget(v) + #self.checkboxes = dict() + self.all_psteps.setSteps(self.solder.psteps) + + #for i in solder.psteps: + #cb = QtGui.QCheckBox(self, checked=False) + #label = QtGui.QLabel(i.name, self) + #label.setBuddy(cb) + #self.checkboxes[i] = cb + ##self.cl.addWidget(label) + ##self.cl.addWidget(cb) + + self.getConstraints() + + + def getConstraints(self): + raise NotImplementedError() + + def constraint_clicked(self, index): + #for cb in self.checkboxes.itervalues(): + #cb.setChecked(False) + + self.handle_clicked(index) + + def handle_clicked(self, index): + raise NotImplementedError() + +class DurationConstraintWidget(ConstraintWidget): + + def getConstraints(self): + tmp = QtCore.QStringList() + for ix in xrange(len(self.solder.durations)): + tmp << unicode(ix + 1) + + self.constraint_model.setStringList(tmp) + k, t = self.solder.durations[0] + + self.value.setValue(t) + self.constraint_list_view.setCurrentIndex(self.constraint_model.index(0, 0)) + + def add_constraint(self): + self.selected_psteps.psteps.append(self.all_psteps.psteps[self.all_psteps_view.currentIndex().row()]) + self.selected_psteps.reset() + #self.selected_psteps_view.setCurrentIndex(QtCore.QModelIndex()) + self.selected_psteps_view.clearSelection() + + def remove_constraint(self): + del self.selected_psteps.psteps[self.selected_psteps_view.currentIndex().row()] + self.selected_psteps.reset() + #self.selected_psteps_view.setCurrentIndex(QtCore.QModelIndex()) + self.selected_psteps_view.clearSelection() + + def handle_clicked(self, index): + psteps, value = self.solder.durations[index.row()] + self.selected_psteps.setSteps(psteps) + self.value.setValue(value) + + +class RateConstraintWidget(ConstraintWidget): + + def getConstraints(self): + tmp = QtCore.QStringList() + for ix in xrange(len(self.solder.durations)): + tmp << unicode(ix + 1) + + self.constraint_model.setStringList(tmp) + k, t = self.solder.rates[0] + + self.value.setValue(t) + self.constraint_list_view.setCurrentIndex(self.constraint_model.index(0, 0)) + + def handle_clicked(self, index): + psteps, value = self.solder.durations[index.row()] + self.selected_psteps.setSteps(psteps) + self.value.setValue(value) + +class ApplicationWindow(QtGui.QMainWindow): + def __init__(self): + QtGui.QMainWindow.__init__(self) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.setWindowTitle("application main window") + + 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) + self.menuBar().addSeparator() + self.menuBar().addMenu(self.help_menu) + + self.help_menu.addAction('&About', self.about) + + self.main_widget = QtGui.QWidget(self) + self.profile_widget = QtGui.QWidget(self) + self.steps_box = QtGui.QGroupBox(self) + + self.tab_widget = QtGui.QTabWidget(self) + self.duration_widget = DurationConstraintWidget(u"Duration (s)") + self.rate_widget = RateConstraintWidget(u"Rate (°C/s)") + self.tab_widget.addTab(self.duration_widget, u"Duration (s)") + self.tab_widget.addTab(self.rate_widget, u"Rate (°C/s)") + + self.dpi = 100 + + pl = QtGui.QHBoxLayout(self.profile_widget) + sl = QtGui.QVBoxLayout(self.steps_box) + + self.solder_model = SolderListModel(self) + self.pstep_model = PStepModel(self) + + self.pstep_view = QtGui.QTableView() + self.pstep_view.setModel(self.pstep_model) + self.pstep_view.verticalHeader().setVisible(False) + self.pstep_view.resizeColumnsToContents() + + self.solder_view = QtGui.QListView() + self.solder_view.setModel(self.solder_model) + self.connect( + self.solder_view, + QtCore.SIGNAL("clicked(QModelIndex)"), + self.solder_selected) + + pl.addWidget(self.solder_view, 1) + pl.addWidget(self.pstep_view, 2) + pl.addWidget(self.tab_widget, 7) + #pl.addWidget(self.duration_widget) + #pl.addWidget(self.rate_widget) + + l = QtGui.QVBoxLayout(self.main_widget) + self.dc = MyDynamicMplCanvas(self, self, width=5, height=4, dpi=self.dpi) + + self.solder_view.setCurrentIndex(self.solder_model.index(0,0)) + self.solder_selected(self.solder_model.index(0,0)) + + l.addWidget(self.profile_widget, 2) + l.addWidget(self.dc, 8) + + self.main_widget.setFocus() + self.setCentralWidget(self.main_widget) + + self.statusBar().showMessage("I'm in reflow heaven", 2000) + + def solder_selected(self, index): + if index.isValid(): + self.dc.solder = self.solder_model.listdata[index.row()] + self.pstep_model.setSteps(self.dc.solder.psteps) + self.pstep_view.resizeColumnsToContents() + self.duration_widget.setData(self.dc.solder) + self.rate_widget.setData(self.dc.solder) + + 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() + + def closeEvent(self, ce): + self.fileQuit() + + def about(self): + QtGui.QMessageBox.about(self, "About %s" % progname, + u"%(prog)s version %(version)s\n" \ + u"Copyright \N{COPYRIGHT SIGN} 2012 Stefan Kögl\n\n" \ + u"reflowctl frontend" % {"prog": progname, "version": progversion}) + + +def main(): + + qApp = QtGui.QApplication(sys.argv) + + aw = ApplicationWindow() + aw.setWindowTitle("%s" % progname) + aw.show() + sys.exit(qApp.exec_()) + + +if __name__ == '__main__': + main() diff --git a/solder_types/lead_noclean.xml b/reflowctl/solder_types/lead_noclean.xml similarity index 100% rename from solder_types/lead_noclean.xml rename to reflowctl/solder_types/lead_noclean.xml diff --git a/solder_types/leadfree_noclean.xml b/reflowctl/solder_types/leadfree_noclean.xml similarity index 100% rename from solder_types/leadfree_noclean.xml rename to reflowctl/solder_types/leadfree_noclean.xml diff --git a/reflowctl_gui.py b/reflowctl_gui.py deleted file mode 100755 index 5e1ffc2..0000000 --- a/reflowctl_gui.py +++ /dev/null @@ -1,413 +0,0 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/python - -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, 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 - -from scipy.interpolate import * - -progname = os.path.basename(sys.argv[0]) -progversion = "0.1" - - -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() - self.name = None - - #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 __unicode__(self): - return unicode(self.name) - - def __str__(self): - return self.name - - - 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() - s.name = root[0].attrib["name"] - 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 SolderListModel(QtCore.QAbstractListModel): - def __init__(self, parent=None, *args): - """ datain: a list where each item is a row - """ - super(SolderListModel, self).__init__(parent, *args) - self.listdata = [Solder.unpack(os.path.join("solder_types", p)) for p in os.listdir("solder_types")] - - def rowCount(self, parent=QtCore.QModelIndex()): - return len(self.listdata) - - def data(self, index, role): - if index.isValid() and role == QtCore.Qt.DisplayRole: - return QtCore.QVariant(self.listdata[index.row()].name) - else: - return QtCore.QVariant() - - -class PStepModel(QtCore.QAbstractTableModel): - def __init__(self, parent=None, *args): - super(PStepModel, self).__init__(parent, *args) - self.psteps = list() - - def rowCount(self, parent): - return len(self.psteps) - - def columnCount(self, parent): - return 2 - - def data(self, index, role): - if not index.isValid(): - return QtCore.QVariant() - elif role != QtCore.Qt.DisplayRole: - return QtCore.QVariant() - - col = index.column() - if col == 0: - return QtCore.QVariant(self.psteps[index.row()].name) - else: - return QtCore.QVariant(self.psteps[index.row()].temperature) - - def removeRows(self, start, _count, _parent): - print type(start), type(_count) - self.beginRemoveRows(_parent, start, start + _count) - self.psteps[:start].extend(self.psteps[start + _count:]) - self.endRemoveRows() - - -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): - 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.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) - - pylab.setp(self.axes.get_xticklabels(), fontsize=8) - pylab.setp(self.axes.get_yticklabels(), fontsize=8) - - super(MyDynamicMplCanvas, self).__init__(self.fig) - self.setParent(parent) - - self.solder = None - - self.compute_initial_figure() - FigureCanvas.setSizePolicy(self, - QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Expanding) - FigureCanvas.updateGeometry(self) - - 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): - - """test foo bar""" - - #start_rate = 1 - #start_temp = 25 - #start_period = None - - ## warmup - #warmup_rate = 1 - #warmup_temp = 155 - #preheat_period = None - - ## preheat start - #preheat_start_rate = 1 - #preheat_start_temp = 155 - #preheat_start_period = 100 - - ## preheat end - #preheat_end_rate = 1 - #preheat_end_temp = 185 - #preheat_end_period = None - - #tal_rate = 1 - #tal_temp = 220 - #tal_duration = 60 - - #peak_temp = 250 - #peak_rate = 1 - - #x, y = self.solder.calc_profile() - - #print "x", repr(x) - #print "y", repr(y) - - #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 - - 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) - x, y, xmax, ymax = self.solder.calc_profile() - - 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): - def __init__(self): - QtGui.QMainWindow.__init__(self) - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.setWindowTitle("application main window") - - 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) - self.menuBar().addSeparator() - self.menuBar().addMenu(self.help_menu) - - self.help_menu.addAction('&About', self.about) - - self.main_widget = QtGui.QWidget(self) - self.profile_widget = QtGui.QWidget(self) - self.steps_box = QtGui.QGroupBox(self) - - self.dpi = 100 - - pl = QtGui.QHBoxLayout(self.profile_widget) - sl = QtGui.QVBoxLayout(self.steps_box) - - self.solder_model = SolderListModel(self) - self.pstep_model = PStepModel(self) - self.solder_view = QtGui.QListView() - self.pstep_view = QtGui.QTableView() - self.solder_view.setModel(self.solder_model) - self.pstep_view.setModel(self.pstep_model) - - self.connect(self.solder_view, QtCore.SIGNAL("clicked(QModelIndex)"), self.solder_selected) - pl.addWidget(self.solder_view) - pl.addWidget(self.pstep_view) - - l = QtGui.QVBoxLayout(self.main_widget) - self.dc = MyDynamicMplCanvas(self.main_widget, width=5, height=4, dpi=self.dpi) - self.dc.solder = self.solder_model.listdata[0] - l.addWidget(self.profile_widget, 1) - l.addWidget(self.dc, 10) - - self.main_widget.setFocus() - self.setCentralWidget(self.main_widget) - - self.statusBar().showMessage("I'm in reflow heaven", 2000) - - def solder_selected(self, index): - if index.isValid(): - self.dc.solder = self.solder_model.listdata[index.row()] - self.pstep_model.removeRows(0, self.pstep_model.rowCount(QtCore.QModelIndex())) - self.pstep_model.insertRows() - - 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() - - def closeEvent(self, ce): - self.fileQuit() - - def about(self): - QtGui.QMessageBox.about(self, "About %s" % progname, -u"""%(prog)s version %(version)s -Copyright \N{COPYRIGHT SIGN} 2012 Stefan Kögl - -reflowctl frontend""" -% {"prog": progname, "version": progversion}) - - -qApp = QtGui.QApplication(sys.argv) - -aw = ApplicationWindow() -aw.setWindowTitle("%s" % progname) -aw.show() -sys.exit(qApp.exec_()) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..718a699 --- /dev/null +++ b/setup.py @@ -0,0 +1,51 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from distribute_setup import use_setuptools +use_setuptools() + +import sys +from setuptools import find_packages, setup + +if sys.version_info >= (3,): + extras['use_2to3'] = True + extras['convert_2to3_doctests'] = ['src/your/module/README.txt'] + #extra['use_2to3_fixers'] = ['your.fixers'] + +setup( + name='reflowctl_gui', + version='0.1', + packages=find_packages(), + + include_package_data = True, + + install_requires=["numpy", "matplotlib"], + + # installing unzipped + zip_safe = False, + + # predefined extension points, e.g. for plugins + entry_points = """ + [console_scripts] + reflowctl_gui = reflowctl.reflowctl_gui:main + """, + # pypi metadata + author = "Stefan Kögl", + + # FIXME: add author email + author_email = "", + description = "paludis useflag management", + + # FIXME: add long_description + long_description = """ + """, + + # FIXME: add license + license = "GPL", + + # FIXME: add keywords + keywords = "", + + # FIXME: add download url + url = "", +)