#!/usr/bin/python # -*- coding: utf-8 -*- import sys, os, random, copy, struct from collections import deque from operator import attrgetter import xml.etree.ElementTree as etree import pylab import numpy 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" def calc_colors(count): if count == 1: return [QtGui.QColor(0, 255, 0),] r = 0 g = 255 step = int(512. / (count-1)) colors = list() for i in range(count): colors.append(QtGui.QColor(r, g, 0)) if r < 255: r += step if r > 255: g -= (r - 256) r = 255 g = max(0, g) else: g -= step g = max(0, g) return colors def set_colors(temp_levels): colors = calc_colors(len(temp_levels) - 1) ix = 0 for temp_level in temp_levels: if not temp_level.is_env: temp_level.color = colors[ix] ix += 1 else: temp_level.color = QtGui.QColor("black") def create_profile_header(x_list, y_list): tpl = "#ifndef _H_SOLDER_PROFILE\n" \ "#define _H_SOLDER_PROFILE\n" \ "\n" \ "%s\n" \ "\n" \ "};\n" \ "\n" \ "#endif\n" data_tpl = "unsigned int profile_%d[%d][2] = \n%s\n" \ s = list() ix = 0 for x, y in zip(x_list, y_list): tmp = list() for a, b in zip(x, y): tmp.append("{%d, %d}" % (a, b)) s.append(data_tpl % (ix, len(x), "\n".join(tmp))) ix += 1 open("test.h", "w").write(tpl % ("\n".join(s))) def create_profile_packet(x, y): # msg = [length, x_0, y_0,x_1, y_1, ..., x_length-1, y_length-1] tmp = [len(x) * 2 * 2,] for a, b in zip(x, y): tmp.append(a) tmp.append(b) tpl = "H" + (len(x) * 2) * "H" res = struct.pack(tpl, *tmp) return res def getTemperature(): return 20. class TempLevel(QtCore.QObject): def __init__(self, name, temp, is_env=False): self.name = name self.temp = temp self.is_env = is_env self.color = None class Solder(QtCore.QObject): log_message = QtCore.pyqtSignal(str) def __init__(self, filename, name=str(), description=str(), parent=None): super(Solder, self).__init__(parent) self.filename = filename self.name = name self.description = description self.temp_levels = list() self.durations = list() self.rates = list() self.changed = False def __unicode__(self): return unicode(self.name) def __str__(self): return self.name def add_temp_level(self, name, temp, is_env): s = TempLevel(name, temp, is_env) self.temp_levels.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_temp_level_by_name(self, name): assert isinstance(name, basestring) for i in self.temp_levels: if i.name == name: return i return None def get_temp_level(self, ix): assert isinstance(ix, int) return self.temp_levels[ix] def calc_rate(self, x1, y1, x2, y2): return (y2 - y1) / (x2 - x1) def check_duration_constraints(self, temp_level, used): x = list() y = list() temp_levels = None value = None for temp_levels, value in self.durations: tl_len = len(temp_levels) if temp_levels and temp_levels[0] == temp_level and tl_len > 1: if temp_level not in used: used.add(temp_level) x.append(self.time) y.append(temp_level.temp) if tl_len == 2: y.append(temp_levels[1].temp) used.add(temp_levels[1]) self.time += value x.append(self.time) elif tl_len >= 3: part = value / (tl_len - 1) for tl in temp_levels[1:]: used.add(tl) self.time += part x.append(self.time) y.append(tl.temp) self.log.append("* Duration connection: TempLevel %r connected to TempLevels %r" % (temp_level.name, [tl.name for tl in temp_levels[1:]])) return x, y def check_rate_constraints(self, temp_level, used): x = list() y = list() for temp_levels, value in self.rates: tl_len = len(temp_levels) if temp_levels and temp_levels[0] == temp_level and tl_len > 1: if temp_level not in used: used.add(temp_level) x.append(self.time) y.append(temp_level.temp) self.time += (temp_levels[1].temp - temp_level.temp) / value used.add(temp_levels[1]) x.append(self.time) y.append(temp_levels[1].temp) self.log.append("* Rate connection: TempLevel %r connected to TempLevels %r" % (temp_level.name, [tl.name for tl in temp_levels[1:]])) return x, y def calc_profile(self): self.log = list() x = list() y = list() duration_points = dict() rate_points = dict() self.time = 0 used = set() unused = list() for temp_level in self.temp_levels: dur_x, dur_y = self.check_duration_constraints(temp_level, used) rate_x, rate_y = self.check_rate_constraints(temp_level, used) if len(dur_x) > 0: x.extend(dur_x) y.extend(dur_y) elif len(rate_x) > 0: x.extend(rate_x) y.extend(rate_y) elif temp_level not in used: unused.append(temp_level) self.log.append("") map(self.log.append, ["* Missing Connection: %r" % tl.name for tl in unused]) self.log_message.emit("\n".join(self.log)) del self.log return array(map(float, x)), array(map(float, y)), min(x), max(x), min(y), max(y), used, unused @staticmethod def unpack(filename, parent): xmltree = etree.parse(filename) root = xmltree.getroot() solder_node = root[0] s = Solder(filename, solder_node.attrib["name"], solder_node.attrib["description"], parent) env_count = 0 for temp_level in solder_node.findall("state"): tstr = temp_level.attrib["temperature"] is_env = False try: temp = int(tstr) except ValueError: if tstr == "$ENV": temp = getTemperature() is_env = True env_count += 1 s.add_temp_level(temp_level.attrib["name"], temp, is_env) set_colors(s.temp_levels) for duration in solder_node.findall("duration"): temp_levels = list() for temp_level in duration: temp_levels.append(s.get_temp_level_by_name(temp_level.attrib["name"])) s.add_duration(temp_levels, int(duration.attrib["value"])) for rate in solder_node.findall("rate"): temp_levels = list() for temp_level in rate: temp_levels.append(s.get_temp_level_by_name(temp_level.attrib["name"])) s.add_rate(temp_levels, int(rate.attrib["value"])) return s def save(self): if self.changed: solder_node = etree.Element("solder_type", {"name" : self.name, "description" : self.description}) for temp_level in self.temp_levels: temp = temp_level.is_env and "$ENV" or str(temp_level.temp) solder_node.append(etree.Element("state", {"name" : temp_level.name, "temperature" : temp})) for temp_levels, value in self.durations: duration = etree.Element("duration", {"value" : str(value)}) for temp_level in temp_levels: duration.append(etree.Element("state", {"name" : temp_level.name})) solder_node.append(duration) for temp_levels, value in self.rates: rate = etree.Element("rate", {"value" : str(value)}) for temp_level in temp_levels: rate.append(etree.Element("state", {"name" : temp_level.name})) solder_node.append(rate) dirname = os.path.join(os.path.dirname(__file__), "solder_types") root = etree.Element("xml") root.append(solder_node) etree.ElementTree(root).write(self.filename, "UTF-8", True) self.changed = False def setChanged(self): print self.setChanged self.changed = True 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.reload() def rowCount(self, parent=QtCore.QModelIndex()): return len(self.listdata) def reload(self): dirname = os.path.join(os.path.dirname(__file__), "solder_types") dirlisting = filter(lambda x: os.path.splitext(x)[1] == ".xml", os.listdir(dirname)) self.listdata = [] for p in dirlisting: self.listdata.append(Solder.unpack(os.path.join(dirname, p), self)) self.listdata.sort(key=lambda x: x.name) self.reset() 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 not index.isValid(): return QtCore.QVariant() solder = self.listdata[index.row()] if role == QtCore.Qt.DisplayRole: return QtCore.QVariant(solder.name) elif role == QtCore.Qt.DecorationRole and solder.changed: return QtGui.QIcon.fromTheme("document-save") def setData(self, index, variant, role): if index.isValid() and role == QtCore.Qt.EditRole: new_name = str(variant.toString()) if new_name and new_name != "": self.listdata[index.row()].name = new_name self.listdata[index.row()].changed = True return True return False def flags(self, index): if not index.isValid(): return 0 return QtCore.Qt.ItemFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable) def create_solder(self): solder = Solder("new", "") tl = solder.add_temp_level("environment temp", getTemperature(), True) tl.color = QtGui.QColor(0, 0, 0) self.listdata.append(solder) self.reset() class TempLevelModel(QtCore.QAbstractTableModel): solder_changed = QtCore.pyqtSignal() def __init__(self, parent): super(TempLevelModel, self).__init__(parent) self._changed = False self.temp_levels = 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.temp_levels) 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.temp_levels[index.row()].name) else: return QtCore.QVariant(self.temp_levels[index.row()].temp) if index.column() == 0 and role == QtCore.Qt.DecorationRole: p = QtGui.QPixmap(10,10) color = self.temp_levels[index.row()].color p.fill(color) return p return QtCore.QVariant() def flags(self, index): if not index.isValid(): return 0 return QtCore.Qt.ItemFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable) def setData(self, index, variant, role): if index.isValid() and role == QtCore.Qt.EditRole: col = index.column() if col == 0: self.temp_levels[index.row()].name = str(variant.toString()) elif col == 1: self.temp_levels[index.row()].temp = variant.toInt()[0] print "emit solder_changed" self.solder_changed.emit() return True return False def remove_temp_level(self, index): tmp = self.temp_levels[index] del self.temp_levels[index] self.reset() self.solder_changed.emit() return tmp def add_temp_level(self, index, temp_level): self.temp_levels.insert(index.row() + 1, temp_level) set_colors(self.temp_levels) self.reset() self.solder_changed.emit() def setTempLevels(self, temp_levels): assert isinstance(temp_levels, list) self.temp_levels = temp_levels self.reset() def clear(self): self.temp_levels = list() self.reset() class Plotter(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(Plotter, self).__init__(self.fig) self.axes = self.fig.add_subplot(111) #self.fig.subplots_adjust( #left=0.1, #bottom=0.05, #right=0.9, #top=0.95, #wspace=0, #hspace=0 #) self.axes.set_axis_bgcolor('white') 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.axes.set_ymargin(0) self.axes.set_xmargin(0) 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) QtCore.QObject.connect(timer, QtCore.SIGNAL("timeout()"), self.update_figure) timer.start(1000) self.updated = False def update_figure(self): self.fig.lines = lines = list() if self.updated: self.updated = False self.axes.patches = list() self.axes.texts = list() self.x = list() self.y = list() self.x, self.y, self.xmin, self.xmax, self.ymin, self.ymax, self.used, self.unused = self.solder.calc_profile() self.plot_data.set_xdata(self.x) self.plot_data.set_ydata(self.y) self.axes.set_xbound(lower=self.xmin, upper=self.xmax + 20) self.axes.set_ybound(lower=self.ymin, upper=self.ymax + 20) self.axes.set_yticks(self.y) self.axes.set_xticks(self.x) for ix, (a, b) in enumerate(zip(self.x[:-1], self.y[:-1])): slope = (self.y[ix+1] - b) / (self.x[ix+1] - a) if not (numpy.isnan(slope) or numpy.isinf(slope)): origin = (a + 10, b) annotation.slope_marker(origin, slope, ax=self.axes) self.axes.legend(("Estimated profile",), loc=2) for temp_level in self.solder.temp_levels: if not temp_level.is_env: line = Line2D([0, self.xmax + 20], [temp_level.temp, temp_level.temp], transform=self.axes.transData, figure=self.fig, color=str(temp_level.color.name()), label="name", zorder=1) lines.append(line) self.draw() def solder_changed(self): print self.solder_changed self.solder.setChanged() self.updated = True def setData(self, solder): self.solder = solder self.updated = True class AddRemoveWidget(QtGui.QWidget): def __init__(self, parent, with_upown=True): #super(AddRemoveWidget, self).__init__(parent) QtGui.QWidget.__init__(self, parent) self.add_button = QtGui.QPushButton(QtGui.QIcon.fromTheme("list-add"), "") self.remove_button = QtGui.QPushButton(QtGui.QIcon.fromTheme("list-remove"), "") sh = "QPushButton:disabled { background-color: #555555; }" self.remove_button.setStyleSheet(sh) self.add_button.setStyleSheet(sh) self._layout = QtGui.QVBoxLayout(self) self._layout.addWidget(self.add_button) self._layout.addWidget(self.remove_button) if with_upown: self.up_button = QtGui.QPushButton(QtGui.QIcon.fromTheme("go-up"), "") self.down_button = QtGui.QPushButton(QtGui.QIcon.fromTheme("go-down"), "") self._layout.addWidget(self.up_button) self._layout.addWidget(self.down_button) self.up_button.setStyleSheet(sh) self.down_button.setStyleSheet(sh) self._layout.addStretch(4) class AddRemoveValueWidget(AddRemoveWidget): def __init__(self, parent): super(AddRemoveValueWidget, self).__init__(parent) self.value = QtGui.QSpinBox(self) self.value.setRange(-500, 500) self._layout.addWidget(self.value) class ConstraintListModel(QtCore.QAbstractListModel): def __init__(self, parent=None, *args): """ datain: a list where each item is a row """ super(ConstraintListModel, self).__init__(parent, *args) self.constraint_list = list() def rowCount(self, parent=QtCore.QModelIndex()): return len(self.constraint_list) 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(index.row() + 1) else: return QtCore.QVariant() def append_constraint(self): self.constraint_list.append([[], 0]) self.reset() def remove_constraint(self): del self.constraint_list[self.currentIndex().row()] self.reset() class ConstraintWidget(QtGui.QWidget): solder_changed = QtCore.pyqtSignal() def __init__(self, name): super(ConstraintWidget, self).__init__() self.name = name self.spinbox_block = False self.constraint_model = ConstraintListModel(self) # constraint selection self.all_temp_levels = TempLevelModel(self) # temp_level selection pool self.selected_temp_levels = TempLevelModel(self) # selected temp_levels self.controls = AddRemoveValueWidget(self) self.constraint_controls = AddRemoveWidget(self, False) self.controls.add_button.setText(u"") self.controls.remove_button.setText(u"") self.constraint_controls.add_button.setText(u"") self.constraint_controls.remove_button.setText(u"") self.constraint_view = QtGui.QListView(self) self.constraint_view.setModel(self.constraint_model) self.all_temp_levels_view = QtGui.QListView(self) self.all_temp_levels_view.setModel(self.all_temp_levels) self.selected_temp_levels_view = QtGui.QListView(self) self.selected_temp_levels_view.setModel(self.selected_temp_levels) h = QtGui.QHBoxLayout() h.addWidget(self.constraint_view, 1) h.addWidget(self.constraint_controls, 1) h.addWidget(self.all_temp_levels_view, 3) h.addWidget(self.controls, 1) h.addWidget(self.selected_temp_levels_view, 3) self.controls.add_button.setIcon(QtGui.QIcon.fromTheme("go-next")) self.controls.remove_button.setIcon(QtGui.QIcon.fromTheme("go-previous")) self.setLayout(h) self.connect( self.constraint_view, QtCore.SIGNAL("clicked(QModelIndex)"), self._constraint_selected) self.connect( self.controls.add_button, QtCore.SIGNAL("clicked()"), self.add_temp_level_to_constraint) self.connect( self.controls.remove_button, QtCore.SIGNAL("clicked()"), self.remove_temp_level_from_constraint) self.connect( self.controls.up_button, QtCore.SIGNAL("clicked()"), self.temp_level_up) self.connect( self.controls.down_button, QtCore.SIGNAL("clicked()"), self.temp_level_down) self.connect( self.constraint_controls.add_button, QtCore.SIGNAL("clicked()"), self.add_constraint) self.connect( self.constraint_controls.remove_button, QtCore.SIGNAL("clicked()"), self.remove_constraint) self.connect( self.controls.value, QtCore.SIGNAL("valueChanged(int)"), self.constraint_value_changed) self.connect( self.selected_temp_levels_view, QtCore.SIGNAL("clicked(QModelIndex)"), self.selected_temp_levels_clicked) def setData(self, solder): self.solder = solder self.all_temp_levels.setTempLevels(solder.temp_levels) self._set_data(solder) self.set_controls() self.controls.up_button.setEnabled(False) self.controls.down_button.setEnabled(False) def set_controls(self): if not self.constraint_model.constraint_list: self.controls.value.setEnabled(False) self.constraint_controls.remove_button.setEnabled(False) self.controls.add_button.setEnabled(False) self.controls.remove_button.setEnabled(False) self.controls.up_button.setEnabled(False) self.controls.down_button.setEnabled(False) else: self.controls.value.setEnabled(True) self.constraint_controls.remove_button.setEnabled(True) self.controls.add_button.setEnabled(True) self.controls.remove_button.setEnabled(True) self.controls.up_button.setEnabled(True) self.controls.down_button.setEnabled(True) def selected_temp_levels_clicked(self, index): if index.isValid(): self.controls.up_button.setEnabled(True) self.controls.down_button.setEnabled(True) def add_constraint(self): self.constraint_model.append_constraint() self.set_controls() self.solder_changed.emit() def _constraint_selected(self, index): raise NotImplementedError() def _set_data(self, solder): raise NotImplementedError() def add_temp_level_to_constraint(self): src_row = self.all_temp_levels_view.currentIndex().row() temp_level = self.all_temp_levels.temp_levels[src_row] self.selected_temp_levels.temp_levels.append(temp_level) self.selected_temp_levels.reset() self.selected_temp_levels_view.setCurrentIndex(self.selected_temp_levels.index(len(self.selected_temp_levels.temp_levels)-1,0)) self.solder_changed.emit() def remove_temp_level_from_constraint(self): del self.selected_temp_levels.temp_levels[self.selected_temp_levels_view.currentIndex().row()] self.selected_temp_levels.reset() self.selected_temp_levels_view.clearSelection() self.solder_changed.emit() def remove_constraint(self): src_row = self.all_temp_levels_view.currentIndex().row() del self.constraint_model.constraint_list[src_row] self.constraint_model.reset() self.selected_temp_levels.clear() self.set_controls() self.solder_changed.emit() def constraint_value_changed(self, value): if self.spinbox_block: self.spinbox_block = False return src_index = self.constraint_view.currentIndex().row() self.constraint_model.constraint_list[src_index][1] = value self.solder_changed.emit() def slot_temp_level_removed(self, temp_level): deletes = deque() ix = 0 for temp_levels, v in self.constraint_model.constraint_list: if temp_level in temp_levels: deletes.appendleft(ix) ix += 1 for i in deletes: del self.constraint_model.constraint_list[i] self.reset() self.solder_changed.emit() def temp_level_up(self): dst_row = self.selected_temp_levels_view.currentIndex().row() self.selected_temp_levels.temp_levels[dst_row - 1], self.selected_temp_levels.temp_levels[dst_row] = self.selected_temp_levels.temp_levels[dst_row], self.selected_temp_levels.temp_levels[dst_row - 1] self.selected_temp_levels.reset() self.solder_changed.emit() def temp_level_down(self): dst_row = self.selected_temp_levels_view.currentIndex().row() self.selected_temp_levels.temp_levels[dst_row], self.selected_temp_levels.temp_levels[dst_row + 1] = self.selected_temp_levels.temp_levels[dst_row + 1], self.selected_temp_levels.temp_levels[dst_row] self.selected_temp_levels.reset() self.solder_changed.emit() class DurationConstraintWidget(ConstraintWidget): def _set_data(self, solder): self.spinbox_block = True self.constraint_model.constraint_list = solder.durations self.constraint_model.reset() ix = self.constraint_model.index(0, 0) self._constraint_selected(ix) self.constraint_view.setCurrentIndex(ix) def _constraint_selected(self, index): if index.isValid(): self.spinbox_block = True temp_levels, value = self.constraint_model.constraint_list[index.row()] self.selected_temp_levels.setTempLevels(temp_levels) self.controls.value.setValue(value) else: self.spinbox_block = True self.selected_temp_levels.setTempLevels(list()) self.controls.value.setValue(0) class OvenControlsWidget(QtGui.QWidget): def __init__(self, parent=None): super(OvenControlsWidget, self).__init__(parent) self.left = QtGui.QWidget(self) self.temp_lcd = QtGui.QLCDNumber(self) self.time_lcd = QtGui.QLCDNumber(self) self.time_lcd.setDigitCount(3) self.temp_lcd.setDigitCount(3) palette = QtGui.QPalette() palette.setColor(QtGui.QPalette.WindowText, QtCore.Qt.black) self.temp_lcd.setPalette(palette) self.time_lcd.setPalette(palette) self.time_lcd.setSmallDecimalPoint(False) self.temp_lcd.setSmallDecimalPoint(False) self.temp_lcd.setSegmentStyle(2) self.time_lcd.setSegmentStyle(2) self.time_lcd.display("000") self.temp_lcd.display("142") self.temp_label = QtGui.QLabel("Time", self) self.time_label = QtGui.QLabel("Temp", self) self.temp_label.setBuddy(self.temp_lcd) self.time_label.setBuddy(self.time_lcd) self.sicon = QtGui.QIcon.fromTheme("media-playback-start") self.picon = QtGui.QIcon.fromTheme("media-playback-pause") self.start_button = QtGui.QPushButton(self.sicon, "start") self.start_button.setCheckable(True) layout = QtGui.QHBoxLayout(self.left) layout.addWidget(self.time_label) layout.addWidget(self.time_lcd) layout.addWidget(self.temp_label) layout.addWidget(self.temp_lcd) layout.addWidget(self.start_button) layout2 = QtGui.QHBoxLayout() self.temp_level_widget = TempLevelWidget(self, True) layout2.addWidget(self.temp_level_widget, 2) layout2.addWidget(self.left, 4) self.setLayout(layout2) self.connect( self.start_button, QtCore.SIGNAL("clicked()"), self.toggle_button) def toggle_button(self): if self.start_button.isChecked(): self.start_button.setIcon(self.picon) else: self.start_button.setIcon(self.sicon) class RateConstraintWidget(ConstraintWidget): def _set_data(self, solder): self.spinbox_block = True self.constraint_model.constraint_list = solder.rates self.constraint_model.reset() ix = self.constraint_model.index(0, 0) self._constraint_selected(ix) self.constraint_view.setCurrentIndex(ix) def _constraint_selected(self, index): if index.isValid(): self.spinbox_block = True temp_levels, value = self.constraint_model.constraint_list[index.row()] self.selected_temp_levels.setTempLevels(temp_levels) self.controls.value.setValue(value) else: self.spinbox_block = True self.selected_temp_levels.setTempLevels([]) self.controls.value.setValue(0) class SolderWidget(QtGui.QWidget): def __init__(self, parent, readonly=False): super(SolderWidget, self).__init__(parent) self.solder_model = SolderListModel(self) self.solder_view = QtGui.QListView() self.solder_view.setModel(self.solder_model) self.solder_controls = AddRemoveWidget(self, False) layout = QtGui.QHBoxLayout(self) layout.addWidget(self.solder_view, 3) layout.addWidget(self.solder_controls, 1) self.connect( self.solder_controls.add_button, QtCore.SIGNAL("clicked()"), self.solder_model.create_solder) self.solder_view.setCurrentIndex(self.solder_model.index(0,0)) class TempLevelWidget(QtGui.QWidget): temp_level_removed = QtCore.pyqtSignal(TempLevel) solder_changed = QtCore.pyqtSignal() def __init__(self, parent, readonly=False): super(TempLevelWidget, self).__init__(parent) self.readonly = readonly self.temp_level_model = TempLevelModel(self) self.temp_level_view = QtGui.QTableView() self.temp_level_view.setModel(self.temp_level_model) self.temp_level_view.verticalHeader().setVisible(False) self.temp_level_view.resizeColumnsToContents() self.temp_level_view.horizontalHeader().setStretchLastSection(True) self.temp_level_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) h = QtGui.QHBoxLayout() h.addWidget(self.temp_level_view) self.setLayout(h) self.connect( self.temp_level_view, QtCore.SIGNAL("clicked(QModelIndex)"), self.temp_level_selected) if not readonly: self.controls = AddRemoveWidget(self) h.addWidget(self.controls) self.connect( self.controls.add_button, QtCore.SIGNAL("clicked()"), self.add_temp_level) self.connect( self.controls.remove_button, QtCore.SIGNAL("clicked()"), self.remove_temp_level) self.connect( self.controls.up_button, QtCore.SIGNAL("clicked()"), self.temp_level_up) self.connect( self.controls.down_button, QtCore.SIGNAL("clicked()"), self.temp_level_down) self.connect( self.temp_level_model, QtCore.SIGNAL("solder_changed()"), self._solder_changed) def _solder_changed(self): print self._solder_changed self.solder_changed.emit() def setData(self, solder): self.temp_level_model.setTempLevels(solder.temp_levels) self.temp_level_view.resizeColumnsToContents() def add_temp_level(self): index = self.temp_level_view.currentIndex() self.temp_level_model.add_temp_level(index, TempLevel("new " + str(self.temp_level_model.rowCount(None)), 0)) self.temp_level_view.setCurrentIndex(self.temp_level_model.index(index.row() + 1, 0)) self.plotter.solder.changed = True self.solder_changed.emit() def remove_temp_level(self): self.temp_level_removed.emit( self.temp_level_model.remove_temp_level( self.temp_level_view.currentIndex().row())) self.solder_changed.emit() def temp_level_up(self): dst_row = self.temp_level_view.currentIndex().row() self.temp_level_model.temp_levels[dst_row - 1], self.temp_level_model.temp_levels[dst_row] = self.temp_level_model.temp_levels[dst_row], self.temp_level_model.temp_levels[dst_row - 1] self.temp_level_model.reset() self.solder_changed.emit() def temp_level_down(self): dst_row = self.selected_temp_levels_view.currentIndex().row() self.temp_level_model.temp_levels[dst_row], self.temp_level_model.temp_levels[dst_row + 1] = self.temp_level_model.temp_levels[dst_row + 1], self.temp_level_model.temp_levels[dst_row] self.temp_level_model.reset() self.solder_changed.emit() def temp_level_selected(self, index): if index.isValid(): row = index.row() if not self.readonly: is_env = self.temp_level_model.temp_levels[row].is_env #is_end = row == len(self.temp_level_model.temp_levels) - 1 #self.controls.add_button.setEnabled(not is_end) self.controls.remove_button.setEnabled(not is_env) class Report(QtGui.QWidget): def __init__(self, parent=None): super(Report, self).__init__(parent) class ApplicationWindow(QtGui.QMainWindow): def __init__(self): QtGui.QMainWindow.__init__(self) self.dpi = 100 self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.setWindowTitle("application main window") self.file_menu = QtGui.QMenu('&File', self) self.file_menu.addAction('&Create profile header', self.create_header, QtCore.Qt.CTRL + QtCore.Qt.Key_C) self.file_menu.addAction('&Save solder', self.save_solder, QtCore.Qt.CTRL + QtCore.Qt.Key_S) self.file_menu.addAction('&Reload solder', self.reload_solder, QtCore.Qt.CTRL + QtCore.Qt.Key_R) self.file_menu.addAction('&Delete solder', self.remove_solder, QtCore.Qt.CTRL + QtCore.Qt.Key_D) self.file_menu.addAction('S&ave plot', self.save_plot, QtCore.Qt.CTRL + QtCore.Qt.Key_A) self.file_menu.addAction('&Quit', self.fileQuit, QtCore.Qt.CTRL + QtCore.Qt.Key_Q) self.menuBar().addMenu(self.file_menu) self.view_menu = QtGui.QMenu('&View', self) self.profile_view_action = self.view_menu.addAction("&Profile View", self.open_profile_view, QtCore.Qt.CTRL + QtCore.Qt.Key_P) self.controls_view_action = self.view_menu.addAction("&Oven Controls View", self.open_controls_view, QtCore.Qt.CTRL + QtCore.Qt.Key_O) self.view_group = QtGui.QActionGroup(self) self.view_group.setExclusive(True) self.view_group.addAction(self.controls_view_action) self.view_group.addAction(self.profile_view_action) self.profile_view_action.setCheckable(True) self.controls_view_action.setCheckable(True) #self.connect(self.profile_view_action, #QtCore.SIGNAL("triggered()"), #self.open_profile_view) #self.connect(self.controls_view_action, #QtCore.SIGNAL("triggered()"), #self.open_controls_view) self.profile_view_action.setChecked(True) self.menuBar().addMenu(self.view_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.tab_widget = QtGui.QTabWidget(self) self.temp_level_widget = TempLevelWidget(self) self.duration_widget = DurationConstraintWidget(u"Duration (s)") self.rate_widget = RateConstraintWidget(u"Rate (°C/s)") self.tab_widget.addTab(self.temp_level_widget, u"Temperature Levels (°C)") self.tab_widget.addTab(self.duration_widget, u"Duration (s)") self.tab_widget.addTab(self.rate_widget, u"Rate (°C/s)") self.plotter = Plotter(self, self, width=5, height=4, dpi=self.dpi) self.controls_widget = OvenControlsWidget(self) self.controls_widget.setVisible(False) self.connect( self.duration_widget, QtCore.SIGNAL("solder_changed()"), self.plotter.solder_changed) self.connect( self.rate_widget, QtCore.SIGNAL("solder_changed()"), self.plotter.solder_changed) self.connect( self.temp_level_widget, QtCore.SIGNAL("solder_changed()"), self.plotter.solder_changed) self.connect( self.duration_widget, QtCore.SIGNAL("solder_changed()"), self.plotter.solder_changed) self.connect( self.rate_widget, QtCore.SIGNAL("solder_changed()"), self.plotter.solder_changed) self.solder_widget = SolderWidget(self) self.connect( self.solder_widget.solder_view, QtCore.SIGNAL("clicked(QModelIndex)"), self.solder_selected) self.settings_widget = QtGui.QWidget(self) pl = QtGui.QHBoxLayout(self.settings_widget) pl.addWidget(self.solder_widget, 1) pl.addWidget(self.tab_widget, 3) self.splitter = QtGui.QSplitter(QtCore.Qt.Vertical, self) self.plotter_splitter = QtGui.QSplitter(self) self.profile_log = QtGui.QTextEdit(self) self.solder_selected(self.solder_widget.solder_model.index(0,0)) self.plotter_splitter.addWidget(self.plotter) self.plotter_splitter.addWidget(self.profile_log) self.splitter.addWidget(self.settings_widget) self.splitter.addWidget(self.controls_widget) self.splitter.addWidget(self.plotter_splitter) self.splitter.setStretchFactor(0, 2) self.splitter.setStretchFactor(1, 2) self.splitter.setStretchFactor(2, 8) self.setCentralWidget(self.splitter) self.statusBar().showMessage("I'm in reflow heaven", 2000) self.temp_level_widget.temp_level_removed.connect(self.duration_widget.slot_temp_level_removed) self.temp_level_widget.temp_level_removed.connect(self.rate_widget.slot_temp_level_removed) def getPlotter(self): return self.plotter def create_header(self): x_list = list() y_list = list() for solder in self.solder_model.listdata: x, y, xmax, ymax, duration_points, rate_points = solder.calc_profile() x_list.append(x) y_list.append(y) #print "packet", repr(create_profile_packet(x, y)) create_profile_header(x_list, y_list) def solder_selected(self, index): if index.isValid(): solder = self.solder_widget.solder_model.listdata[index.row()] self.temp_level_widget.setData(solder) self.duration_widget.setData(solder) self.rate_widget.setData(solder) self.plotter.setData(solder) self.controls_widget.temp_level_widget.setData(solder) solder.log_message.connect(self.profile_log.setPlainText) def remove_solder(self): index = self.solder_widget.solder_view.currentIndex() solder = self.solder_widget.solder_model.listdata[index.row()] del self.solder_widget.solder_model.listdata[index.row()] self.solder_widget.solder_model.reset() new_index = self.solder_widget.solder_model.index(0) self.solder_widget.solder_view.setCurrentIndex(new_index) self.solder_selected(new_index) os.remove(solder.filename) def open_controls_view(self): self.controls_widget.show() self.settings_widget.hide() def open_profile_view(self): self.controls_widget.hide() self.settings_widget.show() def save_plot(self): file_choices = "PNG (*.png)|*.png" filename = QtGui.QFileDialog.getSaveFileName(self, 'Save File', 'qtplot.png') self.plotter.print_figure(str(filename), dpi=self.dpi) def save_solder(self): self.plotter.solder.save() self.solder_widget.solder_model.reload() def reload_solder(self): old_index = self.solder_widget.solder_view.currentIndex() solder = Solder.unpack(self.plotter.solder.filename, self.solder_widget.solder_model) self.solder_widget.solder_model.listdata[old_index.row()] = solder self.solder_widget.solder_model.reset() new_index = self.solder_widget.solder_model.index(old_index.row(), 0) self.solder_widget.solder_view.setCurrentIndex(new_index) self.solder_selected(new_index) 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(): # numpy bug workaround try: numpy.log10(0.0) except: pass qApp = QtGui.QApplication(sys.argv) aw = ApplicationWindow() aw.setWindowTitle("%s" % progname) aw.show() sys.exit(qApp.exec_()) if __name__ == '__main__': main()