reflow/reflowctl/reflowctl_gui.py

474 lines
16 KiB
Python
Executable File

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys, os, struct
import numpy
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
from mpltools import annotation
from solder import Solder, SolderWidget
from temp_level import TempLevelWidget, TempLevel
from edge_widget import EdgeWidget
progname = os.path.basename(sys.argv[0])
progversion = "0.1"
def create_profile_packet(x, y):
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
class Plotter(FigureCanvas):
"""A canvas that updates itself every second with a new plot."""
edge_picked = QtCore.pyqtSignal(int)
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.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.selx = list()
self.sely = list()
self.selected_ix = 0
self.setParent(parent)
self.myapp = myapp
self.solder = None
self.plot_data, = self.axes.plot([], linewidth=2.0, color=(0, 0, 1), zorder=10, picker=5)
self.selection_data, = self.axes.plot([], linewidth=5.0, color=(1,0,0), zorder=5)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.updated = False
self.fig.canvas.mpl_connect('pick_event', self.onpick)
self.update_figure()
self.started = False
def start_timer(self):
timer = QtCore.QTimer(self)
QtCore.QObject.connect(timer, QtCore.SIGNAL("timeout()"), self.update_figure)
timer.start(1000)
def onpick(self, event):
if isinstance(event.artist, Line2D):
self.set_picked(event.ind[0])
else:
raise Exception("fixme: pickme")
def set_picked(self, ix):
if isinstance(ix, QtCore.QModelIndex):
ix = ix.row()
self.edge_picked.emit(ix)
self.selected_ix = ix
self.updated = True
if not self.started:
self.update_figure()
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.solder.calc_profile()
except ValueError:
(self.x, self.y, self.xmin, self.xmax, self.ymin, self.ymax) = ([], [], 0., 300., 0., 300.)
self.plot_data.set_xdata(self.x)
self.plot_data.set_ydata(self.y)
if self.selected_ix is not None:
self.selx = numpy.array(map(float, self.x[self.selected_ix:self.selected_ix + 2]))
self.sely = numpy.array(map(float, self.y[self.selected_ix:self.selected_ix + 2]))
self.selection_data.set_xdata(self.selx)
self.selection_data.set_ydata(self.sely)
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)
labels = self.axes.get_xticklabels()
for label in labels:
label.set_rotation(30)
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.selected_ix = None
self.solder.setChanged()
self.updated = True
if not self.started:
self.update_figure()
def setData(self, solder):
self.solder = solder
self.updated = True
self.selx = numpy.array(list())
self.sely = numpy.array(list())
if not self.started:
self.update_figure()
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('&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('&Show Report', 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.constraint_widget = EdgeWidget(u"Constraints")
self.tab_widget.addTab(self.temp_level_widget, u"Temperature Levels (°C)")
self.tab_widget.addTab(self.constraint_widget, u"Constraints")
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.temp_level_widget,
QtCore.SIGNAL("solder_changed()"),
self.plotter.solder_changed)
self.temp_level_widget.temp_level_added.connect(self.constraint_widget.temp_level_added)
self.temp_level_widget.temp_level_removed.connect(self.constraint_widget.temp_level_removed)
self.connect(
self.constraint_widget,
QtCore.SIGNAL("edge_changed()"),
self.plotter.solder_changed)
self.connect(
self.constraint_widget.edge_view,
QtCore.SIGNAL("clicked(QModelIndex)"),
self.plotter.set_picked)
self.solder_widget = SolderWidget(self)
self.connect(
self.solder_widget.solder_view,
QtCore.SIGNAL("clicked(QModelIndex)"),
self.solder_selected)
self.connect(
self.solder_widget,
QtCore.SIGNAL("solder_selected(QModelIndex)"),
self.solder_selected)
self.connect(
self.plotter,
QtCore.SIGNAL("edge_picked(int)"),
self.constraint_widget.edge_picked)
self.connect(
self.plotter,
QtCore.SIGNAL("edge_picked(int)"),
self.edge_picked)
self.connect(
self.temp_level_widget.temp_level_model,
QtCore.SIGNAL("status_message(QString, int)"),
self.statusBar().showMessage)
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.plotter_splitter.setStretchFactor(0, 8)
self.plotter_splitter.setStretchFactor(1, 2)
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.plotter.update_figure()
def getPlotter(self):
return self.plotter
def solder_selected(self, index):
if index.isValid():
solder = self.solder_widget.solder_model.solder_list[index.row()]
self.temp_level_widget.setData(solder)
self.constraint_widget.setData(solder)
self.plotter.setData(solder)
solder.log_message.connect(self.profile_log.setPlainText)
def edge_picked(self, ix):
if self.tab_widget.currentIndex() != 1:
self.tab_widget.setCurrentIndex(1)
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):
pass
#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.solder_list.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.solder_list.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.solder_list[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():
qApp = QtGui.QApplication(sys.argv)
aw = ApplicationWindow()
aw.setWindowTitle("%s" % progname)
aw.show()
sys.exit(qApp.exec_())
if __name__ == '__main__':
main()