diff --git a/fossils.py b/fossils.py index fc77b59d7185d0767a885099fb1228a1f251eb3b..cb2cf1719ab25935c526d7dc116579386820306f 100755 --- a/fossils.py +++ b/fossils.py @@ -7,7 +7,7 @@ # if starting Qt fails with # qt.qpa.plugin: Could not find the Qt platform plugin "windows" in "" # this means that an application in the PATH provides another version of Qt -# which is incompatible (MikTeX) +# which is incompatible (MikTeX) # => move MikTeX to the bottom of the user PATH. from PyQt5.QtCore import * @@ -17,10 +17,16 @@ from gui.ui_fossils import Ui_Form import sys import os import gui.resources +import numpy as np +import gmsh +if not gmsh.isInitialized(): + gmsh.initialize() + class Window(QWidget, Ui_Form): """Minimal GUI asking for a file and running it """ + def __init__(self, parent=None): super(Window, self).__init__(parent) self.setupUi(self) @@ -40,28 +46,30 @@ class Window(QWidget, Ui_Form): # read Qt settings for the application settings = QSettings() # self.restoreGeometry(settings.value("Geometry", self.saveGeometry())) - self.inpFileLineEdit.setText(settings.value("inpFile", self.default_inputfile())) - self.wrkspLineEdit.setText(settings.value("workspace", self.default_wrksp())) + self.inpFileLineEdit.setText(settings.value( + "inpFile", self.default_inputfile())) + self.wrkspLineEdit.setText(settings.value( + "workspace", self.default_wrksp())) self.action = 'cancelled' - #iconfile = os.path.join('fossils.png') + # iconfile = os.path.join('fossils.png') self.setWindowIcon(QIcon(":/fossils.png")) - def default_inputfile(self): - testnames = [ - os.path.join(os.path.dirname(__file__),'models','others','dolicorhynchops','dolicorhynchops_10k.py'), - os.path.join(os.path.dirname(__file__),'models','dolicorhynchops','dolicorhynchops_10k.py'), + testnames = [ + os.path.join(os.path.dirname(__file__), 'models', 'others', + 'dolicorhynchops', 'dolicorhynchops_10k.py'), + os.path.join(os.path.dirname(__file__), 'models', + 'dolicorhynchops', 'dolicorhynchops_10k.py'), ] for name in testnames: if os.path.isfile(name): return name return "" - def default_wrksp(self): - for key in ['USERPROFILE', 'HOME' ]: + for key in ['USERPROFILE', 'HOME']: try: folder = os.environ[key] if os.path.isdir(folder): @@ -70,7 +78,6 @@ class Window(QWidget, Ui_Form): pass return "" - def on_runPushButton_pressed(self): if not os.path.isfile(self.inpFileLineEdit.text()): QMessageBox.critical(self, "Error", "Input file does not exist!") @@ -78,8 +85,8 @@ class Window(QWidget, Ui_Form): if not os.path.isdir(self.wrkspLineEdit.text()): reply = QMessageBox.question(self, 'Message', - "Workspace does not exist - do you want to continue?", - QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) + "Workspace does not exist - do you want to continue?", + QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.No: return # wdir = build_workspace_name(self.wrkspLineEdit.text(), self.inpFileLineEdit.text()) @@ -87,15 +94,23 @@ class Window(QWidget, Ui_Form): self.action = 'run' self.close() - def on_viewPushButton_pressed(self): - wdir = build_workspace_name(self.wrkspLineEdit.text(), self.inpFileLineEdit.text()) + wdir = build_workspace_name( + self.wrkspLineEdit.text(), self.inpFileLineEdit.text()) if not os.path.isdir(wdir): QMessageBox.critical(self, "Error", f"No results in {wdir}!") return self.action = 'post' self.close() + def on_toCSVPushButton_pressed(self): + wdir = build_workspace_name( + self.wrkspLineEdit.text(), self.inpFileLineEdit.text()) + if not os.path.isdir(wdir): + QMessageBox.critical(self, "Error", f"No results in {wdir}!") + return + self.action = 'csv' + self.close() def on_inpFilePushButton_pressed(self): # try to set a relevant starting folder @@ -107,11 +122,10 @@ class Window(QWidget, Ui_Form): default = curfolder fname = QFileDialog.getOpenFileName( - self, 'Select input file', default, filter='Python File (*.py)') + self, 'Select input file', default, filter='Python File (*.py)') pyfile = fname[0] if pyfile: - self.inpFileLineEdit.setText(QDir.toNativeSeparators(pyfile)) - + self.inpFileLineEdit.setText(QDir.toNativeSeparators(pyfile)) def on_inpFileEditPushButton_pressed(self): """Open input file with a text editor @@ -119,35 +133,30 @@ class Window(QWidget, Ui_Form): editors = [ "C:/Program Files/Just Great Software/EditPad Pro 8/EditPadPro8.exe", "C:/Windows/notepad.exe" - ] + ] for editor in editors: if os.path.isfile(editor): import subprocess - subprocess.Popen([ editor, self.inpFileLineEdit.text() ]) + subprocess.Popen([editor, self.inpFileLineEdit.text()]) break - def on_inpFileResetPushButton_pressed(self): self.inpFileLineEdit.setText(self.default_inputfile()) - def on_wrkspExplorePushButton_pressed(self): """Browse workspace folder with system explorer """ os.startfile(self.wrkspLineEdit.text()) - def on_wrkspPushButton_pressed(self): dir = QFileDialog.getExistingDirectory( self, "Select output folder", self.wrkspLineEdit.text()) if dir: - self.wrkspLineEdit.setText(QDir.toNativeSeparators(dir)) - + self.wrkspLineEdit.setText(QDir.toNativeSeparators(dir)) def on_wrkspResetPushButton_pressed(self): self.wrkspLineEdit.setText(self.default_wrksp()) - def closeEvent(self, event): """save settings to registry and quit """ @@ -163,20 +172,18 @@ def run_simulation(testname): """ # thanks to the "compile" command, the filename appears in the stack # trace in case of errors - script = open(testname, encoding='utf-8').read() - exec(compile(script, testname, 'exec'), - {'__file__': testname, '__name__':'__main__'}) + script = open(testname, encoding='utf-8').read() + exec(compile(script, testname, 'exec'), + {'__file__': testname, '__name__': '__main__'}) -def view_results(): - """Load/display results in the current folder +def load_previous_results(): + """Load previously computed results from the current folder """ - import gmsh - if not gmsh.isInitialized(): - gmsh.initialize() # load empty mesh gmsh.merge('mesh.msh') gmsh.option.setNumber("General.Verbosity", 3) + # load views from the option file views = [] with open('post.opt') as f: @@ -192,12 +199,57 @@ def view_results(): gmsh.merge(v[1]) print('loading options...') gmsh.merge('post.opt') + return views + +def view_results(): + """Load/display results in the current folder + """ + views = load_previous_results() print('starting Gmsh GUI, please wait...') gmsh.fltk.run() +def convert_results_to_csv(): + """Converts results to CSV files + """ + views = load_previous_results() + + # export results to csv + for v in views: + csvfile = v[1].replace('.msh', '.csv') + dataType, tags, data, time, numComp = gmsh.view.getModelData(tag=int(v[0])+1, step=0) # assume tag=index+1 + print(f'exporting "{csvfile}" ({dataType})') + + # export raw "data" to csv + # np.savetxt(csvfile, data, delimiter=',') + + # if tensor => compute j2 for each node + if numComp == 9: + + ndata = np.array(data) + data = np.array([]) + ncols = ndata.shape[1] + nnods = ncols/9 + for i in range(int(nnods)): + xx, xy, xz, yx, yy, yz, zx, zy, zz = ndata[:,i*9:(i+1)*9].T + j2 = np.sqrt( ((xx-yy)**2 + (yy-zz)**2 + (zz-xx)**2 )/2 + 3*(xy*xy+yz*yz+zx*zx) ) + if data.any(): + data = np.vstack([data,j2]) + else: + data = j2 + if nnods == 1: + data = data.reshape(1,-1) + data = data.T + + # export "data" to csv with tags + f = open(csvfile, "w") + for x,y in zip(tags, data): + f.write("{}, {}\n".format(x, ', '.join(map(str, y)))) + f.close() + + def rm_folder_from_pypath(folder): - sys.path = [ p for p in sys.path if not folder in p] + sys.path = [p for p in sys.path if not folder in p] def add_folder2pypath(folder): @@ -209,10 +261,10 @@ def add_folder2pypath(folder): def rm_folder_from_path(folder): import platform if 'Windows' in platform.uname(): - path = [ p for p in os.environ['PATH'].split(';') if not folder in p] + path = [p for p in os.environ['PATH'].split(';') if not folder in p] os.environ['PATH'] = ';'.join(path) # print(f'{folder} added to PATH') - # print(f"os.environ['PATH']={os.environ['PATH']}") + # print(f"os.environ['PATH']={os.environ['PATH']}") def add_folder2path(folder): @@ -240,15 +292,15 @@ def setup_pythonpath(): pyexe = os.path.basename(sys.executable) print(f'pyexe = {pyexe}') add_folder2pypath(os.path.join(this_script_dir, 'cxxfem', - 'build', 'bin')) # gcc/mingw + 'build', 'bin')) # gcc/mingw add_folder2pypath(os.path.join(this_script_dir, 'cxxfem', - 'build', 'bin', 'Release')) # msvc + 'build', 'bin', 'Release')) # msvc # allows this script to be run without setting env # rm_folder_from_path('gmsh') # rm_folder_from_pypath('gmsh') # add_folder2pypath(os.path.join(this_script_dir, 'lib', - # 'gmsh-sdk', 'lib')) # msvc + # 'gmsh-sdk', 'lib')) # msvc # add_folder2path(os.path.join(this_script_dir, 'lib', # 'gmsh-sdk', 'bin')) # gmsh # print(f'sys.path={sys.path}') @@ -260,6 +312,7 @@ def setup_pythonpath(): if os.path.exists(v): os.add_dll_directory(v) + def build_workspace_name(workspace, testname): """create workspace folder and chdir into it """ @@ -268,7 +321,7 @@ def build_workspace_name(workspace, testname): # => workspace + testname common = os.path.basename(testname) resdir = common.replace(os.sep, "_") - resdir, ext = os.path.splitext(resdir) + resdir, ext = os.path.splitext(resdir) wdir = os.path.join(workspace, resdir) else: # workspace is not given: @@ -283,7 +336,6 @@ def build_workspace_name(workspace, testname): wdir = os.path.abspath(os.path.join('workspace', resdir)) return wdir - if __name__ == "__main__": @@ -302,7 +354,8 @@ if __name__ == "__main__": os.environ['OMP_NUM_THREADS'] = str(args.k) cxxfem.set_num_threads(args.k) - __file__ = os.path.abspath(__file__) # relative path on Linux with python <=3.8 + # relative path on Linux with python <=3.8 + __file__ = os.path.abspath(__file__) # display env variables try: @@ -310,7 +363,6 @@ if __name__ == "__main__": except: print(f"OMP_NUM_THREADS=[not set]") - # ask for a file if not given => starts the GUI if not args.file: # QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) @@ -320,7 +372,8 @@ if __name__ == "__main__": win = Window() win.setWindowTitle("Fossils") win.show() - win.setWindowState(Qt.WindowActive) # try to make it appear above any other window (and particularly the console window) + # try to make it appear above any other window (and particularly the console window) + win.setWindowState(Qt.WindowActive) app.lastWindowClosed.connect(app.quit) app.exec_() @@ -329,7 +382,7 @@ if __name__ == "__main__": testname = win.inpFileLineEdit.text() gui = True else: - workspace = None # use default + workspace = None # use default if args.post: action = 'post' else: @@ -340,10 +393,10 @@ if __name__ == "__main__": # run the simulation or display results print(f'action = {action}') - if action=='run' or action=='post': + if action in ['run', 'post', 'csv']: testname = os.path.normcase(testname) # F:/ => f:/ on Windows print(f'testname = {testname}') - + wdir = build_workspace_name(workspace, testname) print('workspace =', wdir) # sys.exit() @@ -353,11 +406,15 @@ if __name__ == "__main__": try: if action == 'run': - tee = cxxfem.Tee('stdout.txt') # split streams (stdout + logfile) + # split streams (stdout + logfile) + tee = cxxfem.Tee('stdout.txt') run_simulation(testname) elif action == 'post': tee = cxxfem.Tee('post.txt') view_results() + elif action == 'csv': + tee = cxxfem.Tee('tocsv.txt') + convert_results_to_csv() except Exception as err: print(f'\n** ERROR: {err}\n') import traceback diff --git a/gui/fossils.ui b/gui/fossils.ui index c849628d3a4238a57c201c7a3514688e12a3d98f..60672be53959fc2eb329bc1a8c22188785369cf3 100644 --- a/gui/fossils.ui +++ b/gui/fossils.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>745</width> - <height>154</height> + <width>756</width> + <height>116</height> </rect> </property> <property name="windowTitle"> @@ -129,7 +129,7 @@ </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> + <layout class="QHBoxLayout" name="horizontalLayout"> <item> <spacer name="horizontalSpacer_2"> <property name="orientation"> @@ -169,6 +169,19 @@ </property> </widget> </item> + <item> + <widget class="QPushButton" name="toCSVPushButton"> + <property name="toolTip"> + <string>read/display the results stored in the folder of the workspace</string> + </property> + <property name="statusTip"> + <string/> + </property> + <property name="text"> + <string>to CSV</string> + </property> + </widget> + </item> <item> <spacer name="horizontalSpacer"> <property name="orientation"> diff --git a/gui/ui_fossils.py b/gui/ui_fossils.py index c13b08da698f32da6e46a06b08e2b93c555d40ce..9f53310a0c573fb23ca440e8d4807c1f7e51cba0 100644 --- a/gui/ui_fossils.py +++ b/gui/ui_fossils.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") - Form.resize(745, 154) + Form.resize(756, 116) self.verticalLayout = QtWidgets.QVBoxLayout(Form) self.verticalLayout.setObjectName("verticalLayout") self.gridLayout = QtWidgets.QGridLayout() @@ -58,21 +58,25 @@ class Ui_Form(object): self.wrkspResetPushButton.setObjectName("wrkspResetPushButton") self.gridLayout.addWidget(self.wrkspResetPushButton, 1, 4, 1, 1) self.verticalLayout.addLayout(self.gridLayout) - self.horizontalLayout_2 = QtWidgets.QHBoxLayout() - self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.horizontalLayout_2.addItem(spacerItem) + self.horizontalLayout.addItem(spacerItem) self.runPushButton = QtWidgets.QPushButton(Form) self.runPushButton.setStatusTip("") self.runPushButton.setObjectName("runPushButton") - self.horizontalLayout_2.addWidget(self.runPushButton) + self.horizontalLayout.addWidget(self.runPushButton) self.viewPushButton = QtWidgets.QPushButton(Form) self.viewPushButton.setStatusTip("") self.viewPushButton.setObjectName("viewPushButton") - self.horizontalLayout_2.addWidget(self.viewPushButton) + self.horizontalLayout.addWidget(self.viewPushButton) + self.toCSVPushButton = QtWidgets.QPushButton(Form) + self.toCSVPushButton.setStatusTip("") + self.toCSVPushButton.setObjectName("toCSVPushButton") + self.horizontalLayout.addWidget(self.toCSVPushButton) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.horizontalLayout_2.addItem(spacerItem1) - self.verticalLayout.addLayout(self.horizontalLayout_2) + self.horizontalLayout.addItem(spacerItem1) + self.verticalLayout.addLayout(self.horizontalLayout) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) @@ -100,3 +104,5 @@ class Ui_Form(object): self.runPushButton.setText(_translate("Form", "Run")) self.viewPushButton.setToolTip(_translate("Form", "read/display the results stored in the folder of the workspace")) self.viewPushButton.setText(_translate("Form", "View")) + self.toCSVPushButton.setToolTip(_translate("Form", "read/display the results stored in the folder of the workspace")) + self.toCSVPushButton.setText(_translate("Form", "to CSV"))