diff --git a/.gitignore b/.gitignore index 0e398a2..70cbc7c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ brent_test tasmet tasmet_automoc.dir tasmet_solvemodel +tasmet_autogen diff --git a/CMakeLists.txt b/CMakeLists.txt index 64ac6f8..ccadf03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # CMakeList.txt for TaSMET -cmake_minimum_required (VERSION 3.6) +cmake_minimum_required (VERSION 2.8.5) project(TaSMET) set(PACKAGE_VERSION 0.1) message("Running Cmake for TaSMET version ${PACKAGE_VERSION}") diff --git a/Doxyfile b/Doxyfile index dad1477..b93776d 100644 --- a/Doxyfile +++ b/Doxyfile @@ -51,14 +51,14 @@ PROJECT_BRIEF = "Thermoacoustic System Modeling Environment" # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = /home/anne/wip/tasmet/src/gui/images/tasmet_small.png +PROJECT_LOGO = src/gui/images/tasmet_small.png # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = /home/anne/wip/tasmet/doc +OUTPUT_DIRECTORY = doc # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and diff --git a/doc/fig/tasys_artist.eps b/doc/fig/tasys_artist.eps old mode 100755 new mode 100644 diff --git a/doc/fig/tasys_artist.png b/doc/fig/tasys_artist.png old mode 100755 new mode 100644 diff --git a/gui/__init__.py b/gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gui/about_dialog.py b/gui/about_dialog.py new file mode 100644 index 0000000..10a541b --- /dev/null +++ b/gui/about_dialog.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'about_dialog.ui' +# +# Created by: PyQt4 UI code generator 4.12 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_about_dialog(object): + def setupUi(self, about_dialog): + about_dialog.setObjectName(_fromUtf8("about_dialog")) + about_dialog.resize(318, 582) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(about_dialog.sizePolicy().hasHeightForWidth()) + about_dialog.setSizePolicy(sizePolicy) + self.verticalLayout = QtGui.QVBoxLayout(about_dialog) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.label = QtGui.QLabel(about_dialog) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans Mono")) + font.setPointSize(14) + font.setBold(True) + font.setWeight(75) + self.label.setFont(font) + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setObjectName(_fromUtf8("label")) + self.verticalLayout.addWidget(self.label) + self.label_4 = QtGui.QLabel(about_dialog) + self.label_4.setAlignment(QtCore.Qt.AlignCenter) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.verticalLayout.addWidget(self.label_4) + self.label_5 = QtGui.QLabel(about_dialog) + self.label_5.setAlignment(QtCore.Qt.AlignCenter) + self.label_5.setObjectName(_fromUtf8("label_5")) + self.verticalLayout.addWidget(self.label_5) + self.label_2 = QtGui.QLabel(about_dialog) + self.label_2.setMinimumSize(QtCore.QSize(300, 433)) + self.label_2.setMaximumSize(QtCore.QSize(300, 433)) + self.label_2.setStyleSheet(_fromUtf8("background-color: rgb(255, 255, 255)")) + self.label_2.setFrameShape(QtGui.QFrame.Box) + self.label_2.setText(_fromUtf8("")) + self.label_2.setPixmap(QtGui.QPixmap(_fromUtf8("images/tasmet.png"))) + self.label_2.setScaledContents(True) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.verticalLayout.addWidget(self.label_2) + self.label_3 = QtGui.QLabel(about_dialog) + self.label_3.setAlignment(QtCore.Qt.AlignCenter) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.verticalLayout.addWidget(self.label_3) + self.pushButton = QtGui.QPushButton(about_dialog) + self.pushButton.setObjectName(_fromUtf8("pushButton")) + self.verticalLayout.addWidget(self.pushButton) + + self.retranslateUi(about_dialog) + QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL(_fromUtf8("clicked()")), about_dialog.accept) + QtCore.QMetaObject.connectSlotsByName(about_dialog) + + def retranslateUi(self, about_dialog): + about_dialog.setWindowTitle(_translate("about_dialog", "About TaSMET", None)) + self.label.setText(_translate("about_dialog", "TaSMET", None)) + self.label_4.setText(_translate("about_dialog", "Thermoacoustic System", None)) + self.label_5.setText(_translate("about_dialog", "Modeling Environment Twente", None)) + self.label_3.setText(_translate("about_dialog", "Copyright (c) 2017 Anne de Jong", None)) + self.pushButton.setText(_translate("about_dialog", "OK", None)) + diff --git a/gui/about_dialog.ui b/gui/about_dialog.ui new file mode 100644 index 0000000..7899116 --- /dev/null +++ b/gui/about_dialog.ui @@ -0,0 +1,130 @@ + + + about_dialog + + + + 0 + 0 + 318 + 582 + + + + + 0 + 0 + + + + About TaSMET + + + + + + + DejaVu Sans Mono + 14 + 75 + true + + + + TaSMET + + + Qt::AlignCenter + + + + + + + Thermoacoustic System + + + Qt::AlignCenter + + + + + + + Modeling Environment Twente + + + Qt::AlignCenter + + + + + + + + 300 + 433 + + + + + 300 + 433 + + + + background-color: rgb(255, 255, 255) + + + QFrame::Box + + + + + + images/tasmet.png + + + true + + + + + + + Copyright (c) 2017 Anne de Jong + + + Qt::AlignCenter + + + + + + + OK + + + + + + + + + pushButton + clicked() + about_dialog + accept() + + + 158 + 562 + + + 158 + 290 + + + + + diff --git a/images/document-new.png b/images/document-new.png new file mode 100644 index 0000000..61db97a Binary files /dev/null and b/images/document-new.png differ diff --git a/images/document-open.png b/images/document-open.png new file mode 100644 index 0000000..3432ed2 Binary files /dev/null and b/images/document-open.png differ diff --git a/images/document-save-as.png b/images/document-save-as.png new file mode 100644 index 0000000..ed2453d Binary files /dev/null and b/images/document-save-as.png differ diff --git a/images/document-save.png b/images/document-save.png new file mode 100644 index 0000000..cc380a0 Binary files /dev/null and b/images/document-save.png differ diff --git a/images/tasmet.png b/images/tasmet.png new file mode 100644 index 0000000..a4b2f1d Binary files /dev/null and b/images/tasmet.png differ diff --git a/images/tasmet_small.png b/images/tasmet_small.png new file mode 100644 index 0000000..6a5c9ed Binary files /dev/null and b/images/tasmet_small.png differ diff --git a/images/tasmet_transparent.png b/images/tasmet_transparent.png new file mode 100644 index 0000000..91e3124 Binary files /dev/null and b/images/tasmet_transparent.png differ diff --git a/src/gui/images/tasmet.png b/src/gui/images/tasmet.png old mode 100755 new mode 100644 diff --git a/src/gui/images/tasmet_small.png b/src/gui/images/tasmet_small.png old mode 100755 new mode 100644 diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 59f9173..b03a61c 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -11,14 +11,6 @@ #include -#include -#include -#include -#include -#include -#include -#include - #include "tasmet_config.h" #include "tasmet_tracer.h" #include "tasmet_assert.h" @@ -31,6 +23,16 @@ #include "solver_dialog.h" #include "message_tools.h" +#include +#include +#include +#include +#include +#include +#include +#include + + const QString default_model_name = QString("Unsaved TaSMET Model") + constants::model_fileext; const QString default_segment_name = "Unnamed segment"; @@ -38,6 +40,7 @@ const QString start_file_location = QStandardPaths:: writableLocation(QStandardPaths::DocumentsLocation); const QString filetype = QString("TaSMET Model files (*") + constants::model_fileext + ")"; const QString window_title_part = "TaSMET Model Builder - "; + namespace { int saveFileFirstQuestion(QWidget* parent) { @@ -55,6 +58,7 @@ namespace { TaSMETMainWindow::TaSMETMainWindow(): _window(new Ui::MainWindow()), _model(pb::Model::default_instance()), + _saved_model(_model), _system(*_model.mutable_system()) { @@ -120,6 +124,7 @@ TaSMETMainWindow::~TaSMETMainWindow(){ void TaSMETMainWindow::newModel() { TRACE(15,"newModel"); _model = pb::Model::default_instance(); + _saved_model = _model; _filepath = ""; set(_model); } @@ -148,7 +153,11 @@ void TaSMETMainWindow::loadModel(const string* filepath_s) { try { TRACE(15,"Setting loaded model"); _filepath = *filepath_s; - set(loadMessage(*filepath_s)); + _model = loadMessage(*filepath_s); + _saved_model = _model; + _system = *_model.mutable_system(); + set(_model); + } catch(TaSMETError &e) { _filepath = ""; @@ -172,6 +181,7 @@ void TaSMETMainWindow::saveModel(string* filepath) { } try { saveMessage(*filepath,_model); + _saved_model = _model; _filepath = *filepath; changed(); } @@ -282,8 +292,6 @@ void TaSMETMainWindow::set(const pb::Model& model) { _window->backlog->setPlainText(QString::fromStdString(model.backlog())); - _model = model; - _system = *_model.mutable_system(); _init = false; changed(); @@ -311,6 +319,9 @@ void TaSMETMainWindow::closeEvent(QCloseEvent *event) { } } + // If we are here, the model is not dirty anymore. We can savely + // close the application. + // Save window configuration to settings QSettings settings(company,appname); settings.setValue("geometry", saveGeometry()); @@ -470,10 +481,12 @@ void TaSMETMainWindow::on_actionSolve_triggered() { void TaSMETMainWindow::on_actionPostprocess_model_triggered() { try { + TaSystem sys(_model.system()); if(_model.solution_size() == 0) throw TaSMETError("No solution found"); vd sol(_model.solution_size()); + for(us i=0;istartDetached(command,params); } catch(TaSMETError &e) { e.show_user("Postprocessing failed"); @@ -491,18 +519,7 @@ void TaSMETMainWindow::on_actionPostprocess_model_triggered() { } bool TaSMETMainWindow::isDirty() const { TRACE(15,"isDirty()"); - if(_filepath.size()==0) return true; - - try { - pb::Model filemodel = loadMessage(_filepath); - bool dirty = !compareMessage(filemodel,_model); - VARTRACE(15,dirty); - return dirty; - } - catch(TaSMETError& e) { - TRACE(15,"Files could not be compared"); - return true; - } + return !compareMessage(_model,_saved_model); } void TaSMETMainWindow::on_actionAbout_triggered(){ diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index fbf97fd..4bf3116 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -26,6 +26,10 @@ class TaSMETMainWindow: public QMainWindow { // In-memory model pb::Model _model; + pb::Model _saved_model; /**< Last saved state of the model, to + compare with to check whether we + are dirty yes or no */ + pb::System& _system; /**< In constructor set to the _model.mutable_system() */ diff --git a/src/gui/mainwindow.ui b/src/gui/mainwindow.ui index 4a690d1..6d07851 100644 --- a/src/gui/mainwindow.ui +++ b/src/gui/mainwindow.ui @@ -348,7 +348,7 @@ 0 0 683 - 18 + 21 @@ -456,7 +456,7 @@ - Create postprocessing file + Start &postprocessor... diff --git a/src/sys/tasmet_variable.h b/src/sys/tasmet_variable.h index 7000c62..e44c7b5 100644 --- a/src/sys/tasmet_variable.h +++ b/src/sys/tasmet_variable.h @@ -96,7 +96,7 @@ public: // add two Variableiables Variable operator+(const Variable& other) const; //Subtract two Variableiables - Variable operator-(const Variable& Variable2) const; + Variable operator-(const Variable& other) const; // with Note multiplication is defined outside of the class // If we need to multiply two numbers in frequency domain, this diff --git a/src/sys/tasystem.cpp b/src/sys/tasystem.cpp index 473b118..393bf8a 100644 --- a/src/sys/tasystem.cpp +++ b/src/sys/tasystem.cpp @@ -457,10 +457,11 @@ void TaSystem::exportHDF5(const string& filename) const { // H5Eset_auto(error_stack, old_func, old_client_data); /// Disable HDF5 error printing if(file_id < 0) - throw TaSMETError("Creation of export file failed. " - "This could mean that the file is blocked " - "by another program, or the user has insufficient " - "privileges to create the file at the given path." + throw TaSMETError("Creation of export file failed. This is probably " + "because the postprocessor is already running. " + "However, this could also mean that the file is blocked " + "by another program, or you have insufficient " + "privileges to create the postprocessing file." ); /// Enable HDF5 error printing diff --git a/tasmet_postprocessor.py b/tasmet_postprocessor.py old mode 100755 new mode 100644 index 0ac3341..4cbe35b --- a/tasmet_postprocessor.py +++ b/tasmet_postprocessor.py @@ -5,17 +5,26 @@ Created on Thu Feb 2 08:33:53 2017 @author: anne """ - +qt = 5 # Here we provide the necessary imports. # The basic GUI widgets are located in QtGui module. import sys, h5py import numpy as np -from PyQt5.QtCore import * -from PyQt5.QtWidgets import * -from PyQt5.QtGui import QIcon,QDoubleValidator -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar +if qt==4: + from PyQt4.QtCore import * + from PyQt4.QtGui import * + from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas + from matplotlib.backends.backend_qt4 import NavigationToolbar2QT as NavigationToolbar +else: + from PyQt5.QtWidgets import * + from PyQt5.QtGui import QIcon,QDoubleValidator + from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas + from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar + from matplotlib.figure import Figure + +import time +from matplotlib.animation import FuncAnimation from gui.about_dialog import Ui_about_dialog scale = [('nano',-9,'n'), @@ -68,7 +77,7 @@ class TaSMETFigure(Figure): def __init__(self, width=5, height=4, dpi=100): - Figure.__init__(self,figsize=(width, height), dpi=dpi) + Figure.__init__(self, figsize=(width, height), dpi=dpi) self._axes = self.add_subplot(111) @@ -76,6 +85,9 @@ class TaSMETFigure(Figure): self._plots = [] self._grid = False + def setCanvas(self,canvas): + self._canvas = canvas + def addPlot(self,plot,one): if one: self._plots = [plot] @@ -94,7 +106,8 @@ class TaSMETFigure(Figure): self._axes.grid(self._grid,'both') - self.canvas.draw_idle() + self.canvas.draw() + def setGrid(self,grid): self._grid = grid self.rePlot() @@ -107,7 +120,44 @@ class TaSMETFigure(Figure): self._plots = [] self.rePlot() + def animate(self, data): + line = self._axes.get_lines()[0] + max_val = np.max(data) + min_val = np.min(data) + + self._axes.set_ylim((min_val,max_val)) + + # Number of frames available in a period + frames_per_period = data.shape[1] + + # Time to pass to show one period + time_per_period = 1.0 + + # interval in seconds + interval = time_per_period/frames_per_period + + # We need a max, here because due to a bug intervals lower than 200 + # are going plain wrong in matplotlib + interval_ms = max(int(interval*1000),200) + + def animate_i(i): + line.set_ydata(data[:,i%frames_per_period]) + return line, + + ani = FuncAnimation(self, + animate_i, + interval = max(200,interval_ms), + frames = frames_per_period, + blit=True, + repeat = True, + save_count = 300 # High value here as animate_i repeats itself + ) + self.canvas.draw() +# for i in range(100): +# print(i) +# animate_i(i) +# class TaSMETCanvas(FigureCanvas): def __init__(self, parent, figure): @@ -120,7 +170,7 @@ class TaSMETCanvas(FigureCanvas): QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) - + figure.setCanvas(self) class MainWindow(QMainWindow): @@ -277,7 +327,7 @@ class MainWindow(QMainWindow): # Plot menu plot_menu = QMenu('&Plot', self) - self._grid_option = QAction('Enable grid') + self._grid_option = QAction('Enable grid',plot_menu) self._grid_option.setCheckable(True) self._grid_option.triggered.connect(self._figure.setGrid) plot_menu.addAction(self._grid_option) @@ -335,8 +385,28 @@ class MainWindow(QMainWindow): def output(self): print('output') + def disableAll(self): + self._seg_box.setEnabled(False) + self._qty_box.setEnabled(False) + self._vspos.setEnabled(False) + self._vstime.setEnabled(False) + self._vs_instance.setEnabled(False) + self._plot_button.setEnabled(False) + self._output_button.setEnabled(False) + self._animate_button.setEnabled(False) + def animate(self): - print('animate') + self.disableAll() + self._hold_button.setChecked(False) + self.plot() + try: + data,datatype = self.getCurrentData() + except: + return + + self._figure.animate(data[:,:]) + + self.qtyItemChanged() def getCurrentData(self): """ @@ -353,13 +423,12 @@ class MainWindow(QMainWindow): datatype = data.attrs['datatype'].decode('utf-8') return data,datatype - - - - def segItemChanged(self,item): + + def segItemChanged(self,item=None): """ Update the combobox with quantities """ + self._suppressed = True self._qty_box.clear() one = False @@ -373,14 +442,14 @@ class MainWindow(QMainWindow): self._suppressed = False self._qty_box.setCurrentIndex(0) - self.qtyItemChanged(0) + self.qtyItemChanged() - def qtyItemChanged(self,item): + def qtyItemChanged(self,item=None): """ The quantity item has been changed """ - if self._suppressed: return + try: data,datatype = self.getCurrentData() except: @@ -388,16 +457,15 @@ class MainWindow(QMainWindow): segitemno = self._seg_box.currentText() - if data is None: - self._vstime.setChecked(False) - self._vspos.setEnabled(False) - self._vstime.setEnabled(False) - self._vs_instance.setEnabled(False) - self._plot_button.setEnabled(False) - self._output_button.setEnabled(False) - self._animate_button.setEnabled(False) - return + self.disableAll() + self._seg_box.setEnabled(True) + self._qty_box.setEnabled(True) + + if data is None: + return + + self._plot_button.setEnabled(True) self._output_button.setEnabled(True) @@ -405,9 +473,6 @@ class MainWindow(QMainWindow): if datatype == 'time': self._vstime.setChecked(True) self._vspos.setEnabled(False) - self._vstime.setEnabled(False) - self._vs_instance.setEnabled(False) - self._animate_button.setEnabled(False) elif datatype == 'pos': self._vspos.setChecked(True) @@ -429,6 +494,7 @@ class MainWindow(QMainWindow): self._vspos.setEnabled(True) self._vstime.setEnabled(True) self._vs_instance.setEnabled(True) + self._animate_button.setEnabled(True) def initGui(self): self.defaultOutput() @@ -552,8 +618,7 @@ def usage(): if __name__ == '__main__': if(len(sys.argv) < 2): usage() - file_ = '../second_duct_sinomgt.tasmet.h5' -# return -1 + exit(-1) else: file_ = sys.argv[1] @@ -573,4 +638,4 @@ if __name__ == '__main__': w.show() # The show() method displays the widget on the screen. - sys.exit(app.exec_()) # Finally, we enter the mainloop of the application. \ No newline at end of file + sys.exit(app.exec_()) # Finally, we enter the mainloop of the application.