reflow/reflowctl/reflowctl_gui.py

1374 lines
47 KiB
Python
Executable File

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys, os, random, copy, struct
from datetime import datetime
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 get_valid_neigbors(temp_level, temp_levels):
ix = temp_levels.index(temp_level)
return temp_levels[ix-1], temp_levels[ix+1]
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):
super(TempLevel, self).__init__()
self.name = name
self.temp = temp
self.is_env = is_env
self.color = None
def __str__(self):
return "<TempLevel: name=%r, temp=%r>" % (self.name, self.temp)
def __lt__(self, other):
return self.temp < other.temp
def __le__(self, other):
return self.temp <= other.temp
def __eq__(self, other):
return self.temp == other.temp
def is_between(self, tl1, tl2):
tmp = [tl1, tl2, self]
tmp.sort()
return self == tmp[1]
__repr__ = __str__
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 TempLevelWidget(QtGui.QWidget):
temp_level_removed = QtCore.pyqtSignal(TempLevel)
temp_levels_changed = QtCore.pyqtSignal()
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()
self.temp_level_view.setCurrentIndex(self.temp_level_model.index(0, 0))
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.temp_levels_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 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, ix):
x = list()
y = list()
temp_levels = None
value = None
dx = 0
for temp_levels, value in self.durations:
tl_len = len(temp_levels)
print "check dur", dx, temp_level, repr(temp_levels), self.ud
if temp_levels and temp_levels[0] == temp_level and tl_len > 1 and dx not in self.ud:
if temp_level not in used:
used.add(temp_level)
x.append(self.time)
y.append(temp_level.temp)
self.ud.add(dx)
print "using duration constraint", dx
if tl_len == 2:
ix = self.temp_levels.index(temp_levels[1])
y.append(temp_levels[1].temp)
used.add(temp_levels[1])
self.time += value
x.append(self.time)
elif tl_len >= 3:
ix = self.temp_levels.index(temp_levels[-1])
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, ix
dx += 1
raise Exception()
def check_rate_constraints(self, temp_level, used, ix):
x = list()
y = list()
rx = 0
for temp_levels, value in self.rates:
tl_len = len(temp_levels)
print "check rate", rx, temp_level, repr(temp_levels), self.ur
if temp_levels and tl_len > 1 and rx not in self.ur and (temp_levels[0] == temp_level or temp_level.is_between(*temp_levels)):
if temp_level < temp_levels[1] and value < 0:
self.log.append("!RateError: negative rate for increasing temp levels is wrong")
elif temp_level > temp_levels[1] and value > 0:
self.log.append("!RateError: positive rate for decreasing temp levels is wrong")
if temp_level not in used:
used.add(temp_level)
x.append(self.time)
y.append(temp_level.temp)
self.ur.add(rx)
print "using rate constraint", rx
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)
ix = self.temp_levels.index(temp_levels[-1])
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, ix
rx += 1
raise Exception()
def calc_profile(self):
print self.calc_profile
self.log = list()
x = list()
y = list()
duration_points = dict()
rate_points = dict()
self.time = 0
used = set()
unused = list()
ix = 0
last = len(self.temp_levels)
self.ud = set()
self.ur = set()
count = len(self.durations) + len(self.rates)
print "max rounds", count
while 1:
print "while", ix, last, x, y, self.ud, self.ur
temp_level = self.temp_levels[ix]
count -= 1
try:
dur_x, dur_y, dur_new_ix = self.check_duration_constraints(temp_level, used, ix)
ix = dur_new_ix
print "dur_new_ix", dur_new_ix
x.extend(dur_x)
y.extend(dur_y)
except Exception, e:
print e
try:
rate_x, rate_y, rate_new_ix = self.check_rate_constraints(temp_level, used, ix)
ix = rate_new_ix
print "rate_new_ix", rate_new_ix
x.extend(rate_x)
y.extend(rate_y)
except Exception, e:
print e
ix+=1
#if temp_level not in used:
#unused.append(temp_level)
if (y and y[-1] == y[0]) or count <= 0:
break
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 = SolderListModel.dirname()
root = etree.Element("xml")
root.append(solder_node)
if self.filename is None:
self.filename = os.path.join(dirname, self.name + ".xml")
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 = self.dirname()
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(None, datetime.now().strftime("%Y-%M-%D %H:%m:%s"), "")
tl = solder.add_temp_level("environment temp", getTemperature(), True)
tl.color = QtGui.QColor(0, 0, 0)
self.listdata.append(solder)
self.reset()
@staticmethod
def dirname():
return os.path.join(os.path.dirname(__file__), "solder_types")
def check_name(self, name):
for solder in self.listdata:
if name == solder.name:
return False
return True
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.connect(
self.solder_controls.remove_button,
QtCore.SIGNAL("clicked()"),
self.remove_solder)
self.solder_view.setCurrentIndex(self.solder_model.index(0,0))
def remove_solder(self):
index = self.solder_view.currentIndex()
solder = self.solder_model.listdata[index.row()]
try:
os.remove(solder.filename)
except OSError, e:
print e
pass
del self.solder_model.listdata[index.row()]
self.solder_model.reset()
new_index = self.solder_model.index(0)
self.solder_view.setCurrentIndex(new_index)
return new_index
def save_solder(self, index):
self.solder_model.listdata[index.row()].save()
self.solder_model.reset()
new_index = self.solder_model.index(self.solder_model.listdata.index(self.plotter.solder))
self.solder_widget.solder_view.setCurrentIndex(new_index)
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):
if self.updated:
self.fig.lines = lines = list()
self.updated = False
self.axes.patches = list()
self.axes.texts = list()
self.x = list()
self.y = list()
try:
self.x, self.y, self.xmin, self.xmax, self.ymin, self.ymax, self.used, self.unused = self.solder.calc_profile()
except ValueError:
self.x, self.y, self.xmin, self.xmax, self.ymin, self.ymax, self.used, self.unused = [], [], 0., 300., 0., 300., [], self.solder.temp_levels
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_xlim(self.xmin, self.xmax + 20, auto=True)
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):
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.temp_levels_model = TempLevelModel(self) # temp_level selection pool
self.selected_temp_levels_model = 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.temp_levels_view = QtGui.QListView(self)
self.temp_levels_view.setModel(self.temp_levels_model)
self.selected_temp_levels_view = QtGui.QListView(self)
self.selected_temp_levels_view.setModel(self.selected_temp_levels_model)
h = QtGui.QHBoxLayout()
h.addWidget(self.constraint_view, 1)
h.addWidget(self.constraint_controls, 1)
h.addWidget(self.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.temp_levels_model.setTempLevels(solder.temp_levels)
self._set_data(solder)
self.set_controls()
self.temp_levels_view.setCurrentIndex(self.temp_levels_model.index(0,0))
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()
new_index = self.constraint_model.rowCount() - 1
self.constraint_view.setCurrentIndex(self.constraint_model.index(new_index, 0))
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.temp_levels_view.currentIndex().row()
temp_level = self.temp_levels_model.temp_levels[src_row]
self.selected_temp_levels_model.temp_levels.append(temp_level)
self.selected_temp_levels_model.reset()
self.selected_temp_levels_view.setCurrentIndex(self.selected_temp_levels_model.index(len(self.selected_temp_levels_model.temp_levels)-1,0))
self.solder_changed.emit()
def remove_temp_level_from_constraint(self):
del self.selected_temp_levels_model.temp_levels[self.selected_temp_levels_view.currentIndex().row()]
self.selected_temp_levels_model.reset()
self.selected_temp_levels_view.clearSelection()
self.solder_changed.emit()
def remove_constraint(self):
src_row = self.temp_levels_view.currentIndex().row()
del self.constraint_model.constraint_list[src_row]
self.constraint_model.reset()
self.selected_temp_levels_model.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 temp_levels_changed(self):
print self.temp_levels_changed
self.temp_levels_model.reset()
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_model.temp_levels[dst_row - 1], self.selected_temp_levels_model.temp_levels[dst_row] = self.selected_temp_levels_model.temp_levels[dst_row], self.selected_temp_levels_model.temp_levels[dst_row - 1]
self.selected_temp_levels_model.reset()
self.solder_changed.emit()
def temp_level_down(self):
dst_row = self.selected_temp_levels_view.currentIndex().row()
self.selected_temp_levels_model.temp_levels[dst_row], self.selected_temp_levels_model.temp_levels[dst_row + 1] = self.selected_temp_levels_model.temp_levels[dst_row + 1], self.selected_temp_levels_model.temp_levels[dst_row]
self.selected_temp_levels_model.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_model.setTempLevels(temp_levels)
self.controls.value.setValue(value)
else:
self.spinbox_block = True
self.selected_temp_levels_model.setTempLevels(list())
self.controls.value.setValue(0)
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_model.setTempLevels(temp_levels)
self.controls.value.setValue(value)
else:
self.spinbox_block = True
self.selected_temp_levels_model.setTempLevels([])
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 Report(QtGui.QWidget):
def __init__(self, parent=None):
super(Report, self).__init__(parent)
self.operator = QtGui.QLineEdit(self)
self.date_begin = QtGui.QDateTimeEdit(self)
self.date_end = QtGui.QDateTimeEdit(self)
self.durations = list()
self.plot_file = ""
layout = QtGui.QGridLayout(self)
layout.addWidget(QtGui.QLabel("operator"), 0, 0)
layout.addWidget(QtGui.QLabel("date begin"), 1, 0)
layout.addWidget(QtGui.QLabel("date end"), 2, 0)
layout.addWidget(self.operator, 0, 1)
layout.addWidget(self.date_begin, 1, 1)
layout.addWidget(self.date_end, 2, 1)
def export_pdf(self):
pass
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.show_report,
QtCore.Qt.CTRL + QtCore.Qt.Key_T)
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.report = Report(self)
self.report.hide()
#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("Reflow GORE!1!", 10000)
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)
self.temp_level_widget.temp_levels_changed.connect(self.duration_widget.temp_levels_changed)
self.temp_level_widget.temp_levels_changed.connect(self.rate_widget.temp_levels_changed)
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):
self.solder_selected(self.solder_widget.remove_solder())
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 show_report(self):
self.report.exec()
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.listdata.sort(key=lambda x: x.name)
self.solder_widget.solder_model.reset()
new_index = self.solder_widget.solder_model.index(self.solder_widget.solder_model.listdata.index(self.plotter.solder))
self.solder_widget.solder_view.setCurrentIndex(new_index)
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,
QtCore.QString(u"<html>%(prog)s version %(version)s<br/>Copyright \N{COPYRIGHT SIGN} 2012 Stefan Kögl<br/><br/>reflowctl frontend<br/><br/>Special thanks to <a href='http://www.chaostreff-dortmund.de/'>Chaostreff Dortmund</a></html>" % {"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()
tls = [TempLevel('peak', 260), TempLevel('environment temp', 20.0)]
a = TempLevel('tal', 200)
print a.is_between(*tls)