From 6d8e7580c0729aca413c057298e09c5e39e98ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Wed, 21 Nov 2012 20:39:22 +0100 Subject: [PATCH] save, bugs fixed, slope annotations and more --- reflowctl/reflowctl_gui.py | 268 ++++++++++++++------ reflowctl/solder_types/lead_noclean.xml | 4 +- reflowctl/solder_types/leadfree_noclean.xml | 4 +- reflowctl/solder_types/test kotze.xml | 2 + 4 files changed, 198 insertions(+), 80 deletions(-) create mode 100644 reflowctl/solder_types/test kotze.xml diff --git a/reflowctl/reflowctl_gui.py b/reflowctl/reflowctl_gui.py index 307749e..5f83d39 100755 --- a/reflowctl/reflowctl_gui.py +++ b/reflowctl/reflowctl_gui.py @@ -18,7 +18,7 @@ from matplotlib.lines import Line2D from matplotlib.path import Path import matplotlib.patches as patches -#from mpltools import annotation- +from mpltools import annotation progname = os.path.basename(sys.argv[0]) progversion = "0.1" @@ -235,6 +235,29 @@ class Solder(object): return s + def save(self, filename): + 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(os.path.join(dirname, self.name + ".xml"), "UTF-8", True) + self.changed = False + class SolderListModel(QtCore.QAbstractListModel): @@ -243,20 +266,19 @@ class SolderListModel(QtCore.QAbstractListModel): """ super(SolderListModel, self).__init__(parent, *args) - 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: - #try: - self.listdata.append(Solder.unpack(os.path.join(dirname, p))) - #except Exception, e: - #print e - #pass - + 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.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: @@ -265,10 +287,28 @@ class SolderListModel(QtCore.QAbstractListModel): def data(self, index, role): if index.isValid() and role == QtCore.Qt.DisplayRole: - return QtCore.QVariant(self.listdata[index.row()].name) + solder = self.listdata[index.row()] + if solder.changed: + return QtCore.QVariant(solder.name + " *") + else: + return QtCore.QVariant(solder.name) else: return QtCore.QVariant() + 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", "") @@ -279,6 +319,8 @@ class SolderListModel(QtCore.QAbstractListModel): class TempLevelModel(QtCore.QAbstractTableModel): + solder_changed = QtCore.pyqtSignal() + def __init__(self, parent): super(TempLevelModel, self).__init__(parent) self._changed = False @@ -327,7 +369,7 @@ class TempLevelModel(QtCore.QAbstractTableModel): self.temp_levels[index.row()].name = str(variant.toString()) elif col == 1: self.temp_levels[index.row()].temp = variant.toInt()[0] - self._changed = True + self.solder_changed.emit() return True return False @@ -335,14 +377,14 @@ class TempLevelModel(QtCore.QAbstractTableModel): tmp = self.temp_levels[index] del self.temp_levels[index] self.reset() - self._changed = True + 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._changed = True + self.solder_changed.emit() def setTempLevels(self, temp_levels): assert isinstance(temp_levels, list) @@ -357,12 +399,13 @@ class TempLevelModel(QtCore.QAbstractTableModel): 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.axes.set_axis_bgcolor('black') + 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) @@ -383,7 +426,6 @@ class Plotter(FigureCanvas): timer = QtCore.QTimer(self) - self.counter = list() QtCore.QObject.connect(timer, QtCore.SIGNAL("timeout()"), self.update_figure) timer.start(1000) self.updated = True @@ -391,10 +433,22 @@ class Plotter(FigureCanvas): def update_figure(self): if self.updated: updated = False - self.x, self.y, self.xmax, self.ymax, self.duration_points, self.rate_points = self.solder.calc_profile() + self.axes.patches = list() + self.axes.texts = list() + self.x = list() + self.y = list() - #for states, value in self.durations.iteritems(): - #annotation.slope_marker((states[0]) + try: + self.x, self.y, self.xmax, self.ymax, self.duration_points, self.rate_points = self.solder.calc_profile() + + for ix, (a, b) in enumerate(zip(self.x[:-1], self.y[:-1])): + annotation.slope_marker((a + 10, b), (self.y[ix+1] - b) / (self.x[ix+1] - a), ax=self.axes) + except Exception, e: + self.xmax = 500 + self.ymax = 300 + + self.plot_data.set_xdata(self.x) + self.plot_data.set_ydata(self.y) self.axes.set_xbound(lower=0, upper=self.xmax + 20) self.axes.set_ybound(lower=0, upper=self.ymax + 20) @@ -402,11 +456,7 @@ class Plotter(FigureCanvas): self.axes.set_yticks([state.temp for state in self.solder.temp_levels]) self.axes.set_xticks(self.x) - self.plot_data.set_xdata(self.x) - self.plot_data.set_ydata(self.y) - self.plot_data.set_zorder(20) - - duration_widget = self.myapp.duration_widget + #duration_widget = self.myapp.duration_widget #self.selection_data.set_xdata(array(da)) #self.selection_data.set_ydata(array(db)) @@ -421,6 +471,9 @@ class Plotter(FigureCanvas): self.axes.legend(("Estimated profile",)) self.draw() + def solder_changed(self): + self.solder.changed = True + def setData(self, solder): self.solder = solder self.updated = True @@ -430,8 +483,8 @@ 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("Add") - self.remove_button = QtGui.QPushButton("Remove") + 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) @@ -442,8 +495,8 @@ class AddRemoveWidget(QtGui.QWidget): self._layout.addWidget(self.add_button) self._layout.addWidget(self.remove_button) if with_upown: - self.up_button = QtGui.QPushButton("Up") - self.down_button = QtGui.QPushButton("Down") + 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) @@ -490,6 +543,7 @@ class ConstraintListModel(QtCore.QAbstractListModel): class ConstraintWidget(QtGui.QWidget): + solder_changed = QtCore.pyqtSignal() def __init__(self, name): super(ConstraintWidget, self).__init__() self.name = name @@ -503,11 +557,11 @@ class ConstraintWidget(QtGui.QWidget): self.controls = AddRemoveValueWidget(self) self.constraint_controls = AddRemoveWidget(self, False) - self.controls.add_button.setText(u"Add TempLevel") - self.controls.remove_button.setText(u"Remove TempLevel") + self.controls.add_button.setText(u"") + self.controls.remove_button.setText(u"") - self.constraint_controls.add_button.setText(u"Add Constraint") - self.constraint_controls.remove_button.setText(u"Remove Constraint") + 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) @@ -524,6 +578,9 @@ class ConstraintWidget(QtGui.QWidget): 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( @@ -566,12 +623,20 @@ class ConstraintWidget(QtGui.QWidget): 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) @@ -589,9 +654,16 @@ class ConstraintWidget(QtGui.QWidget): 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() @@ -601,18 +673,17 @@ class ConstraintWidget(QtGui.QWidget): def add_temp_level_to_constraint(self): src_row = self.all_temp_levels_view.currentIndex().row() - dst_row = self.selected_temp_levels_view.currentIndex().row() temp_level = self.all_temp_levels.temp_levels[src_row] - self.selected_temp_levels.temp_levels.insert(dst_row, temp_level) - #tls.append(temp_level) - #tls.sort(key=attrgetter("temp")) + self.selected_temp_levels.temp_levels.append(temp_level) self.selected_temp_levels.reset() - self.selected_temp_levels_view.clearSelection() + 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() @@ -620,6 +691,7 @@ class ConstraintWidget(QtGui.QWidget): 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: @@ -627,9 +699,9 @@ class ConstraintWidget(QtGui.QWidget): 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: @@ -639,16 +711,19 @@ class ConstraintWidget(QtGui.QWidget): 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): @@ -720,9 +795,6 @@ class OvenControlsWidget(QtGui.QWidget): self.setLayout(layout2) - - - self.connect( self.start_button, QtCore.SIGNAL("clicked()"), @@ -757,10 +829,30 @@ class RateConstraintWidget(ConstraintWidget): 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) @@ -806,6 +898,14 @@ class TempLevelWidget(QtGui.QWidget): QtCore.SIGNAL("clicked()"), self.temp_level_down) + self.connect( + self.temp_level_model, + QtCore.SIGNAL("solder_changed()"), + self._solder_changed) + + def _solder_changed(self): + self.solder_changed.emit() + def setData(self, solder): self.temp_level_model.setTempLevels(solder.temp_levels) @@ -817,34 +917,36 @@ class TempLevelWidget(QtGui.QWidget): 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.plotter.solder.changed = True + 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.plotter.solder.changed = True + 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.plotter.solder.changed = True + self.solder_changed.emit() def temp_level_selected(self, index): if index.isValid(): row = index.row() - 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) + 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 ApplicationWindow(QtGui.QMainWindow): def __init__(self): @@ -857,16 +959,20 @@ class ApplicationWindow(QtGui.QMainWindow): 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 plot', self.save_plot, + self.file_menu.addAction('&Save solder', self.save_solder, QtCore.Qt.CTRL + QtCore.Qt.Key_S) + 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.controls_view_action = self.view_menu.addAction("&Oven Controls View") + 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) @@ -878,13 +984,13 @@ class ApplicationWindow(QtGui.QMainWindow): 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.profile_view_action, + #QtCore.SIGNAL("triggered()"), + #self.open_profile_view) - self.connect(self.controls_view_action, - QtCore.SIGNAL("triggered()"), - self.open_controls_view) + #self.connect(self.controls_view_action, + #QtCore.SIGNAL("triggered()"), + #self.open_controls_view) self.profile_view_action.setChecked(True) @@ -896,11 +1002,7 @@ class ApplicationWindow(QtGui.QMainWindow): self.help_menu.addAction('&About', self.about) - self.solder_model = SolderListModel(self) - self.solder_view = QtGui.QListView() - self.solder_view.setModel(self.solder_model) - self.solder_controls = AddRemoveWidget(self, False) self.tab_widget = QtGui.QTabWidget(self) self.temp_level_widget = TempLevelWidget(self) @@ -914,27 +1016,39 @@ class ApplicationWindow(QtGui.QMainWindow): self.controls_widget = OvenControlsWidget(self) self.controls_widget.setVisible(False) + + self.connect( - self.solder_view, + 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.solder_widget = SolderWidget(self) + + self.connect( + self.solder_widget.solder_view, QtCore.SIGNAL("clicked(QModelIndex)"), self.solder_selected) - self.connect( - self.solder_controls.add_button, - QtCore.SIGNAL("clicked()"), - self.solder_model.create_solder) self.settings_widget = QtGui.QWidget(self) pl = QtGui.QHBoxLayout(self.settings_widget) - pl.addWidget(self.solder_view, 1) - pl.addWidget(self.solder_controls, 1) - pl.addWidget(self.tab_widget, 6) - + pl.addWidget(self.solder_widget, 1) + pl.addWidget(self.tab_widget, 3) self.splitter = QtGui.QSplitter(QtCore.Qt.Vertical, self) - self.solder_view.setCurrentIndex(self.solder_model.index(0,0)) - self.solder_selected(self.solder_model.index(0,0)) + self.solder_selected(self.solder_widget.solder_model.index(0,0)) self.splitter.addWidget(self.settings_widget) self.splitter.addWidget(self.controls_widget) @@ -943,8 +1057,6 @@ class ApplicationWindow(QtGui.QMainWindow): self.splitter.setStretchFactor(1, 2) self.splitter.setStretchFactor(2, 8) - self.solder_controls.hide() - self.setCentralWidget(self.splitter) self.statusBar().showMessage("I'm in reflow heaven", 2000) @@ -968,7 +1080,7 @@ class ApplicationWindow(QtGui.QMainWindow): def solder_selected(self, index): if index.isValid(): - solder = self.solder_model.listdata[index.row()] + 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) @@ -991,6 +1103,10 @@ class ApplicationWindow(QtGui.QMainWindow): 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("foo") + self.solder_widget.solder_model.reload() + def fileQuit(self): self.close() diff --git a/reflowctl/solder_types/lead_noclean.xml b/reflowctl/solder_types/lead_noclean.xml index 1405853..d0bc8ec 100644 --- a/reflowctl/solder_types/lead_noclean.xml +++ b/reflowctl/solder_types/lead_noclean.xml @@ -2,8 +2,8 @@ - - + + diff --git a/reflowctl/solder_types/leadfree_noclean.xml b/reflowctl/solder_types/leadfree_noclean.xml index 8bd4a22..ae5ee13 100644 --- a/reflowctl/solder_types/leadfree_noclean.xml +++ b/reflowctl/solder_types/leadfree_noclean.xml @@ -1,7 +1,7 @@ - + @@ -9,7 +9,7 @@ - + diff --git a/reflowctl/solder_types/test kotze.xml b/reflowctl/solder_types/test kotze.xml new file mode 100644 index 0000000..d474aad --- /dev/null +++ b/reflowctl/solder_types/test kotze.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file