#!/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 ConstraintWidget 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]) self.edge_picked.emit(event.ind) else: print type(avent.artist) def set_picked(self, ix): if isinstance(ix, QtCore.QModelIndex): ix = ix.row() self.selected_ix = ix self.updated = True 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) 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.solder.setChanged() self.updated = True if not self.started: self.update_figure() def setData(self, solder): self.solder = solder self.updated = True 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 = ConstraintWidget(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.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.plotter, QtCore.SIGNAL("edge_picked(int)"), self.constraint_widget.edge_picked) self.connect( self.plotter, QtCore.SIGNAL("edge_picked(int)"), self.edge_picked) 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) if not self.plotter.started: self.plotter.update_figure() def edge_picked(self, ix): if self.tab_widget.currentIndex() != 1: self.tab_widget.setCurrentIndex(1) if not self.plotter.started: self.plotter.update_figure() 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"%(prog)s version %(version)s
Copyright \N{COPYRIGHT SIGN} 2012 Stefan Kögl

reflowctl frontend

Special thanks to Chaostreff Dortmund" % {"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()