diff --git a/CfdOF/CfdConsoleProcess.py b/CfdOF/CfdConsoleProcess.py
index 980c77f2..abb06718 100644
--- a/CfdOF/CfdConsoleProcess.py
+++ b/CfdOF/CfdConsoleProcess.py
@@ -51,6 +51,7 @@ def __init__(self, finished_hook=None, stdout_hook=None, stderr_hook=None):
def __del__(self):
self.terminate()
+ #TODO document what the format of cmd is for developers
def start(self, cmd, env_vars=None, working_dir=None):
""" Start process and return immediately """
self.print_next_error_lines = 0
@@ -79,12 +80,18 @@ def terminate(self):
self.process.write(b"terminate\n")
self.process.waitForBytesWritten() # 'flush'
else:
+ print("Process has been terminated")
self.process.terminate()
self.process.waitForFinished()
def finished(self, exit_code):
+ print("Process has finished")
if self.finishedHook:
+ #print("finished hook was called with exit code:" + str(exit_code))
self.finishedHook(exit_code)
+ else:
+ pass
+ #print("Error: finished wasn't hooked to a handler")
def readStdout(self):
# Ensure only complete lines are passed on
diff --git a/CfdOF/CfdPreferencePage.py b/CfdOF/CfdPreferencePage.py
index d9f9ed21..1e4dfefb 100644
--- a/CfdOF/CfdPreferencePage.py
+++ b/CfdOF/CfdPreferencePage.py
@@ -114,6 +114,7 @@ def __init__(self):
self.form.tb_choose_output_dir.clicked.connect(self.chooseOutputDir)
self.form.le_output_dir.textChanged.connect(self.outputDirChanged)
+ self.form.cb_add_filename_to_output.clicked.connect(self.addFilenameChanged)
self.form.cb_docker_sel.clicked.connect(self.dockerCheckboxClicked)
self.form.pb_download_install_docker.clicked.connect(self.downloadInstallDocker)
@@ -145,6 +146,13 @@ def __init__(self):
def __del__(self):
self.cleanUp()
+ # This is a special version that only returns the base output path.
+ # The version in CfdTools.py returns the full output path, with the filename appended
+ def getDefaultOutputPath(self):
+ prefs = CfdTools.getPreferencesLocation()
+ output_path = FreeCAD.ParamGet(prefs).GetString("DefaultOutputPath", "")
+ return output_path
+
def cleanUp(self):
if self.thread and self.thread.isRunning():
FreeCAD.Console.PrintError("Terminating a pending install task\n")
@@ -179,7 +187,8 @@ def loadSettings(self):
self.initial_gmsh_path = str(self.gmsh_path)
self.form.le_gmsh_path.setText(self.gmsh_path)
- self.output_dir = CfdTools.getDefaultOutputPath()
+ #self.output_dir = CfdTools.getDefaultOutputPath()
+ self.output_dir = self.getDefaultOutputPath()
self.form.le_output_dir.setText(self.output_dir)
if FreeCAD.ParamGet(prefs).GetBool("UseDocker", 0):
@@ -192,6 +201,13 @@ def loadSettings(self):
self.setDownloadURLs()
+ # disable add_filename_to_output for now.
+ # self.form.cb_add_filename_to_output.setEnabled(False)
+ if FreeCAD.ParamGet(prefs).GetBool("AddFilenameToOutput",0):
+ self.form.cb_add_filename_to_output.setChecked(True)
+ else:
+ self.form.cb_add_filename_to_output.setChecked(False)
+
def consoleMessage(self, message="", colour_type=None):
message = escape(message)
message = message.replace('\n', ' ')
@@ -202,6 +218,10 @@ def consoleMessage(self, message="", colour_type=None):
self.form.textEdit_Output.setText(self.console_message)
self.form.textEdit_Output.moveCursor(QtGui.QTextCursor.End)
+ def addFilenameChanged(self):
+ prefs = CfdTools.getPreferencesLocation()
+ FreeCAD.ParamGet(prefs).SetBool("AddFilenameToOutput", self.form.cb_add_filename_to_output.isChecked())
+
def foamDirChanged(self, text):
self.foam_dir = text
if self.foam_dir and os.access(self.foam_dir, os.R_OK):
diff --git a/CfdOF/CfdRemotePreferencePage.py b/CfdOF/CfdRemotePreferencePage.py
new file mode 100644
index 00000000..8b2af3f4
--- /dev/null
+++ b/CfdOF/CfdRemotePreferencePage.py
@@ -0,0 +1,1653 @@
+
+
+# ***************************************************************************
+# * *
+# * Copyright (c) 2017-2018 Oliver Oxtoby (CSIR) *
+# * Copyright (c) 2017 Johan Heyns (CSIR) *
+# * Copyright (c) 2017 Alfred Bogaers (CSIR) *
+# * Copyright (c) 2019-2022 Oliver Oxtoby *
+# * *
+# * This program is free software: you can redistribute it and/or modify *
+# * it under the terms of the GNU Lesser General Public License as *
+# * published by the Free Software Foundation, either version 3 of the *
+# * License, or (at your option) any later version. *
+# * *
+# * This program is distributed in the hope that it will be useful, *
+# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+# * GNU Lesser General Public License for more details. *
+# * *
+# * You should have received a copy of the GNU Lesser General Public *
+# * License along with this program. If not, *
+# * see . *
+# * *
+# ***************************************************************************
+#
+# LinuxGuy123@gmail.com's notes.
+#
+# This file started out as a simple clone of the CfdPreferencesPage to handle a remote host instead
+# of the local host. But with the changes needed to handle multiple host profiles, it changed a lot.
+#
+# The first iteration of this code was to change it to use a single remote host.
+#
+# The current iteration of this code now handles multiple remote hosts via host profiles. This resulted in
+# the working parts getting almost complelely rewritten. This code presently only works with Linux remote hosts
+# or at least it has only been tested with Linux remote hosts. I left in many pieces of the old code in case they are
+# needed to add Docker, Windows, etc. on the remote host.
+#
+# How this page works...
+# If Use Remote Processing is enabled, the controls are enabled. If not, they aren't.
+# The page then loads the profile names from the parameters file.
+#
+# Then it loads the parameters for the first profile listed in the profile drop down box.
+# With these parameters it initializes the controls and the local vars.
+# When the users changes a parameter in the gui it is immediately written to the parameter file and
+# the local var is updated.
+# When the profile is changed the new parameters for the profile are loaded and the local vars
+# are updated as are the controls.
+#
+# This page ignores the OK, Apply and Cancel buttons. There is a routine to save the current
+# profile (saveProfile) on OK, change profile and Apply, but are those buttons are ignored in CfdOF.
+# As they were in the previous version. Instead, every field is saved to the local var and the parameter file
+# when it is entered.
+#
+# The remotePreferences GUI file (CfdRemotePreferencePage.ui) file still has many controls that are not used. See
+# enableControls() for a list of what is or isn't used.
+# They are set to disabled and not visible so they don't appear in the running GUI. They were left in in case
+# they would be needed in the future. But generally the disabled controls don't make sense on a remote host -
+# Paraview, for example. At some point, once the page has been deemed to operate in an acceptable manner, they
+# should be removed entirely.
+#
+# In the initial code I wrote the variables that were used with remote operation had _remote_ in their name. I removed
+# them because the var names were pretty long. Now all the vars in this code are assumed to be referring to a remote host.
+# The only names that have remote in them are procedures that replaced non remote procedures. For example runRemoteDependencyCheck.
+# I left the code for runDependencyCheck in the file because parts of it may be used if/when Windows or Docker is added for remote hosts.
+#
+# The reason I'm telling you this is to justify how messy the code is right now. It would be very easy to remove all the unused
+# code and clean things up. But right now the unused code is present in case they are needed in the near future.
+#
+# This application doesn't install OpenFOAM on the remote host. It also doesn't install gmsh. It does install cfmesh
+# if OpenFOAM (and OpenFOAM-devel) is installed.
+#
+# I did not use worker threads to install cfMesh. I just ran the processes from the ssh command line.
+#
+# TODO:
+
+# - fix downloadInstallCFMesh Its broken due to using profiles. Doesn't stop installing if a step fails.
+# Also requires the working directory folder to be present. Fails if it isn't.
+# - get About Remote Processing document window working - put User's Guide in it
+# - put the remote host computer fields in a container like Docker, OpenFOAM, etc.
+# - get tooltips working
+# - OpenFOAM doesn't need the number of threads and processes like the meshers do. Remove one.
+
+# Done: get cfMesh URL control working
+# Done: implement host profiles so that multiple remote hosts can be used
+# Done: store and load the use remote processing boolean
+# Done: enable and disable all the controls with the cb_use_remote_processing checkbox result
+# Done: store and load the host name and username fields
+# Done: get ping host working
+# Done: get test ssh working
+# Nope: move ping and ssh to CfdOFTools ?
+# Done: get remote foam dir storing and loading
+# Done: remote gmsh path storing and loading
+# Done: remote work dir storing and loading
+# Done: get download and install cfMesh working
+
+#********************************************************************************
+
+import os
+import os.path
+import platform
+import sys
+if sys.version_info >= (3,): # Python 3
+ import urllib.request as urlrequest
+ import urllib.parse as urlparse
+else:
+ import urllib as urlrequest
+ import urlparse
+
+ #import traceback #This as used during debugging
+
+import ssl
+import ctypes
+import FreeCAD
+from CfdOF import CfdTools
+import tempfile
+from contextlib import closing
+from xml.sax.saxutils import escape
+
+if FreeCAD.GuiUp:
+ import FreeCADGui
+ from PySide import QtCore
+ from PySide import QtGui
+ from PySide.QtCore import Qt, QObject, QThread
+ from PySide.QtGui import QApplication
+
+
+# Constants
+OPENFOAM_URL = \
+ "https://sourceforge.net/projects/openfoam/files/v2206/OpenFOAM-v2206-windows-mingw.exe/download"
+OPENFOAM_FILE_EXT = ".exe"
+PARAVIEW_URL = \
+ "https://www.paraview.org/paraview-downloads/download.php?submit=Download&version=v5.10&type=binary&os=Windows&downloadFile=ParaView-5.10.1-Windows-Python3.9-msvc2017-AMD64.exe"
+PARAVIEW_FILE_EXT = ".exe"
+CFMESH_URL = \
+ "https://sourceforge.net/projects/cfmesh-cfdof/files/cfmesh-cfdof.zip/download"
+CFMESH_URL_MINGW = \
+ "https://sourceforge.net/projects/cfmesh-cfdof/files/cfmesh-cfdof-binaries-{}.zip/download"
+CFMESH_FILE_BASE = "cfmesh-cfdof"
+CFMESH_FILE_EXT = ".zip"
+HISA_URL = \
+ "https://sourceforge.net/projects/hisa/files/hisa-master.zip/download"
+HISA_URL_MINGW = \
+ "https://sourceforge.net/projects/hisa/files/hisa-master-binaries-{}.zip/download"
+HISA_FILE_BASE = "hisa-master"
+HISA_FILE_EXT = ".zip"
+DOCKER_URL = \
+ "docker.io/mmcker/cfdof-openfoam"
+
+OPENFOAM_DIR = "/usr/lib/openfoam/openfoam2206"
+
+
+# Tasks for the worker thread
+# Not used anymore
+DOWNLOAD_OPENFOAM = 1
+DOWNLOAD_PARAVIEW = 2
+DOWNLOAD_CFMESH = 3
+DOWNLOAD_HISA = 4
+DOWNLOAD_DOCKER = 5
+
+
+class CloseDetector(QObject):
+ def __init__(self, obj, callback):
+ super().__init__(obj)
+ self.callback = callback
+
+ def eventFilter(self, obj, event):
+ if event.type() == QtCore.QEvent.ChildRemoved:
+ self.callback()
+ return False
+
+
+#*************************************************************************
+
+class CfdRemotePreferencePage:
+ def __init__(self):
+
+ # load the page
+ ui_path = os.path.join(CfdTools.getModulePath(), 'Gui', "CfdRemotePreferencePage.ui")
+ self.form = FreeCADGui.PySideUic.loadUi(ui_path)
+
+ # set the preferences parameters location
+ self.prefs_location = CfdTools.getPreferencesLocation()
+
+ # get the use remote processing parameter and set the use RP control
+ self.use_remote_processing = FreeCAD.ParamGet(self.prefs_location).GetBool("UseRemoteProcessing")
+ self.form.cb_use_remote_processing.setChecked(self.use_remote_processing)
+
+ #enable controls appropriately
+ self.enableControls(self.use_remote_processing)
+
+ # connect the handler for remote_processing.clicked
+ self.form.cb_use_remote_processing.clicked.connect(self.useRemoteProcessingChanged)
+
+ #TODOself.form.cb_use_remote_processing.setToolTip("Remote processing is
+ # the use of a remote computer to run meshing and OpenFOAM operations")
+
+ # set up the profiles combo box
+ self.form.cb_profile.currentIndexChanged.connect(self.profileChanged)
+
+ # connect the hostname and username controls
+ self.form.le_hostname.textChanged.connect(self.hostnameChanged)
+ self.form.le_username.textChanged.connect(self.usernameChanged)
+
+ #connect the ping and ssh test push buttons
+ self.form.pb_ping_host.clicked.connect(self.pingHost)
+ self.form.pb_test_ssh.clicked.connect(self.testSSH)
+
+ #connect the mesh and foam processes and threads controls
+ self.form.le_mesh_threads.textChanged.connect(self.meshThreadsChanged)
+ self.form.le_mesh_processes.textChanged.connect(self.meshProcessesChanged)
+ self.form.le_foam_threads.textChanged.connect(self.foamThreadsChanged)
+ self.form.le_foam_processes.textChanged.connect(self.foamProcessesChanged)
+
+ # connect the foam_dir changed control
+ #self.form.tb_choose_remote_foam_dir.clicked.connect(self.chooseFoamDir)
+ self.form.le_foam_dir.textChanged.connect(self.foamDirChanged)
+
+ #connect the add filename to output control
+ self.form.cb_add_filename_to_output.clicked.connect(self.addFilenameToOutputChanged)
+
+ self.form.cb_copy_back.clicked.connect(self.copyBackChanged)
+ self.form.cb_delete_remote_results.clicked.connect(self.deleteRemoteResultsChanged)
+
+
+
+ # connect handlers for various control events
+ # note: some of these aren't actually used. Code left in tact anyway
+ self.form.tb_choose_paraview_path.clicked.connect(self.chooseParaviewPath)
+ self.form.le_paraview_path.textChanged.connect(self.paraviewPathChanged)
+
+ self.form.tb_choose_remote_gmsh_path.clicked.connect(self.chooseGmshPath)
+ self.form.le_gmsh_path.textChanged.connect(self.gmshPathChanged)
+
+ self.form.pb_run_dependency_checker.clicked.connect(self.runRemoteDependencyChecker)
+ self.form.pb_download_install_openfoam.clicked.connect(self.downloadInstallOpenFoam)
+ self.form.tb_pick_openfoam_file.clicked.connect(self.pickOpenFoamFile)
+ self.form.pb_download_install_paraview.clicked.connect(self.downloadInstallParaview)
+ self.form.tb_pick_paraview_file.clicked.connect(self.pickParaviewFile)
+
+ self.form.pb_download_install_cfMesh.clicked.connect(self.remoteDownloadInstallCfMesh)
+ self.form.tb_pick_cfmesh_file.clicked.connect(self.pickCfMeshFile)
+ self.form.pb_download_install_hisa.clicked.connect(self.downloadInstallHisa)
+ self.form.tb_pick_hisa_file.clicked.connect(self.pickHisaFile)
+
+ self.form.pb_add_profile.clicked.connect(self.addProfile)
+ self.form.pb_delete_profile.clicked.connect(self.deleteProfile)
+
+ # initialize general things with defaults
+ # this gets done from loadProfile() now.
+ # self.form.le_openfoam_url.setText(OPENFOAM_URL)
+ # self.form.le_paraview_url.setText(PARAVIEW_URL)
+
+ self.form.tb_choose_remote_output_dir.clicked.connect(self.chooseOutputDir)
+ self.form.le_output_path.textChanged.connect(self.outputPathChanged)
+
+ self.form.cb_docker_sel.clicked.connect(self.dockerCheckboxClicked)
+ self.form.pb_download_install_docker.clicked.connect(self.downloadInstallDocker)
+
+ self.dockerCheckboxClicked()
+
+ self.ev_filter = CloseDetector(self.form, self.cleanUp)
+ self.form.installEventFilter(self.ev_filter)
+
+ # set global vars
+ self.thread = None
+ self.install_process = None
+
+ self.console_message = ""
+
+ #setting these here so they get created as globals
+ #they also get initiated in loadProfile()
+ # self.use_remote_processing = False <- this is set above before the control is loaded
+
+ self.profile_name = ""
+ self.host_prefs_location = ""
+ self.hostname = ""
+ self.username = ""
+ self.mesh_processes = 0
+ self.mesh_threads = 0
+ self.foam_processes = 0
+ self.foam_threads = 0
+ self.foam_dir = ""
+ self.output_path = ""
+ self.gmsh_path = ""
+ self.add_filename_to_output = False
+ self.delete_remote_results = False
+ self.copy_back = False
+
+ # TODO:fix these references
+ # if they are still used. Most are not.
+ #self.initial_remote_foam_dir = ""
+ #self.paraview_path = ""
+ #self.initial_paraview_path = ""
+ #self.initial_remote_gmsh_path = ""
+ #self.remote_output_dir = ""
+
+ # not sure what this is for
+ self.form.gb_openfoam.setVisible(platform.system() == 'Windows')
+ self.form.gb_paraview.setVisible(platform.system() == 'Windows')
+
+ # now load the profiles into the combo box
+ self.loadProfileNames()
+
+ #now load the top profile
+ self.loadProfile(self.form.cb_profile.currentText())
+
+
+ # enable/disable the controls on this page
+ def enableControls(self,value):
+
+ self.form.cb_profile.setEnabled(value)
+ self.form.pb_add_profile.setEnabled(value)
+ self.form.pb_delete_profile.setEnabled(value)
+
+ self.form.le_hostname.setEnabled(value)
+ self.form.le_username.setEnabled(value)
+ self.form.pb_ping_host.setEnabled(value)
+ self.form.pb_test_ssh.setEnabled(value)
+
+ self.form.le_mesh_processes.setEnabled(value)
+ self.form.le_mesh_threads.setEnabled(value)
+ self.form.le_foam_processes.setEnabled(value)
+ self.form.le_foam_threads.setEnabled(value)
+
+ self.form.le_foam_dir.setEnabled(value)
+ self.form.le_output_path.setEnabled(value)
+ self.form.cb_add_filename_to_output.setEnabled(value)
+
+ self.form.pb_run_dependency_checker.setEnabled(value)
+
+ self.form.pb_download_install_cfMesh.setEnabled(value)
+ self.form.le_cfmesh_url.setEnabled(value)
+ self.form.le_cfmesh_url.setText(CFMESH_URL)
+
+ self.form.cb_copy_back.setEnabled(value)
+ self.form.cb_delete_remote_results.setEnabled(value)
+ self.form.cb_add_filename_to_output.setEnabled(value)
+
+
+ # Controls below here are not yet operational regardless of if remote processing
+ # is enabled or not. So they are disabled. Change this as new functionality
+ # is added to the page
+ value = False
+
+
+ self.form.pb_about_remote_processing.setEnabled(value)
+
+ # disable the foam path chooser
+ self.form.tb_choose_remote_foam_dir.setEnabled(value)
+ self.form.tb_choose_remote_foam_dir.setVisible(value)
+
+ # disable paraview stuff and make it invisible because we
+ # don't run paraview on the remote computer
+ # This makes it disappear from the page but allows us to
+ # leave the controls and code in place
+ self.form.le_paraview_path.setEnabled(value)
+ self.form.le_paraview_path.setVisible(False)
+ self.form.lbl_remote_paraview_path.setVisible(False)
+ self.form.tb_choose_paraview_path.setEnabled(value)
+ self.form.tb_choose_paraview_path.setVisible(False)
+
+ # disable the gmsh path
+ # we don't need to know gmsh's path in Linux. Either it is installed or not
+ # we just call it from the shell, This might change if the remote host is running
+ # a different OS.
+ self.form.lbl_gmsh_path.setVisible(False)
+ self.form.le_gmsh_path.setEnabled(value)
+ self.form.le_gmsh_path.setVisible(False)
+ self.form.tb_choose_remote_gmsh_path.setEnabled(value)
+ self.form.tb_choose_remote_gmsh_path.setVisible(value)
+
+ #disable the output path chooser
+ self.form.tb_choose_remote_output_dir.setEnabled(value)
+ self.form.tb_choose_remote_output_dir.setVisible(False)
+
+
+ # disable docker stuff. Setting it to invisible does not
+ # make it disappear.
+ self.form.gb_docker.setEnabled(False)
+ self.form.gb_docker.setVisible(False)
+
+ self.form.pb_download_install_openfoam.setEnabled(value)
+ self.form.tb_pick_openfoam_file.setEnabled(value)
+
+ self.form.pb_download_install_paraview.setEnabled(value)
+ self.form.tb_pick_paraview_file.setEnabled(value)
+
+ self.form.cb_docker_sel.setEnabled(value)
+ self.form.le_docker_url.setEnabled(value)
+ self.form.pb_download_install_docker.setEnabled(value)
+
+ self.form.tb_pick_cfmesh_file.setEnabled(value)
+ #self.form.pb_download_install_cfMesh.setEnabled(value)
+
+ self.form.pb_download_install_hisa.setEnabled(value)
+ self.form.tb_pick_hisa_file.setEnabled(value)
+ self.form.le_hisa_url.setEnabled(value)
+ self.form.pb_download_install_hisa.setEnabled(value)
+
+ self.form.le_openfoam_url.setEnabled(value)
+
+ # loads the profiles names into the profile combo box
+ def loadProfileNames(self):
+ profileDir = self.prefs_location + "/Hosts"
+ profiles = FreeCAD.ParamGet(profileDir)
+ profileList = profiles.GetGroups()
+ for item in profileList:
+ self.form.cb_profile.addItem(item)
+
+ # load profile parameters into the controls and local vars
+ def loadProfile(self, profile_name):
+
+ #set the global profile name
+ self.profile_name = profile_name
+
+ #set the global host prefs location
+ self.host_prefs_location = self.prefs_location + "/Hosts/" + profile_name
+
+ #set the other global vars
+ if profile_name == "":
+ self.username = ""
+ self.hostname = ""
+ self.mesh_processes = 0
+ self.mesh_threads = 0
+ self.foam_processes = 0
+ self.foam_threads = 0
+ self.foam_dir = ""
+ self.output_path = ""
+ self.add_filename_to_output = False
+ self.copy_back = False
+ self.delete_remote_results = False
+
+ else:
+ hostPrefs = self.host_prefs_location
+ self.hostname = FreeCAD.ParamGet(hostPrefs).GetString("Hostname", "")
+ self.username = FreeCAD.ParamGet(hostPrefs).GetString("Username", "")
+ self.mesh_processes = FreeCAD.ParamGet(hostPrefs).GetInt("MeshProcesses")
+ self.mesh_threads = FreeCAD.ParamGet(hostPrefs).GetInt("MeshThreads")
+ self.foam_processes = FreeCAD.ParamGet(hostPrefs).GetInt("FoamProcesses")
+ self.foam_threads = FreeCAD.ParamGet(hostPrefs).GetInt("FoamThreads")
+ self.foam_dir = FreeCAD.ParamGet(hostPrefs).GetString("FoamDir", "")
+ self.output_path = FreeCAD.ParamGet(hostPrefs).GetString("OutputPath","")
+ self.add_filename_to_output = FreeCAD.ParamGet(hostPrefs).GetBool("AddFilenameToOutput")
+ self.copy_back = FreeCAD.ParamGet(hostPrefs).GetBool("CopyBack")
+ self.delete_remote_results = FreeCAD.ParamGet(hostPrefs).GetBool("DeleteRemoteResults")
+
+
+ #now set the UI controls
+ self.form.le_hostname.setText(self.hostname)
+ self.form.le_username.setText(self.username)
+ self.form.le_mesh_processes.setText(str(self.mesh_processes))
+ self.form.le_mesh_threads.setText(str(self.mesh_threads))
+ self.form.le_foam_processes.setText(str(self.foam_processes))
+ self.form.le_foam_threads.setText(str(self.foam_threads))
+ self.form.le_foam_dir.setText(self.foam_dir)
+ self.form.le_output_path.setText(self.output_path)
+ self.form.cb_add_filename_to_output.setChecked(self.add_filename_to_output)
+ self.form.cb_copy_back.setChecked(self.copy_back)
+ self.form.cb_delete_remote_results.setChecked(self.delete_remote_results)
+
+
+ # create a new profile and add it to the cb_profile control
+ # and create its entry in the CfdOF parameters
+ def addProfile(self):
+ # get the new profile name
+ #https://wiki.freecad.org/PySide_Beginner_Examples/en
+ profile_name, ok = QtGui.QInputDialog.getText(None,"Profile Name", "Enter the name of the profile you'd like to add.\n It cannot contain spaces or slashes")
+ if ok and profile_name:
+ print("Adding: " + profile_name)
+ #TODO: test for spaces or slashes here.
+
+ # add the profile name to profile combo list
+ self.form.cb_profile.addItem(profile_name)
+ # move to the item just added
+ # TODO: check if text isn't found, ie index = -1
+ index = self.form.cb_profile.findText(profile_name)
+ self.form.cb_profile.setCurrentIndex(index)
+
+ # update the global profile_name var
+ self.profile_name = profile_name
+
+ # update the global host_prefs_location
+ self.host_prefs_location = self.prefs_location + "/Hosts/" + profile_name
+
+ # add the default setting for a profile into parameters
+ hostPrefs = self.host_prefs_location
+ FreeCAD.ParamGet(hostPrefs).SetString("Hostname", "")
+ FreeCAD.ParamGet(hostPrefs).SetString("Username", "")
+ FreeCAD.ParamGet(hostPrefs).SetInt("MeshProcesses", 0)
+ FreeCAD.ParamGet(hostPrefs).SetInt("MeshThreads", 0)
+ FreeCAD.ParamGet(hostPrefs).SetInt("FoamProcesses", 0)
+ FreeCAD.ParamGet(hostPrefs).SetInt("FoamThreads", 0)
+ FreeCAD.ParamGet(hostPrefs).SetString("FoamDir", OPENFOAM_DIR)
+ FreeCAD.ParamGet(hostPrefs).SetString("OutputPath","/tmp")
+ FreeCAD.ParamGet(hostPrefs).SetBool("AddFilenameToOutput", False)
+ FreeCAD.ParamGet(hostPrefs).SetBool("CopyBack", False)
+ FreeCAD.ParamGet(hostPrefs).SetBool("DeleteRemoteResults", False)
+
+ # now load the controls and local vars from the profile parameters
+ self.loadProfile(profile_name)
+
+ # delete the current profile in the cb_profile and in parameters
+ # this will trigger a profile change which will update the controls and
+ # vars with the values from the new profile
+ def deleteProfile(self):
+ #check if there is a profile in the profile combo box to delete
+ if self.form.cb_profile.currentText() == "":
+ return
+
+ # ask the user if they really want to delete the profile
+ #https://wiki.freecad.org/PySide_Beginner_Examples/en
+ deleteProfile = self.form.cb_profile.currentText()
+ deleteIndex = self.form.cb_profile.currentIndex()
+ deleteQuestion = "Delete profile " + deleteProfile + "?\n" + "This cannot be undone."
+
+ reply = QtGui.QMessageBox.question(None, "Delete profile ?", deleteQuestion,
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
+
+ if reply == QtGui.QMessageBox.Yes:
+ #delete it from the control
+ self.form.cb_profile.removeItem(deleteIndex)
+ # delete it from the parameters list
+ profileDir = self.prefs_location + "/Hosts"
+ FreeCAD.ParamGet(profileDir).RemGroup(deleteProfile)
+
+ if reply == QtGui.QMessageBox.No:
+ # this is where the code relevant to a 'No' answer goes
+ pass
+
+ # this gets called when the user changes the profile
+ # Note: doesn't save the last profile before changing to the new profile
+ def profileChanged(self):
+ print("The profile was changed")
+ # change the global profile name
+ self.profile_name = self.form.cb_profile.currentText()
+ #load the values for the new profile
+ print ("New profile is ", self.profile_name)
+ self.loadProfile(self.profile_name)
+
+
+ # save the current profile by writing its the parameters
+ # not currently used.
+ def saveProfile(self):
+ print("saveProfile has fired")
+ if self.profile_name != "":
+ hostPrefs = self.host_prefs_location
+ FreeCAD.ParamGet(hostPrefs).SetString("Hostname", self.hostname)
+ FreeCAD.ParamGet(hostPrefs).SetString("Username", self.username)
+ FreeCAD.ParamGet(hostPrefs).SetInt("MeshProcesses", self.mesh_processes)
+ FreeCAD.ParamGet(hostPrefs).SetInt("MeshThreads", self.mesh_threads)
+ FreeCAD.ParamGet(hostPrefs).SetInt("FoamProcesses",self.foam_processes)
+ FreeCAD.ParamGet(hostPrefs).SetInt("FoamThreads",self.foam_threads)
+ FreeCAD.ParamGet(hostPrefs).SetString("FoamDir", self.foam_dir)
+ FreeCAD.ParamGet(hostPrefs).SetString("OutputPath",self.output_path)
+ FreeCAD.ParamGet(hostPrefs).SetBool("AddFilenameToOutput", self.add_filename_to_output)
+ FreeCAD.ParamGet(hostPrefs).SetBool("DeleteRemoteResults", self.delete_remote_results)
+ FreeCAD.ParamGet(hostPrefs).SetBool("CopyBack", self.copy_back)
+ self.profileChanged = False
+
+ def meshThreadsChanged(self):
+ if self.profile_name == "":
+ return
+ if self.form.le_mesh_threads.text() == "":
+ return
+ #else
+ #update the global var
+ self.mesh_threads = int(self.form.le_mesh_threads.text())
+ #write the new ValueError to the host profile
+ FreeCAD.ParamGet(self.host_prefs_location).SetInt("MeshThreads", self.mesh_threads)
+
+ def meshProcessesChanged(self):
+ if self.profile_name == "":
+ return
+ if self.form.le_mesh_processes.text() == "":
+ return
+ #else
+ #update the global var
+ self.mesh_processes = int(self.form.le_mesh_processes.text())
+ #write the new ValueError to the host profile
+ FreeCAD.ParamGet(self.host_prefs_location).SetInt("MeshProcesses", self.mesh_processes)
+
+ def foamThreadsChanged(self):
+ if self.profile_name == "":
+ return
+ if self.form.le_foam_threads.text() == "":
+ return
+ #else
+ #update the global var
+ self.foam_threads = int(self.form.le_foam_threads.text())
+ #write the new ValueError to the host profile
+ FreeCAD.ParamGet(self.host_prefs_location).SetInt("FoamThreads", self.foam_threads)
+
+ def foamProcessesChanged(self):
+ #print("foamProcessesChanged has fired")
+ #traceback.print_stack()
+ if self.profile_name == "":
+ return
+ if self.form.le_foam_processes.text() == "":
+ return
+ #else
+ #update the global var
+ self.foam_processes = int(self.form.le_foam_processes.text())
+ #write the new ValueError to the host profile
+ FreeCAD.ParamGet(self.host_prefs_location).SetInt("FoamProcesses", self.foam_processes)
+
+ def useRemoteProcessingChanged(self):
+ if (self.form.cb_use_remote_processing.isChecked() == True):
+ self.enableControls(True)
+ FreeCAD.ParamGet(self.prefs_location).SetBool("UseRemoteProcessing", True)
+ else:
+ self.enableControls(False)
+ FreeCAD.ParamGet(self.prefs_location).SetBool("UseRemoteProcessing", False)
+
+ def hostnameChanged(self):
+ if self.profile_name == "":
+ return
+ # update the global variables
+ self.hostname = self.form.le_hostname.text()
+ FreeCAD.ParamGet(self.host_prefs_location).SetString("Hostname", self.hostname )
+ self.profile_changed = True
+
+ def usernameChanged(self):
+ if self.profile_name == "":
+ return
+ # update the global variables
+ self.username = self.form.le_username.text()
+ FreeCAD.ParamGet(self.host_prefs_location).SetString("Username", self.username )
+ self.profile_changed = True
+
+ def foamDirChanged(self):
+ #TODO: check that the path doesn't end with "/"
+ if self.profile_name == "":
+ return
+ # update the global variables
+ self.foam_dir = self.form.le_foam_dir.text()
+ FreeCAD.ParamGet(self.host_prefs_location).SetString("FoamDir", self.foam_dir )
+ self.profile_changed = True
+ """
+ self.foam_dir = text
+ if self.foam_dir and os.access(self.foam_dir, os.R_OK):
+ #TODO: change this?
+ #self.setDownloadURLs()
+ """
+ def gmshPathChanged(self):
+ #TODO: check that the path doesn't end in "/"
+ if self.profile_name == "":
+ return
+ self.gmsh_path = self.form.le_gmsh_path.text()
+ FreeCAD.ParamGet(self.host_prefs_location).SetString("GmshDir", self.foam_dir )
+ self.profile_changed = True
+
+ def outputPathChanged(self, text):
+ #TODO: check that the path doesn't end in "/"
+ if self.profile_name == "":
+ return
+ self.output_path = self.form.le_output_path.text()
+ FreeCAD.ParamGet(self.host_prefs_location).SetString("OutputPath", self.output_path )
+ self.profile_changed = True
+
+ def addFilenameToOutputChanged(self):
+ #TODO: check that the filename doesn't have a space in it
+ if self.profile_name == "":
+ return
+ self.add_filename_to_output = self.form.cb_add_filename_to_output.isChecked()
+ FreeCAD.ParamGet(self.host_prefs_location).SetBool("AddFilenameToOutput", self.add_filename_to_output)
+ self.profile_changed = True
+
+ def copyBackChanged(self):
+ if self.profile_name == "":
+ return
+ self.copy_back = self.form.cb_copy_back.isChecked()
+ FreeCAD.ParamGet(self.host_prefs_location).SetBool("CopyBack", self.copy_back)
+ self.profile_changed = True
+
+ def deleteRemoteResultsChanged(self):
+ if self.profile_name == "":
+ return
+ self.delete_remote_results = self.form.cb_delete_remote_results.isChecked()
+ FreeCAD.ParamGet(self.host_prefs_location).SetBool("DeleteRemoteResults", self.delete_remote_results)
+ self.profile_changed = True
+
+ #TODO: Move this to CfdTools
+ def pingHost(self):
+ self.consoleMessage("Performing ping test...")
+ if self.hostname == "":
+ self.consoleMessage("Error: missing hostname or IP address.")
+ return
+ result = CfdTools.runCommand("ping",["-c8", self.hostname])
+ #result = runCommand("ssh", ["me@192.168.2.159", "ls", "-al"])
+ #result = runCommand("ssh", ["me@192.168.2.159", "ping", "-c8","www.google.com"])
+ #result = runCommand("ssh", ["me@192.168.2.159", "uname", "-a"])
+ if result == 0:
+ self.consoleMessage("Successfully pinged " + self.hostname + ".") #+ remote_hostname +".")
+ else:
+ self.consoleMessage("Could not ping the remote host. Check network configure and test manually.")
+
+ #TODO: Move this to CfdTools
+ def testSSH(self):
+ self.consoleMessage("Performing ssh test...")
+ if self.hostname == "":
+ self.consoleMessage("Error: missing hostname or IP address.")
+ return
+ if self.username =="":
+ self.consoleMessage("Error: missing username.")
+ return
+
+ connectString = self.form.le_username.text() + "@" + self.hostname
+ #print("ssh connectString:", connectString);
+ result = CfdTools.runCommand("ssh",[connectString,"ls -al"])
+ if result == 0:
+ self.consoleMessage("Successfully ran an ssh session on " + self.hostname + ".")
+ else:
+ self.consoleMessage("Failed to establish an ssh session with the remote host. Manually verify ssh operation with the remote host.")
+
+
+ def __del__(self):
+ self.cleanUp()
+
+
+ def cleanUp(self):
+ if self.thread and self.thread.isRunning():
+ FreeCAD.Console.PrintError("Terminating a pending install task\n")
+ self.thread.quit = True
+ if self.install_process and self.install_process.state() == QtCore.QProcess.Running:
+ FreeCAD.Console.PrintError("Terminating a pending install task\n")
+ self.install_process.terminate()
+ QApplication.restoreOverrideCursor()
+
+ # This gets called from outside the page
+ def saveSettings(self):
+ pass
+ #self.saveProfile() <- this is called in _init_ Doesn't need to be called here.
+ """
+ print("Error: saveSettings has been depreciated.")
+ CfdTools.setRemoteFoamDir(self.foam_dir)
+ CfdTools.setParaviewPath(self.paraview_path)
+ prefs = self.prefs_location
+ FreeCAD.ParamGet(prefs).SetString("RemoteOutputPath", self.remote_output_dir)
+ FreeCAD.ParamGet(prefs).SetBool("UseDocker",self.form.cb_docker_sel.isChecked())
+ FreeCAD.ParamGet(prefs).SetString("DockerURL",self.form.le_docker_url.text())
+ """
+
+ # This gets called form outside the page
+ def loadSettings(self):
+ pass
+ #self.loadProfile(self.profile_name)
+ """
+ print("Error: loadSettings has been depreciated.")
+ # Don't set the autodetected location, since the user might want to allow that to vary according
+ # to WM_PROJECT_DIR setting
+ prefs = self.prefs_location
+ self.foam_dir = FreeCAD.ParamGet(prefs).GetString("RemoteInstallationPath", "")
+ self.initial_remote_foam_dir = str(self.foam_dir)
+ self.form.le_foam_dir.setText(self.foam_dir)
+
+ # Added by me
+ self.remote_output_dir = FreeCAD.ParamGet(prefs).GetString("RemoteOutputPath", "")
+ self.initial_remote_output_dir = str(self.remote_output_dir)
+ self.form.le_output_path.setText(self.remote_output_dir)
+
+ self.paraview_path = CfdTools.getParaviewPath()
+ self.initial_paraview_path = str(self.paraview_path)
+ self.form.le_paraview_path.setText(self.paraview_path)
+
+ self.gmsh_path = CfdTools.getRemoteGmshPath()
+ self.initial_remote_gmsh_path = str(self.gmsh_path)
+ self.form.le_gmsh_path.setText(self.gmsh_path)
+
+ self.output_dir = CfdTools.getDefaultRemoteOutputPath()
+ self.form.le_output_path.setText(self.remote_output_dir)
+
+ # Load the use_remote_processing parameter
+ if FreeCAD.ParamGet(prefs).GetBool("UseRemoteProcessing", 0):
+ self.form.cb_use_remote_processing.setCheckState(Qt.Checked)
+ self.enableControls(True)
+ else:
+ self.enableControls(False)
+
+ #load the hostname
+ self.form.le_hostname.setText(FreeCAD.ParamGet(prefs).GetString("RemoteHostname",""))
+ self.form.le_username.setText(FreeCAD.ParamGet(prefs).GetString("RemoteUsername",""))
+
+ if FreeCAD.ParamGet(prefs).GetBool("UseDocker", 0):
+ self.form.cb_docker_sel.setCheckState(Qt.Checked)
+
+ # Set usedocker and enable/disable download buttons
+ self.dockerCheckboxClicked()
+
+ self.form.le_docker_url.setText(FreeCAD.ParamGet(prefs).GetString("DockerURL", DOCKER_URL))
+
+ self.setDownloadURLs()
+ """
+
+
+ def consoleMessage(self, message="", colour_type=None):
+ message = escape(message)
+ message = message.replace('\n', ' ')
+ if colour_type:
+ self.console_message += '{1} '.format(CfdTools.getColour(colour_type), message)
+ else:
+ self.console_message += message+' '
+ self.form.textEdit_Output.setText(self.console_message)
+ self.form.textEdit_Output.moveCursor(QtGui.QTextCursor.End)
+
+
+ # Not used anymore
+ def testGetRuntime(self, disable_exception=True):
+ print("Error:testGetRuntime has been depreciated.")
+ """ Set the foam dir temporarily and see if we can detect the runtime """
+ CfdTools.setFoamDir(self.foam_dir)
+ try:
+ runtime = CfdTools.getFoamRuntime()
+ except IOError as e:
+ runtime = None
+ if not disable_exception:
+ raise
+ CfdTools.setFoamDir(self.initial_remote_foam_dir)
+ return runtime
+
+
+ def setDownloadURLs(self):
+ print("Error:setDownloadURLs has been depreciated.")
+ if self.testGetRuntime() == "MinGW":
+ # Temporarily apply the foam dir selection
+ CfdTools.setFoamDir(self.foam_dir)
+ foam_ver = os.path.split(CfdTools.getFoamDir())[-1]
+ self.form.le_cfmesh_url.setText(CFMESH_URL_MINGW.format(foam_ver))
+ self.form.le_hisa_url.setText(HISA_URL_MINGW.format(foam_ver))
+ CfdTools.setFoamDir(self.initial_remote_foam_dir)
+ else:
+ self.form.le_cfmesh_url.setText(CFMESH_URL)
+ self.form.le_hisa_url.setText(HISA_URL)
+
+ def paraviewPathChanged(self, text):
+ print("Error:paraviewPathChanged has been depreciated.")
+ self.paraview_path = text
+
+ def chooseFoamDir(self):
+ d = QtGui.QFileDialog().getExistingDirectory(None, 'Choose OpenFOAM directory', self.foam_dir)
+ if d and os.access(d, os.R_OK):
+ self.foam_dir = d
+ self.form.le_foam_dir.setText(self.foam_dir)
+
+ def chooseParaviewPath(self):
+ p, filter = QtGui.QFileDialog().getOpenFileName(None, 'Choose ParaView executable', self.paraview_path,
+ filter="*.exe" if platform.system() == 'Windows' else None)
+ if p and os.access(p, os.R_OK):
+ self.paraview_path = p
+ self.form.le_paraview_path.setText(self.paraview_path)
+
+ def chooseGmshPath(self):
+ p, filter = QtGui.QFileDialog().getOpenFileName(None, 'Choose gmsh executable', self.gmsh_path,
+ filter="*.exe" if platform.system() == 'Windows' else None)
+ if p and os.access(p, os.R_OK):
+ self.gmsh_path = p
+ self.form.le_gmsh_path.setText(self.gmsh_path)
+
+ def chooseOutputDir(self):
+ d = QtGui.QFileDialog().getExistingDirectory(None, 'Choose output directory', self.output_dir)
+ if d and os.access(d, os.W_OK):
+ self.output_dir = os.path.abspath(d)
+ self.form.le_output_path.setText(self.output_dir)
+
+
+ #*******************************************************************************************
+ # This function assumes the remote computer is running Linux or a derivative and a vanilla
+ # OpenFOAM installation. It does not handle a Windows remote computer or Docker at this time.
+ # It also does simple checks of the provided paths. It does not attempt to find executables the
+ # way the non remote dependency checker does
+ # It does not attempt to find ParaView on the remote computer because ParaView is not used as a server in
+ # FreeCAD. Though it could be, some day.
+
+ def checkRemoteCfdDependencies(self):
+
+ CF_MAJOR_VER_REQUIRED = 1
+ CF_MINOR_VER_REQUIRED = 16
+
+ HISA_MAJOR_VER_REQUIRED = 1
+ HISA_MINOR_VER_REQUIRED = 6
+ HISA_PATCH_VER_REQUIRED = 4
+
+ return_message = ""
+ FreeCAD.Console.PrintMessage("Checking remote host CFD dependencies on " + self.hostname + "...\n")
+
+ remote_user = self.username
+ remote_hostname = self.hostname
+ ssh_prefix = "ssh " + remote_user + "@" + remote_hostname + " "
+
+ # check that the remote host is running Linux
+ # right now Linux is the only OS supported on the remote host
+ print("Checking the operating system on the remote host...\n")
+
+ try:
+ os_string = CfdTools.runFoamCommand(ssh_prefix + "uname -o")[0]
+ except:
+ return_message += "Could not determine the OS on " + self.hostname + ".\n"
+ else:
+ if os_string.find("Linux") != 0:
+ return_message += "Remote host OS is " + os_string[:-1] + ".\n"
+
+ else:
+ return_message += "The remote host OS is not Linux.\n"
+ return_message += "CfdOF requires that the remote host be running Linux.\n"
+
+ # check that the open foam directory exists on the remote host
+ print("Checking the OpenFOAM directory...")
+ remote_foam_dir = self.foam_dir
+ foam_ls_command = ssh_prefix + " ls " + remote_foam_dir
+ #print("OpenFOAM dir testCommand:", foam_ls_command)
+
+ try:
+ CfdTools.runFoamCommand(foam_ls_command)
+ except:
+ return_message += "Failed to find the OpenFOAM directory: "+ remote_foam_dir + " on " + self.hostname +".\n"
+ # print the message on the console
+ else:
+ return_message += "Found OpenFOAM directory " + remote_foam_dir + " on " + self.hostname + ".\n"
+
+
+ """
+ try:
+ foam_dir = getRemoteFoamDir()
+ sys_msg = "System: {}\nRuntime: {}\nOpenFOAM directory: {}".format(
+ platform.system(), getFoamRuntime(), foam_dir if len(foam_dir) else "(system installation)")
+ print(sys_msg)
+ message += sys_msg + '\n'
+ except IOError as e:
+ ofmsg = "Could not find OpenFOAM installation: " + str(e)
+ print(ofmsg)
+ message += ofmsg + '\n'
+ else:
+ if foam_dir is None:
+ ofmsg = "OpenFOAM installation path not set and OpenFOAM environment neither pre-loaded before " + \
+ "running FreeCAD nor detected in standard locations"
+ print(ofmsg)
+ message += ofmsg + '\n'
+ else:
+ if getFoamRuntime() == "PosixDocker":
+ startDocker()
+ try:
+ if getFoamRuntime() == "MinGW":
+ foam_ver = runFoamCommand("echo $FOAM_API")[0]
+ else:
+ foam_ver = runFoamCommand("echo $WM_PROJECT_VERSION")[0]
+ """
+
+ # Check if OpenFOAM will run and what its version is
+ FreeCAD.Console.PrintMessage("Checking OpenFOAM operation and version... ")
+
+ try:
+ # TODO: figure out why foamVersion won't run properly in an ssh statement
+ # need sleep in the command so that the OpenFOAM source script gets run in the shell before foamVersion does
+ # help_string = runFoamCommand(ssh_prefix +"sleep 4s ; foamVersion")[0]
+
+ help_string = CfdTools.runFoamCommand(ssh_prefix + "simpleFoam -help")[0]
+ except Exception as e:
+ runmsg = "Unable to run OpenFOAM: " + str(e)
+ return_message += runmsg + '\n'
+ print(runmsg)
+ #raise
+ else:
+ # TODO: this should be put in a try to go with the except below in case
+ # there is an issue with parsing out the OF version
+ # The command $foamVersion will give the version as well
+ version_start = help_string.find("OpenFOAM-") + 9
+ version_string = help_string[version_start:version_start+4]
+ print(version_string)
+ return_message += "Successfully ran OpenFOAM version " + version_string + " on " + self.hostname + ".\n"
+ foam_version = int(version_string)
+
+ """
+ foam_ver = foam_ver.rstrip()
+ if foam_ver:
+ foam_ver = foam_ver.split()[-1]
+ if foam_ver and foam_ver != 'dev' and foam_ver != 'plus':
+ try:
+ # Isolate major version number
+ foam_ver = foam_ver.lstrip('v')
+ foam_ver = int(foam_ver.split('.')[0])
+ if getFoamRuntime() == "MinGW":
+ if foam_ver < 2012 or foam_ver > 2206:
+ vermsg = "OpenFOAM version " + str(foam_ver) + \
+ " is not currently supported with MinGW installation"
+ message += vermsg + "\n"
+ print(vermsg)
+ """
+ if foam_version >= 1000: # Plus version
+ if foam_version < 1706:
+ vermsg = "OpenFOAM version " + str(foam_version) + " is outdated:\n" + \
+ "Minimum version 1706 or 5 required"
+ return_message += vermsg + "\n"
+ print(vermsg)
+ if foam_version > 2206:
+ vermsg = "OpenFOAM version " + str(foam_version) + " is not yet supported:\n" + \
+ "Last tested version is 2206"
+ return_message += vermsg + "\n"
+ print(vermsg)
+ """
+ This code does not support foundation version numbers at this time.
+ TODO: handle foundation version numbers
+ else: # Foundation version
+ if foam_ver < 5:
+ vermsg = "OpenFOAM version " + str(foam_ver) + " is outdated:\n" + \
+ "Minimum version 5 or 1706 required"
+ message += vermsg + "\n"
+ print(vermsg)
+ if foam_ver > 9:
+ vermsg = "OpenFOAM version " + str(foam_ver) + " is not yet supported:\n" + \
+ "Last tested version is 9"
+ message += vermsg + "\n"
+ print(vermsg)
+ except ValueError:
+ vermsg = "Error parsing OpenFOAM version string " + foam_ver
+ message += vermsg + "\n"
+ print(vermsg)
+
+ # Check for wmake
+ # if getFoamRuntime() != "MinGW" and getFoamRuntime() != "PosixDocker":
+ """
+
+ try:
+ # changed this from -help to -version
+ wmake_reply = CfdTools.runFoamCommand(ssh_prefix + "wmake -version")[0]
+
+ except Exception as e:
+ #print(e)
+ wmakemsg = "Cannot run 'wmake' on remote host. \n"
+ wmakemsg += "Installation of cfMesh and HiSA is not be possible without 'wmake'.\n"
+ wmakemsg += "'wmake' is installed with most OpenFOAM development packages.\n"
+ wmakemsg += "You may want to install the OpenFOAM development package on the remote host to provide 'wmake'.\n"
+ return_message += wmakemsg
+ print(wmakemsg)
+
+ else:
+ print(wmake_reply)
+ return_message += "'wmake' is installed on " + self.hostname + ".\n"
+
+ # Check for cfMesh
+ try:
+ cfmesh_ver = CfdTools.runFoamCommand(ssh_prefix + "cartesianMesh -version")[0]
+ version_line = cfmesh_ver.splitlines()[-1]
+ vermsg = version_line[:-1] + " found on " + self.hostname + "."
+ cfmesh_ver = cfmesh_ver.rstrip().split()[-1]
+ cfmesh_ver = cfmesh_ver.split('.')
+ if (not cfmesh_ver or len(cfmesh_ver) != 2 or
+ int(cfmesh_ver[0]) < CF_MAJOR_VER_REQUIRED or
+ (int(cfmesh_ver[0]) == CF_MAJOR_VER_REQUIRED and
+ int(cfmesh_ver[1]) < CF_MINOR_VER_REQUIRED)):
+ vermsg += "cfMesh-CfdOF version {}.{} required".format(CF_MAJOR_VER_REQUIRED, CF_MINOR_VER_REQUIRED)
+
+ except Exception as e: #subprocess.CalledProcessError:
+ vermsg = "cfMesh (CfdOF version) not found on " + self.hostname + "."
+ return_message += vermsg + '\n'
+ print(vermsg)
+
+
+ # Check for HiSA
+ try:
+ hisa_ver = CfdTools.runFoamCommand(ssh_prefix + "hisa -version")[0]
+ hisa_ver = hisa_ver.rstrip().split()[-1]
+ hisa_ver = hisa_ver.split('.')
+ if (not hisa_ver or len(hisa_ver) != 3 or
+ int(hisa_ver[0]) < HISA_MAJOR_VER_REQUIRED or
+ (int(hisa_ver[0]) == HISA_MAJOR_VER_REQUIRED and
+ (int(hisa_ver[1]) < HISA_MINOR_VER_REQUIRED or
+ (int(hisa_ver[1]) == HISA_MINOR_VER_REQUIRED and
+ int(hisa_ver[2]) < HISA_PATCH_VER_REQUIRED)))):
+ vermsg = "HiSA version {}.{}.{} required".format(HISA_MAJOR_VER_REQUIRED,
+ HISA_MINOR_VER_REQUIRED,
+ HISA_PATCH_VER_REQUIRED)
+ return_message += vermsg + "\n"
+ print(vermsg)
+ except Exception as e: #subprocess.CalledProcessError:
+ hisa_msg = "HiSA not found on " + self.hostname + "."
+ return_message += hisa_msg + '\n'
+ print(hisa_msg)
+
+
+ print("Checking for gmsh:")
+ # check that gmsh version 2.13 or greater is installed
+ # Note: ssh runs things in a shell. As long as the environment vars for that shell
+ # are set up properly on the remote computer, one doesn't need to know the gmsh path.
+ # You just call it and the shell $PATH takes care of the rest.
+
+ try:
+ gmsh_reply = CfdTools.runFoamCommand(ssh_prefix + "gmsh -version")[0]
+
+ except Exception as e:
+ #print(e)
+ gmsh_msg = "Cannot run 'gmsh' on " + self.hostname + ". \n"
+ gmsh_msg += "Please install gmsh on the remote host.\n"
+ return_message += gmsh_msg
+ print(gmsh_msg)
+
+ else:
+ # print(gmsh_reply)
+ # print(type(gmsh_reply))
+ return_message += "gmsh version " + gmsh_reply[:-1] + " is installed on " + self.hostname + ".\n"
+ versionlist = gmsh_reply.split(".")
+ if int(versionlist[0]) < 2 or (int(versionlist[0]) == 2 and int(versionlist[1]) < 13):
+ gmsh_ver_msg = "The installed gmsh version is older than minimum required (2.13).\n"
+ gmsh_ver_msg += "Please update gmsh on the remote host."
+ return_message += gmsh_ver_msg + '\n'
+ print(gmsh_ver_msg)
+
+ """
+ gmshversion = ""
+ gmsh_exe = getRemoteGmshExecutable()
+ if gmsh_exe is None:
+ gmsh_msg = "gmsh not found (optional)"
+ return_message += gmsh_msg + '\n'
+ print(gmsh_msg)
+ else:
+ gmsh_msg = "gmsh executable: " + gmsh_exe
+ return_message += gmsh_msg + '\n'
+ print(gmsh_msg)
+ try:
+ # Needs to be runnable from OpenFOAM environment
+ gmshversion = runFoamCommand("'" + gmsh_exe + "'" + " -version")[2]
+ except (OSError, subprocess.CalledProcessError):
+ gmsh_msg = "gmsh could not be run from OpenFOAM environment"
+ return_message += gmsh_msg + '\n'
+ print(gmsh_msg)
+ if len(gmshversion) > 1:
+ # Only the last line contains gmsh version number
+ gmshversion = gmshversion.rstrip().split()
+ gmshversion = gmshversion[-1]
+ versionlist = gmshversion.split(".")
+ if int(versionlist[0]) < 2 or (int(versionlist[0]) == 2 and int(versionlist[1]) < 13):
+ gmsh_ver_msg = "gmsh version is older than minimum required (2.13)"
+ return_message += gmsh_ver_msg + '\n'
+ print(gmsh_ver_msg)
+ """
+
+ print("Completed CFD remote dependency check.")
+ return_message += "Completed remote host dependency check on " + self.hostname + ".\n"
+ return return_message
+
+
+ # This dependency checker runs on the remote host
+ def runRemoteDependencyChecker(self):
+
+ if self.profile_name == "":
+ self.consoleMessage("Error: empty profile")
+ return
+
+ if self.hostname == "":
+ self.consoleMessage("Error: missing hostname or IP address")
+ return
+
+ if self.username == "":
+ self.consoleMessage("Error: missing username")
+ return
+
+ remote_hostname = self.hostname
+
+ # Temporarily apply the foam dir selection and paraview path selection
+ #CfdTools.setRemoteFoamDir(self.foam_dir)
+ #CfdTools.setParaviewPath(self.paraview_path)
+ #CfdTools.setRemoteGmshPath(self.gmsh_path)
+ #QApplication.setOverrideCursor(Qt.WaitCursor)
+
+ self.consoleMessage("Checking dependencies on " + remote_hostname + "...")
+ msg = self.checkRemoteCfdDependencies()
+ self.consoleMessage(msg)
+ #CfdTools.setRemoteFoamDir(self.initial_remote_foam_dir)
+ #CfdTools.setParaviewPath(self.initial_paraview_path)
+ #CfdTools.setRemoteGmshPath(self.initial_remote_gmsh_path)
+ QApplication.restoreOverrideCursor()
+
+
+ # Not used anymore. Kept for reference sake
+ # Some of the vars may have been changed to their remote versions
+ """
+ def runDependencyChecker(self):
+ # Temporarily apply the foam dir selection and paraview path selection
+ CfdTools.setFoamDir(self.foam_dir)
+ CfdTools.setParaviewPath(self.paraview_path)
+ CfdTools.setGmshPath(self.gmsh_path)
+ QApplication.setOverrideCursor(Qt.WaitCursor)
+ self.consoleMessage("Checking dependencies...")
+ msg = CfdTools.checkCfdDependencies()
+ self.consoleMessage(msg)
+ CfdTools.setFoamDir(self.initial_foam_dir)
+ CfdTools.setParaviewPath(self.initial_paraview_path)
+ CfdTools.setGmshPath(self.initial_gmsh_path)
+ QApplication.restoreOverrideCursor()
+ """
+
+
+ def showAdministratorWarningMessage(self):
+ if platform.system() == "Windows":
+ is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
+ if not is_admin:
+ button = QtGui.QMessageBox.question(
+ None,
+ "CfdOF Workbench",
+ "Before installing this software, it is advised to run FreeCAD in administrator mode (hold down "
+ " the 'Shift' key, right-click on the FreeCAD launcher, and choose 'Run as administrator').\n\n"
+ "If this is not possible, please make sure OpenFOAM is installed in a location to which you have "
+ "full read/write access rights.\n\n"
+ "You are not currently running as administrator - do you wish to continue anyway?")
+ return button == QtGui.QMessageBox.StandardButton.Yes
+ return True
+
+ def downloadInstallOpenFoam(self):
+ if not self.showAdministratorWarningMessage():
+ return
+ if self.createThread():
+ self.thread.task = DOWNLOAD_OPENFOAM
+ self.thread.openfoam_url = self.form.le_openfoam_url.text()
+ self.thread.start()
+
+ def pickOpenFoamFile(self):
+ f, filter = QtGui.QFileDialog().getOpenFileName(None, 'Choose OpenFOAM install file', filter="*.exe")
+ if f and os.access(f, os.R_OK):
+ self.form.le_openfoam_url.setText(urlparse.urljoin('file:', urlrequest.pathname2url(f)))
+
+ def downloadInstallParaview(self):
+ if self.createThread():
+ self.thread.task = DOWNLOAD_PARAVIEW
+ self.thread.paraview_url = self.form.le_paraview_url.text()
+ self.thread.start()
+
+ def pickParaviewFile(self):
+ f, filter = QtGui.QFileDialog().getOpenFileName(None, 'Choose ParaView install file', filter="*.exe")
+ if f and os.access(f, os.R_OK):
+ self.form.le_paraview_url.setText(urlparse.urljoin('file:', urlrequest.pathname2url(f)))
+
+
+ #**********************************************************************************************
+ # TODO: Update to use profiles
+ # TODO: Check if cfmesh is already installed before installing in case the user
+ # accidentally pushes the button
+ def remoteDownloadInstallCfMesh(self):
+
+ #TODO: this routine doesn't clean up after itself if it fails
+ # Nor does it check for a full or partial build
+ # If the user reruns the build after fully or partially building previously
+ # this routine will fail because the cfmesh directory is already present.
+
+ # TODO this routine assumes the output dir exists. Will fail if it doesn't. Fix this.
+
+ # TODO: if the remote host is missingzlib-dev, this routine fails silently and then the user will get
+ # an error /usr/bin/ld: checkSurfaceMesh.C:(.text.startup+0xabc): undefined reference to `Foam::triSurf::~triSurf()'
+ # when the solver is run See here: https://forum.freecad.org/viewtopic.php?style=10&t=60010
+ # This appears to be a problem with the local install process as well.
+
+ # Get the username and hostname for the remote host
+ remote_user = self.username
+ remote_hostname = self.hostname
+ ssh_prefix = "ssh -tt " + remote_user + "@" + remote_hostname + " "
+ working_dir = self.output_path
+
+ self.consoleMessage("Installing cfMesh on " + self.hostname + "...")
+
+ # create a dir to download the CfMesh source files to
+ # cfMesh is installed in the user's home directory, not the output directory
+
+ command = "EOT\n"
+ command += "cd " + working_dir + "\n"
+ command += "mkdir cfMesh" + "\n"
+ command += "exit \n"
+ command += "EOT"
+
+ try:
+ command_result = CfdTools.runFoamCommand(ssh_prefix + "<< " + command)[0]
+ working_dir += "/cfMesh"
+
+ print("Created cfMesh install dir: " + working_dir)
+ self.consoleMessage("Created cfMesh install directory " + working_dir)
+ except:
+ self.consoleMessage("Could not create an install directory on " + self.hostname + ".")
+ return
+
+ # download the cfMesh source files
+ command = "EOT\n"
+ command += "cd " + working_dir + "\n"
+ command += "wget " + self.form.le_cfmesh_url.text() + "\n"
+ command += "exit \n"
+ command += "EOT"
+
+ try:
+ command_result = CfdTools.runFoamCommand(ssh_prefix + " << " + command)[0]
+ self.consoleMessage("Downloaded the cfMesh source files")
+ except:
+ self.consoleMessage("Could not download the cfMesh source files.")
+ return
+
+ # unzip the downloaded file
+ # TODO: this assumes the downloaded archive is called "download". Which might not always be the case.
+ # Get name of the downloaded file from the URL and use that filename in this command
+ command = "EOT\n"
+ command += "cd " + working_dir + "\n"
+ command += "unzip download \n"
+ command += "exit \n"
+ command += "EOT"
+
+ try:
+ command_result = CfdTools.runFoamCommand(ssh_prefix + " << " + command)[0]
+ self.consoleMessage("Unzipped cfMesh source files")
+ except:
+ self.consoleMessage("Could not unzip cfMesh source files.")
+ return
+
+ # run the wmake process
+ # TODO: add the number of processes to use on the build with export WM_NCOMPPROCS=`nproc`;
+ # TODO: add logging wiht ./Allwmake -> log.Allwmake
+ # TODO: delete the previous log before starting the new build
+ # Executing: { rm -f log.Allwmake; export WM_NCOMPPROCS=`nproc`; ./Allwmake 1> >(tee -a log.Allwmake) 2> >(tee -a log.Allwmake >&2); } in $WM_PROJECT_USER_DIR/cfmesh-cfdof
+
+ command = "EOT\n"
+ command += "cd " + working_dir + "/cfmesh-cfdof \n"
+ command += "./Allwmake \n"
+ command += "exit \n"
+ command += "EOT"
+
+ self.consoleMessage("Starting cfMesh build on " + self.hostname + ".")
+ self.consoleMessage("This takes a bit of time. Please wait.")
+ try:
+ command_result = CfdTools.runFoamCommand(ssh_prefix + " << " + command)[0]
+ self.consoleMessage("Successfully built and installed cfMesh.")
+ except:
+ self.consoleMessage("Could not build cfMesh: " + command_result)
+ return
+
+
+
+
+
+ # old version, not used anymore. Doesn't handle remote install
+ def downloadInstallCfMesh(self):
+ print("Error:downloadInstallCFMesh has been depreciated.")
+ return
+
+ if not self.showAdministratorWarningMessage():
+ return
+
+ runtime = self.testGetRuntime(False)
+ if runtime == "MinGW" and self.form.le_cfmesh_url.text() == CFMESH_URL:
+ # Openfoam might have just been installed and the URL would not have had a chance to update
+ self.setDownloadURLs()
+
+ if self.createThread():
+ self.thread.task = DOWNLOAD_CFMESH
+ # We are forced to apply the foam dir selection - reset when the task finishes
+ CfdTools.setFoamDir(self.foam_dir)
+ self.thread.cfmesh_url = self.form.le_cfmesh_url.text()
+ self.thread.start()
+
+ def pickCfMeshFile(self):
+ f, filter = QtGui.QFileDialog().getOpenFileName(None, 'Choose cfMesh archive', filter="*.zip")
+ if f and os.access(f, os.R_OK):
+ self.form.le_cfmesh_url.setText(urlparse.urljoin('file:', urlrequest.pathname2url(f)))
+
+ def downloadInstallHisa(self):
+ if not self.showAdministratorWarningMessage():
+ return
+
+ runtime = self.testGetRuntime(False)
+ if runtime == "MinGW" and self.form.le_hisa_url.text() == HISA_URL:
+ # Openfoam might have just been installed and the URL would not have had a chance to update
+ self.setDownloadURLs()
+
+ if self.createThread():
+ self.thread.task = DOWNLOAD_HISA
+ # We are forced to apply the foam dir selection - reset when the task finishes
+ CfdTools.setFoamDir(self.foam_dir)
+ self.thread.hisa_url = self.form.le_hisa_url.text()
+ self.thread.start()
+
+ def pickHisaFile(self):
+ f, filter = QtGui.QFileDialog().getOpenFileName(None, 'Choose HiSA archive', filter="*.zip")
+ if f and os.access(f, os.R_OK):
+ self.form.le_hisa_url.setText(urlparse.urljoin('file:', urlrequest.pathname2url(f)))
+
+
+ def createThread(self):
+ if self.thread and self.thread.isRunning():
+ self.consoleMessage("Busy - please wait...", 'Error')
+ return False
+ else:
+ self.thread = CfdRemotePreferencePageThread()
+ self.thread.signals.error.connect(self.threadError)
+ self.thread.signals.finished.connect(self.threadFinished)
+ self.thread.signals.status.connect(self.threadStatus)
+ self.thread.signals.downloadProgress.connect(self.downloadProgress)
+ return True
+
+ def threadStatus(self, msg):
+ self.consoleMessage(msg)
+
+ def threadError(self, msg):
+ self.consoleMessage(msg, 'Error')
+ self.consoleMessage("Download unsuccessful")
+
+ def threadFinished(self, status):
+ if self.thread.task == DOWNLOAD_CFMESH:
+ if status:
+ if CfdTools.getFoamRuntime() != "MinGW":
+ self.consoleMessage("Download completed")
+ user_dir = self.thread.user_dir
+ self.consoleMessage("Building cfMesh. Lengthy process - please wait...")
+ self.consoleMessage("Log file: {}/{}/log.Allwmake".format(user_dir, CFMESH_FILE_BASE))
+ if CfdTools.getFoamRuntime() == 'WindowsDocker':
+ # There seem to be issues when using multi processors to build in docker
+ self.install_process = CfdTools.startFoamApplication(
+ "export WM_NCOMPPROCS=1; ./Allwmake",
+ "$WM_PROJECT_USER_DIR/"+CFMESH_FILE_BASE,
+ 'log.Allwmake', self.installFinished, stderr_hook=self.stderrFilter)
+ else:
+ self.install_process = CfdTools.startFoamApplication(
+ "export WM_NCOMPPROCS=`nproc`; ./Allwmake",
+ "$WM_PROJECT_USER_DIR/"+CFMESH_FILE_BASE,
+ 'log.Allwmake', self.installFinished, stderr_hook=self.stderrFilter)
+ else:
+ self.consoleMessage("Install completed")
+ # Reset foam dir for now in case the user presses 'Cancel'
+ CfdTools.setFoamDir(self.initial_remote_foam_dir)
+ elif self.thread.task == DOWNLOAD_HISA:
+ if status:
+ if CfdTools.getFoamRuntime() != "MinGW":
+ self.consoleMessage("Download completed")
+ user_dir = self.thread.user_dir
+ self.consoleMessage("Building HiSA. Please wait...")
+ self.consoleMessage("Log file: {}/{}/log.Allwmake".format(user_dir, HISA_FILE_BASE))
+ if CfdTools.getFoamRuntime() == 'WindowsDocker':
+ # There seem to be issues when using multi processors to build in docker
+ self.install_process = CfdTools.startFoamApplication(
+ "export WM_NCOMPPROCS=1; ./Allwmake",
+ "$WM_PROJECT_USER_DIR/"+HISA_FILE_BASE,
+ 'log.Allwmake', self.installFinished, stderr_hook=self.stderrFilter)
+ else:
+ self.install_process = CfdTools.startFoamApplication(
+ "export WM_NCOMPPROCS=`nproc`; ./Allwmake",
+ "$WM_PROJECT_USER_DIR/"+HISA_FILE_BASE,
+ 'log.Allwmake', self.installFinished, stderr_hook=self.stderrFilter)
+ else:
+ self.consoleMessage("Install completed")
+ # Reset foam dir for now in case the user presses 'Cancel'
+ CfdTools.setFoamDir(self.initial_remote_foam_dir)
+ elif self.thread.task == DOWNLOAD_DOCKER:
+ if status:
+ self.consoleMessage("Download completed")
+ else:
+ self.consoleMessage("Download unsuccessful")
+ self.thread = None
+
+ def installFinished(self, exit_code):
+ if exit_code:
+ self.consoleMessage("Install finished with error {}".format(exit_code))
+ else:
+ self.consoleMessage("Install completed")
+
+ def downloadProgress(self, bytes_done, bytes_total):
+ mb_done = float(bytes_done)/(1024*1024)
+ msg = "Downloaded {:.2f} MB".format(mb_done)
+ if bytes_total > 0:
+ msg += " of {:.2f} MB".format(float(bytes_total)/(1024*1024))
+ self.form.labelDownloadProgress.setText(msg)
+
+ def stderrFilter(self, text):
+ # Print to stdout rather than stderr so as not to alarm the user
+ # with the spurious wmkdep errors on stderr
+ print(text, end='')
+ return ''
+
+ def dockerCheckboxClicked(self):
+ if CfdTools.docker_container==None:
+ CfdTools.docker_container = CfdTools.DockerContainer()
+ CfdTools.docker_container.usedocker = self.form.cb_docker_sel.isChecked()
+ self.form.pb_download_install_docker.setEnabled(CfdTools.docker_container.usedocker)
+ self.form.pb_download_install_openfoam.setEnabled(not CfdTools.docker_container.usedocker)
+ #self.form.pb_download_install_hisa.setEnabled((not CfdTools.docker_container.usedocker ))
+ #self.form.pb_download_install_cfMesh.setEnabled((not CfdTools.docker_container.usedocker))
+ self.form.gb_docker.setVisible(CfdTools.docker_container.docker_cmd!=None or CfdTools.docker_container.usedocker)
+
+ def downloadInstallDocker(self):
+ # Set foam dir and output dir in preparation for using docker
+ CfdTools.setFoamDir(self.form.le_foam_dir.text())
+ self.saveSettings()
+ if self.createThread():
+ self.thread.task = DOWNLOAD_DOCKER
+ self.thread.docker_url = self.form.le_docker_url.text()
+ self.thread.start()
+
+class CfdRemotePreferencePageSignals(QObject):
+ error = QtCore.Signal(str) # Signal in PySide, pyqtSignal in PyQt
+ finished = QtCore.Signal(bool)
+ status = QtCore.Signal(str)
+ downloadProgress = QtCore.Signal(int, int)
+
+
+class CfdRemotePreferencePageThread(QThread):
+ """ Worker thread to complete tasks in preference page """
+ def __init__(self):
+ super(CfdRemotePreferencePageThread, self).__init__()
+ self.signals = CfdRemotePreferencePageSignals()
+ self.quit = False
+ self.user_dir = None
+ self.task = None
+ self.openfoam_url = None
+ self.paraview_url = None
+ self.cfmesh_url = None
+ self.hisa_url = None
+ self.docker_url = None
+
+ def run(self):
+ self.quit = False
+
+ try:
+ if self.task == DOWNLOAD_OPENFOAM:
+ self.downloadOpenFoam()
+ elif self.task == DOWNLOAD_PARAVIEW:
+ self.downloadParaview()
+ elif self.task == DOWNLOAD_CFMESH:
+ self.downloadCfMesh()
+ elif self.task == DOWNLOAD_HISA:
+ self.downloadHisa()
+ elif self.task == DOWNLOAD_DOCKER:
+ self.downloadDocker()
+ except Exception as e:
+ if self.quit:
+ self.signals.finished.emit(False) # Exit quietly since UI already destroyed
+ return
+ else:
+ self.signals.error.emit(str(e))
+ self.signals.finished.emit(False)
+ return
+ self.signals.finished.emit(True)
+
+ def downloadFile(self, url, **kwargs):
+ block_size = kwargs.get('block_size', 10*1024)
+ context = kwargs.get('context', None)
+ reporthook = kwargs.get('reporthook', None)
+ suffix = kwargs.get('suffix', '')
+ with closing(urlrequest.urlopen(url, context=context)) as response: # For Python < 3.3 backward compatibility
+ download_len = int(response.info().get('Content-Length', 0))
+ with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp_file:
+ i = 0
+ while True:
+ data = response.read(block_size)
+ if not data:
+ break
+ if self.quit:
+ raise RuntimeError("Premature termination received")
+ tmp_file.write(data)
+ i += 1
+ if reporthook:
+ reporthook(i, block_size, download_len)
+ filename = tmp_file.name
+ return filename, response.info()
+
+ def download(self, url, suffix, name):
+ self.signals.status.emit("Downloading {}, please wait...".format(name))
+ try:
+ if hasattr(ssl, 'create_default_context'):
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
+ else:
+ context = None
+ # Download
+ (filename, header) = self.downloadFile(
+ url, suffix=suffix, reporthook=self.downloadStatus, context=context)
+ except Exception as ex:
+ raise Exception("Error downloading {}: {}".format(name, str(ex)))
+
+ self.signals.status.emit("{} downloaded to {}".format(name, filename))
+ return filename
+
+ def downloadOpenFoam(self):
+ filename = self.download(self.openfoam_url, OPENFOAM_FILE_EXT, "OpenFOAM")
+ if QtCore.QProcess().startDetached(filename):
+ self.signals.status.emit("OpenFOAM installer launched - please complete the installation")
+ else:
+ raise Exception("Failed to launch OpenFOAM installer")
+
+ def downloadParaview(self):
+ filename = self.download(self.paraview_url, PARAVIEW_FILE_EXT, "ParaView")
+ if QtCore.QProcess().startDetached(filename):
+ self.signals.status.emit("ParaView installer launched - please complete the installation")
+ else:
+ raise Exception("Failed to launch ParaView installer")
+
+ def downloadCfMesh(self):
+ filename = self.download(self.cfmesh_url, CFMESH_FILE_EXT, "cfMesh")
+
+ if CfdTools.getFoamRuntime() == "MinGW":
+ self.user_dir = None
+ self.signals.status.emit("Installing cfMesh...")
+ CfdTools.runFoamCommand(
+ '{{ mkdir -p "$FOAM_APPBIN" && cd "$FOAM_APPBIN" && unzip -o "{}"; }}'.
+ format(CfdTools.translatePath(filename)))
+ else:
+ self.user_dir = CfdTools.runFoamCommand("echo $WM_PROJECT_USER_DIR")[0].rstrip().split('\n')[-1]
+ # We can't reverse-translate the path for docker since it sits inside the container. Just report it as such.
+ if CfdTools.getFoamRuntime() != 'WindowsDocker':
+ self.user_dir = CfdTools.reverseTranslatePath(self.user_dir)
+
+ self.signals.status.emit("Extracting cfMesh...")
+ if CfdTools.getFoamRuntime() == 'WindowsDocker':
+ from zipfile import ZipFile
+ with ZipFile(filename, 'r') as zip:
+ with tempfile.TemporaryDirectory() as tempdir:
+ zip.extractall(path=tempdir)
+ CfdTools.runFoamCommand(
+ '{{ mkdir -p "$WM_PROJECT_USER_DIR" && cp -r "{}" "$WM_PROJECT_USER_DIR/"; }}'
+ .format(CfdTools.translatePath(os.path.join(tempdir, CFMESH_FILE_BASE))))
+ else:
+ CfdTools.runFoamCommand(
+ '{{ mkdir -p "$WM_PROJECT_USER_DIR" && cd "$WM_PROJECT_USER_DIR" && ( rm -r {}; unzip -o "{}"; ); }}'.
+ format(CFMESH_FILE_BASE, CfdTools.translatePath(filename)))
+
+ def downloadHisa(self):
+ filename = self.download(self.hisa_url, HISA_FILE_EXT, "HiSA")
+
+ if CfdTools.getFoamRuntime() == "MinGW":
+ self.user_dir = None
+ self.signals.status.emit("Installing HiSA...")
+ CfdTools.runFoamCommand(
+ '{{ mkdir -p "$FOAM_APPBIN" && cd "$FOAM_APPBIN" && unzip -o "{}"; }}'.
+ format(CfdTools.translatePath(filename)))
+ else:
+ self.user_dir = CfdTools.runFoamCommand("echo $WM_PROJECT_USER_DIR")[0].rstrip().split('\n')[-1]
+ # We can't reverse-translate the path for docker since it sits inside the container. Just report it as such.
+ if CfdTools.getFoamRuntime() != 'WindowsDocker':
+ self.user_dir = CfdTools.reverseTranslatePath(self.user_dir)
+
+ self.signals.status.emit("Extracting HiSA...")
+ if CfdTools.getFoamRuntime() == 'WindowsDocker':
+ from zipfile import ZipFile
+ with ZipFile(filename, 'r') as zip:
+ with tempfile.TemporaryDirectory() as tempdir:
+ zip.extractall(path=tempdir)
+ CfdTools.runFoamCommand(
+ '{{ mkdir -p "$WM_PROJECT_USER_DIR" && cp -r "{}" "$WM_PROJECT_USER_DIR/"; }}'
+ .format(CfdTools.translatePath(os.path.join(tempdir, HISA_FILE_BASE))))
+ else:
+ CfdTools.runFoamCommand(
+ '{{ mkdir -p "$WM_PROJECT_USER_DIR" && cd "$WM_PROJECT_USER_DIR" && ( rm -r {}; unzip -o "{}"; ); }}'.
+ format(HISA_FILE_BASE, CfdTools.translatePath(filename)))
+
+ def downloadDocker(self):
+ self.signals.status.emit("Downloading Docker image, please wait until 'Download completed' message shown below")
+ if CfdTools.docker_container.container_id!=None:
+ CfdTools.docker_container.stop_container()
+ cmd = '{} pull {}'.format(CfdTools.docker_container.docker_cmd, self.docker_url)
+ if 'docker'in CfdTools.docker_container.docker_cmd:
+ cmd = cmd.replace('docker.io/','')
+
+ CfdTools.runFoamCommand(cmd)
+
+ def downloadStatus(self, blocks, block_size, total_size):
+ self.signals.downloadProgress.emit(blocks*block_size, total_size)
diff --git a/CfdOF/CfdTools.py b/CfdOF/CfdTools.py
index 36e2e1fe..8f498796 100644
--- a/CfdOF/CfdTools.py
+++ b/CfdOF/CfdTools.py
@@ -40,12 +40,18 @@
from datetime import timedelta
import FreeCAD
from FreeCAD import Units
+#import App
import Part
import BOPTools
from BOPTools import SplitFeatures
from CfdOF.CfdConsoleProcess import CfdConsoleProcess
from CfdOF.CfdConsoleProcess import removeAppimageEnvironment
from PySide import QtCore
+
+# added for runCommand
+from PySide.QtCore import QProcess # I added
+from PySide.QtGui import QApplication # I added
+
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtGui
@@ -83,15 +89,75 @@
docker_container = None
-def getDefaultOutputPath():
+
+# Modified by LinuxGuy to handle add_filename_to_path
+# This is called default because it is used in the mesh and solver objects
+# but can be over ridden by the user
+# WARNING: this routine returns the path with the filename add if enabled in Preferences
+def getDefaultOutputPath(profile = 'local'):
+
+ # host is local
+ if profile == 'local':
+ prefs = getPreferencesLocation()
+ output_path = FreeCAD.ParamGet(prefs).GetString("DefaultOutputPath", "")
+ if not output_path:
+ output_path = tempfile.gettempdir()
+ print("Root output path:" + output_path)
+
+ add_filename = FreeCAD.ParamGet(prefs).GetBool("AddFilenameToOutput")
+ #print("AddFilenameToOutput:" + str(add_filename))
+
+ if add_filename:
+ #TODO: might want to warn the user if they haven't saved the filename yet
+ App = FreeCAD
+ filename = App.ActiveDocument.Name
+ if filename:
+ output_path += "/" + filename
+
+ output_path = os.path.normpath(output_path)
+ print("Output path:" + output_path)
+ return output_path
+
+ #remote case
+ else:
+ prefs = getPreferencesLocation()
+ hostprefs = prefs +"/Hosts/" + profile
+ output_path = FreeCAD.ParamGet(hostprefs).GetString("OutputPath", "")
+
+ if not output_path:
+ #TODO: might want to warn the user if they haven't saved the filename yet
+ output_path = tempfile.gettempdir()
+
+ if FreeCAD.ParamGet(hostprefs).GetBool("AddFilenameToOutput"):
+ App = FreeCAD
+ filename = App.ActiveDocument.Name
+ if filename:
+ output_path += "/" + filename
+ # the local computer might be Windows and the remote one Linux
+ # so don't normalize the path to the local OS
+ #output_path = os.path.normpath(output_path)
+ print("Output path:" + output_path)
+ return output_path
+
+
+# TODO: Is this used anymore ?
+def getDefaultRemoteOutputPath():
+ print("Error: getDefaultRemoteOutputPath is depreciated.")
prefs = getPreferencesLocation()
- output_path = FreeCAD.ParamGet(prefs).GetString("DefaultOutputPath", "")
+ output_path = FreeCAD.ParamGet(prefs).GetString("DefaultRemoteOutputPath", "")
if not output_path:
- output_path = tempfile.gettempdir()
- output_path = os.path.normpath(output_path)
+ #TODO: fix this so it will run on the server
+ #should be /home/username from the reference page
+ # output_path = tempfile.gettempdir()
+ output_path = ""
+ # assume the remote computer is Linux and
+ # don't adjust the output path
+ # output_path = os.path.normpath(output_path)
return output_path
+# This used to be called by CfdCaseWriterFoam, but isn't anymore.
+# Not sure what uses it
def getOutputPath(analysis):
if analysis and 'OutputPath' in analysis.PropertiesList:
output_path = analysis.OutputPath
@@ -108,6 +174,20 @@ def getOutputPath(analysis):
return output_path
+# TODO Is this used anymore ?
+def getRemoteOutputPath(analysis):
+ if analysis and 'RemoteOutputPath' in analysis.PropertiesList:
+ output_path = analysis.RemoteOutputPath
+ else:
+ output_path = ""
+ if not output_path:
+ output_path = getDefaultRemoteOutputPath()
+ # Assume the remote computer is Linux and don't
+ # adjust the path
+ # output_path = os.path.normpath(output_path)
+ return output_path
+
+
# Get functions
if FreeCAD.GuiUp:
def getResultObject():
@@ -478,6 +558,20 @@ def setFoamDir(installation_path):
# Set OpenFOAM install path in parameters
FreeCAD.ParamGet(prefs).SetString("InstallationPath", installation_path)
+#not used anymore
+def setRemoteFoamDir(remote_installation_path):
+ print("Error: setRemoteFoamDir is depreciated.")
+ prefs = getPreferencesLocation()
+ # Set OpenFOAM remote install path in parameters
+ FreeCAD.ParamGet(prefs).SetString("RemoteInstallationPath", remote_installation_path)
+
+# not used anymore
+def getRemoteFoamDir():
+ print("Error: getRemoteFoamDir is depreciated.")
+ prefs = getPreferencesLocation()
+ # Set OpenFOAM remote install path in parameters
+ return FreeCAD.ParamGet(prefs).GetString("RemoteInstallationPath", "")
+
def startDocker():
global docker_container
if docker_container==None:
@@ -640,7 +734,6 @@ def setParaviewPath(paraview_path):
# Set Paraview install path in parameters
FreeCAD.ParamGet(prefs).SetString("ParaviewPath", paraview_path)
-
def getParaviewPath():
prefs = getPreferencesLocation()
# Get path from parameters
@@ -655,6 +748,12 @@ def setGmshPath(gmsh_path):
# Set Paraview install path in parameters
FreeCAD.ParamGet(prefs).SetString("GmshPath", gmsh_path)
+# not used anymore
+def setRemoteGmshPath(gmsh_path):
+ print("Error: setRemoteGmshPath is depreciated.")
+ prefs = getPreferencesLocation()
+ # Set Paraview install path in parameters
+ FreeCAD.ParamGet(prefs).SetString("RemoteGmshPath", gmsh_path)
def getGmshPath():
prefs = getPreferencesLocation()
@@ -664,6 +763,18 @@ def getGmshPath():
setGmshPath(gmsh_path)
return gmsh_path
+# not used anymore
+def getRemoteGmshPath():
+ print("Error: getRemoteGmshPath is depreciated.")
+ prefs = getPreferencesLocation()
+ # Get path from parameters
+ gmsh_path = FreeCAD.ParamGet(prefs).GetString("RemoteGmshPath", "")
+ # Ensure parameters exist for future editing
+ setGmshPath(gmsh_path)
+ return gmsh_path
+
+
+
def translatePath(p):
"""
@@ -796,7 +907,13 @@ def getRunEnvironment():
else:
return {}
-
+# This routine is used by both the remote meshing process and the remote solver process
+# to convert the remote command into a command that will run with CfDConsole process
+# However, it adds a bash call that it doesn't need to and it sources the OpenFOAM init file
+# on the local computer, which it doesn't have to. When a remote host is used, it is assumed
+# that OpenFOAM will run from the command line. This means its init file is run in bashrc file.
+# This is probably a dangerous assumption and this routine should be changed to work with remote
+# hosts and source the OpenFOAM init file.
def makeRunCommand(cmd, dir, source_env=True):
"""
Generate native command to run the specified Linux command in the relevant environment,
@@ -913,7 +1030,6 @@ def makeRunCommand(cmd, dir, source_env=True):
cmdline = ['bash', '-c', source + cd + cmd]
return cmdline
-
def runFoamCommand(cmdline, case=None):
"""
Run a command in the OpenFOAM environment and wait until finished. Return output as (stdout, stderr, combined)
@@ -930,6 +1046,7 @@ def runFoamCommand(cmdline, case=None):
return proc.output, proc.outputErr, proc.outputAll
+
def startFoamApplication(cmd, case, log_name='', finished_hook=None, stdout_hook=None, stderr_hook=None):
"""
Run command cmd in OpenFOAM environment, sending output to log file.
@@ -1230,6 +1347,71 @@ def checkCfdDependencies(msgFn):
msgFn("Completed CFD dependency check")
+#******************************************************************************
+# Done: TODO: right now this routine returns the error code of the outermost process, ie the one called with cmd parameter.
+# So if you call ssh, for example, and it runs successfully, it will return no error, no matter what happens to the command
+# that was used in ssh. This should be changed. If one gets an error message via stderr, this routine should
+# return an error code, regardless of how ssh itself ran.
+#
+# TODO: add a timer so that the process times out if it doesn't create any output or do anything.
+# If ssh credentials aren't set up properly on the remote host, it will ask for a password and not echo that to stdout or stderr.
+# Thus the process will be stuck.
+#
+# This routine is not needed. runFoamCommand does the same thing but is integrated into CfdTools already.
+# The only routines that use runCommand are ping and ssh test in RemotePreferences.py. Those routines should be
+# rewritten with runFoamCommand and put in CfdTools.
+#
+# When that happens, this routine can be deleted.
+
+def runCommand(cmd, args=[]):
+
+ process = QProcess()
+ returnValue = 0
+
+ def handleStdout():
+ #print("In Stdout handler")
+ data = process.readAllStandardOutput()
+ message = bytes(data).decode("utf8")
+ print(message)
+
+ def handleStderr():
+ nonlocal returnValue
+ #print("In Stderr handler")
+ data = process.readAllStandardError()
+ message = bytes(data).decode("utf8")
+ print("Error:" + message)
+ returnValue = -1
+
+ def handleFinished(self, exitCode):
+ nonlocal returnValue
+ print("Command done.")
+ if ((exitCode == QProcess.NormalExit) and (returnValue == 0)):
+ returnValue = 0
+ else:
+ returnValue = -1
+
+ # connect the events to handlers
+ process.readyReadStandardOutput.connect(handleStdout)
+ process.readyReadStandardError.connect(handleStderr)
+ process.finished.connect(handleFinished)
+
+ # debug
+ message = "Remotely executing: " + cmd
+ for arg in args:
+ message = message + " " + arg
+ print(message)
+
+ # start the command process
+ process.start(cmd, args)
+
+ # wait for the process to get started
+ process.waitForStarted()
+
+ # process application events while waiting for it to finish
+ while(process.state() == QProcess.Running):
+ QApplication.processEvents()
+ return(returnValue)
+
def getParaviewExecutable():
# If path of paraview executable specified, use that
@@ -1264,6 +1446,22 @@ def getGmshExecutable():
gmsh_cmd='gmsh'
return gmsh_cmd
+def getRemoteGmshExecutable():
+ # If path of gmsh executable specified, use that
+ gmsh_cmd = getGmshPath()
+ if not gmsh_cmd:
+ # On Windows, use gmsh supplied
+ if platform.system() == "Windows":
+ # Use forward slashes to avoid escaping problems
+ gmsh_cmd = '/'.join([FreeCAD.getHomePath().rstrip('/'), 'bin', 'gmsh.exe'])
+ if not gmsh_cmd:
+ # Otherwise, see if the command 'gmsh' is in the path.
+ gmsh_cmd = shutil.which("gmsh")
+ if getFoamRuntime() == "PosixDocker":
+ gmsh_cmd='gmsh'
+ return gmsh_cmd
+
+
def startParaview(case_path, script_name, console_message_fn):
proc = QtCore.QProcess()
@@ -1730,6 +1928,11 @@ def run(self, cmdline, case=None):
if startDocker():
return 1
print("Running ", cmdline)
+
+ mycmdline = makeRunCommand(cmdline,case)
+ print("cmdline in CfdSynFoamProcess:")
+ print(mycmdline)
+
self.process.start(makeRunCommand(cmdline, case), env_vars=getRunEnvironment())
if not self.process.waitForFinished():
raise Exception("Unable to run command " + cmdline)
@@ -1743,6 +1946,8 @@ def readError(self, output):
self.outputErr += output
self.outputAll += output
+
+
# Only one container is needed. Start one for the CfdOF workbench as required
class DockerContainer:
container_id = None
diff --git a/CfdOF/Mesh/CfdMesh.py b/CfdOF/Mesh/CfdMesh.py
index 253da724..335f448d 100644
--- a/CfdOF/Mesh/CfdMesh.py
+++ b/CfdOF/Mesh/CfdMesh.py
@@ -102,12 +102,24 @@ def initProperties(self, obj):
"Mesh elements per 360 degrees for surface triangulation with GMSH")
addObjectProperty(obj, 'NumberOfProcesses', 1, "App::PropertyInteger", "",
- "Number of parallel processes (only applicable to cfMesh and snappyHexMesh)")
+ "Number of parallel processes on local computer (only applicable to cfMesh and snappyHexMesh)")
addObjectProperty(obj, 'NumberOfThreads', 0, "App::PropertyInteger", "",
- "Number of parallel threads per process (only applicable to cfMesh and gmsh). "
+ "Number of parallel threads per process on local computer (only applicable to cfMesh and gmsh). "
"0 means use all available (if NumberOfProcesses = 1) or use 1 (if NumberOfProcesses > 1)")
+
+ # I added the remote processes and threads
+ """
+ addObjectProperty(obj, 'NumberOfRemoteProcesses', 1, "App::PropertyInteger", "",
+ "Number of parallel processes on remote computer (only applicable to cfMesh and snappyHexMesh)")
+
+ addObjectProperty(obj, 'NumberOfRemoteThreadsPerProcess', 0, "App::PropertyInteger", "",
+ "Number of parallel threads per process on remote computer (only applicable to cfMesh and gmsh). "
+ "0 means use all available (if NumberOfProcesses = 1) or use 1 (if NumberOfProcesses > 1)")
+ """
+
+
addObjectProperty(obj, "Part", None, "App::PropertyLinkGlobal", "Mesh Parameters", "Part object to mesh")
if addObjectProperty(obj, "MeshUtility", MESHERS, "App::PropertyEnumeration",
diff --git a/CfdOF/Mesh/CfdMeshTools.py b/CfdOF/Mesh/CfdMeshTools.py
index 2a1a56b7..9bf987b7 100644
--- a/CfdOF/Mesh/CfdMeshTools.py
+++ b/CfdOF/Mesh/CfdMeshTools.py
@@ -70,7 +70,7 @@ def __init__(self, cart_mesh_obj):
self.progressCallback = None
- def writeMesh(self):
+ def writeMesh(self, profile_name):
self.setupMeshCaseDir()
CfdTools.cfdMessage("Exporting mesh refinement data ...\n")
if self.progressCallback:
@@ -81,10 +81,11 @@ def writeMesh(self):
if self.progressCallback:
self.progressCallback("Exporting the part surfaces ...")
self.writePartFile()
- self.writeMeshCase()
+ self.writeMeshCase(profile_name)
CfdTools.cfdMessage("Wrote mesh case to {}\n".format(self.meshCaseDir))
if self.progressCallback:
- self.progressCallback("Mesh case written successfully")
+ self.progressCallback("Mesh case written successfully")
+
def processExtrusions(self):
""" Find and process any extrusion objects """
@@ -586,7 +587,7 @@ def loadSurfMesh(self):
else:
print('No mesh was created.')
- def writeMeshCase(self):
+ def writeMeshCase(self, host_profile):
""" Collect case settings, and finally build a runnable case. """
CfdTools.cfdMessage("Populating mesh dictionaries in folder {}\n".format(self.meshCaseDir))
@@ -711,14 +712,20 @@ def writeMeshCase(self):
if CfdTools.getFoamRuntime() != 'WindowsDocker':
self.settings['TranslatedFoamPath'] = CfdTools.translatePath(CfdTools.getFoamDir())
+ # set the number of threads and processes
+ # these were set appropriately when the host was selected
if self.mesh_obj.NumberOfProcesses <= 1:
- self.settings['ParallelMesh'] = False
- self.settings['NumberOfProcesses'] = 1
+ self.settings['ParallelMesh'] = False
+ self.settings['NumberOfProcesses'] = 1
else:
- self.settings['ParallelMesh'] = True
- self.settings['NumberOfProcesses'] = self.mesh_obj.NumberOfProcesses
+ self.settings['ParallelMesh'] = True
+ self.settings['NumberOfProcesses'] = self.mesh_obj.NumberOfProcesses
+
self.settings['NumberOfThreads'] = self.mesh_obj.NumberOfThreads
+ if self.progressCallback:
+ self.progressCallback("Mesh case will use " + str(self.settings['NumberOfProcesses']) + " processes and " + str(self.settings['NumberOfThreads']) + " threads per process.")
+ self.progressCallback("0 threads per process means use all available (if NumberOfProcesses = 1) or use 1 per process (if NumberOfProcesses > 1)")
TemplateBuilder(self.meshCaseDir, self.template_path, self.settings)
# Update Allmesh permission - will fail silently on Windows
@@ -728,7 +735,41 @@ def writeMeshCase(self):
os.chmod(fname, s.st_mode | stat.S_IEXEC)
self.analysis.NeedsMeshRewrite = False
- CfdTools.cfdMessage("Successfully wrote meshCase to folder {}\n".format(self.meshCaseDir))
+ CfdTools.cfdMessage("Wrote meshCase to local folder {}\n".format(self.meshCaseDir))
+ if self.progressCallback:
+ self.progressCallback("Wrote meshCase to local folder {}\n".format(self.meshCaseDir))
+
+ #if this is a remote mesh, copy the mesh case folder from the local mesh case dir
+ # to the remote host's directory
+ if host_profile != "local":
+ #this code is also used in reverse in TaskPanelCfdMesh.py for copyback after the meshing is done
+ # TODO: make it a function ?
+ profile_prefs = CfdTools.getPreferencesLocation() +"/Hosts/" + host_profile
+ remote_user = FreeCAD.ParamGet(profile_prefs).GetString("Username", "")
+ remote_hostname = FreeCAD.ParamGet(profile_prefs).GetString("Hostname", "")
+ #remote_output_path = FreeCAD.ParamGet(profile_prefs).GetString("OutputPath","")
+ remote_output_path = CfdTools.getDefaultOutputPath(host_profile)
+
+ # rsync the meshCase directory to the remote host's output directory
+ # Typical useage: rsync -r --remove-source-files --delete /tmp/meshCase me@david:/tmp
+ # --remove-source-files removes the files that get transfered
+ # --delete removes files from the destination that didn't get transfered
+ # not using --remove-source-files because the workstation uses it to determine what actions are
+ # appropriate to do on the remote host, ie has a meshCase been written.
+ try:
+ CfdTools.runFoamCommand("rsync -r --delete --remove-source-files " + self.meshCaseDir + " " + remote_user + "@" + remote_hostname + \
+ ":" + remote_output_path)
+ except Exception as e:
+ CfdTools.cfdMessage("Could not move mesh case to remote host: " + str(e))
+ if self.progressCallback:
+ self.progressCallback("Could not move mesh case to remote host: " + str(e))
+ else:
+ CfdTools.cfdMessage("Moved mesh case to " + remote_hostname + ":" + remote_output_path + "\n" )
+ if self.progressCallback:
+ self.progressCallback("Moved mesh case to " + remote_hostname + ":" + remote_output_path + "\n")
+ if self.progressCallback:
+ self.progressCallback("Mesh case write process is complete.")
+
def writeSurfaceMeshFromShape(shape, path, name, mesh_obj):
prefs = CfdTools.getPreferencesLocation()
diff --git a/CfdOF/Mesh/TaskPanelCfdMesh.py b/CfdOF/Mesh/TaskPanelCfdMesh.py
index 4acfde29..9717b224 100644
--- a/CfdOF/Mesh/TaskPanelCfdMesh.py
+++ b/CfdOF/Mesh/TaskPanelCfdMesh.py
@@ -21,6 +21,49 @@
# * see . *
# * *
# ***************************************************************************
+#
+# LinuxGuy123@gmail.com's notes:
+#
+# TODOs, in addition to TODOs in the code itself
+#
+# -This code uses (dangerous) global vars to access things like profile_name, hostname, output dir, etc.
+# The profile_name, hostname, output dir should be attached to the mesh object and used from it.
+#
+# -the ParaView button gets enabled as soon as the mesh case is written before the results are
+# available. This should be fixed. See updateUI.
+#
+# -the Clear Surface mesh button is enabled even when the surface isn't loaded. This should be fixed in
+# updateUI.
+#
+# -the UI doesn't know where a meshCase is available to run. You can write a mesh case to the local, then
+# change to a host and Run Mesh will be available to use. But there is no meshCase on that host yet. Ideally
+# the mesh object would retain the host that the mesh case is on so that it could properly enable various actions. Luckily
+# run mesh fails gracefully if no mesh case is found.
+#
+# -delete_remote_results removes the meshCase after a successful solve. Actually, I disabled this. Thus you (would) have to rewrite
+# the mesh case and rerun it on the server after every successful solve, if delete_remote_results is selected.
+#
+# -edit mesh should copy the edited mesh to the server after the edit is done. Right now it doesn't.
+#
+# -remote meshing has not been tested in macros.
+#
+# -cfMesh runs only 1 mesh process on the remote machine if 1,0 is selected for processes, threads. If you run 8 threads (1,8) for example, it
+# doesn't ever finish the meshing process stack on the remote host is this. Looks legit, but never finishes.
+#
+# $ ps aux | grep Mesh
+#me 134162 0.0 0.0 174292 17336 ? Sl 21:58 0:00 mpiexec -np 8 /home/me/OpenFOAM/me-2206/platforms/linux64GccDPInt32Opt/bin/cartesianMesh -parallel
+#me 134163 0.0 0.0 221328 944 ? S 21:58 0:00 tee -a log.cartesianMesh
+#me 134164 0.0 0.0 221328 892 ? S 21:58 0:00 tee -a log.cartesianMesh
+#me 134168 102 0.3 4034732 237604 ? Rl 21:58 5:22 /home/me/OpenFOAM/me-2206/platforms/linux64GccDPInt32Opt/bin/cartesianMesh -parallel
+#me 134169 102 0.3 4038380 231280 ? Rl 21:58 5:22 /home/me/OpenFOAM/me-2206/platforms/linux64GccDPInt32Opt/bin/cartesianMesh -parallel
+#me 134170 102 0.3 4029584 221532 ? Rl 21:58 5:22 /home/me/OpenFOAM/me-2206/platforms/linux64GccDPInt32Opt/bin/cartesianMesh -parallel
+#me 134171 102 0.3 4063928 258864 ? Rl 21:58 5:22 /home/me/OpenFOAM/me-2206/platforms/linux64GccDPInt32Opt/bin/cartesianMesh -parallel
+#me 134172 102 0.3 4035376 234968 ? Rl 21:58 5:22 /home/me/OpenFOAM/me-2206/platforms/linux64GccDPInt32Opt/bin/cartesianMesh -parallel
+#me 134173 102 0.3 4032120 233276 ? Rl 21:58 5:22 /home/me/OpenFOAM/me-2206/platforms/linux64GccDPInt32Opt/bin/cartesianMesh -parallel
+#me 134174 102 0.3 4030184 221800 ? Rl 21:58 5:22 /home/me/OpenFOAM/me-2206/platforms/linux64GccDPInt32Opt/bin/cartesianMesh -parallel
+#me 134175 102 0.3 4066644 257484 ? Rl 21:58 5:22 /home/me/OpenFOAM/me-2206/platforms/linux64GccDPInt32Opt/bin/cartesianMesh -parallel
+
+
from __future__ import print_function
import FreeCAD
@@ -36,7 +79,6 @@
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore
- from PySide import QtCore
from PySide import QtGui
from PySide.QtCore import Qt
from PySide.QtGui import QApplication
@@ -58,20 +100,65 @@ def __init__(self, obj):
stdout_hook=self.gotOutputLines,
stderr_hook=self.gotErrorLines)
+ #set the prefs and host prefs locations
+ self.prefs_location = CfdTools.getPreferencesLocation()
+ self.host_prefs_location = self.prefs_location + "/Hosts"
+ self.useRemoteProcessing = FreeCAD.ParamGet(self.prefs_location).GetBool('UseRemoteProcessing', 0)
+
+ #setting these here so they get created as globals
+ #they also get initiated in loadProfile()
+ # self.use_remote_processing = False <- this is set above before the control is loaded
+
+ self.profile_name = ""
+ self.hostname = ""
+ self.username = ""
+ self.mesh_processes = 0
+ self.mesh_threads = 0
+ self.foam_processes = 0
+ self.foam_threads = 0
+ self.foam_dir = ""
+ self.output_path = ""
+ self.gmsh_path = ""
+ self.add_filename_to_output = False
+ self.copy_back = False
+ self.delete_remote_results = False
+
+ #add a local host to cb_profile
+ self.form.cb_profile.addItem("local")
+
+ # if using remote processing, add the host profiles as well
+ if self.useRemoteProcessing:
+ self.loadProfileNames()
+ else:
+ #disable cb_profile so that users aren't trying to change the host
+ self.form.cb_profile.setEnabled(False)
+
+ # load the local profile
+ self.loadProfile("local")
+
self.Timer = QtCore.QTimer()
self.Timer.setInterval(1000)
self.Timer.timeout.connect(self.update_timer_text)
+ # set up the profiles combo box connection
+ self.form.cb_profile.currentIndexChanged.connect(self.profileChanged)
+
self.form.cb_utility.activated.connect(self.choose_utility)
+
self.form.pb_write_mesh.clicked.connect(self.writeMesh)
+
self.form.pb_edit_mesh.clicked.connect(self.editMesh)
+
self.form.pb_run_mesh.clicked.connect(self.runMesh)
+
self.form.pb_stop_mesh.clicked.connect(self.killMeshProcess)
self.form.pb_paraview.clicked.connect(self.openParaview)
self.form.pb_load_mesh.clicked.connect(self.pbLoadMeshClicked)
self.form.pb_clear_mesh.clicked.connect(self.pbClearMeshClicked)
self.form.pb_searchPointInMesh.clicked.connect(self.searchPointInMesh)
self.form.pb_check_mesh.clicked.connect(self.checkMeshClicked)
+ self.form.pb_copy_to_host.clicked.connect(self.copyMeshcaseToHost)
+ self.form.pb_delete_mesh.clicked.connect(self.deleteMeshcase)
self.radioGroup = QtGui.QButtonGroup()
self.radioGroup.addButton(self.form.radio_explicit_edge_detection)
@@ -97,6 +184,152 @@ def __init__(self, obj):
self.Start = time.time()
self.Timer.start()
+ # loads the profiles names into the profile combo box
+ def loadProfileNames(self):
+ profileDir = self.prefs_location + "/Hosts"
+ profiles = FreeCAD.ParamGet(profileDir)
+ profileList = profiles.GetGroups()
+ for item in profileList:
+ self.form.cb_profile.addItem(item)
+
+
+ # load profile parameters into the controls and local vars
+ def loadProfile(self, profile_name):
+
+ #set the global profile name
+ self.profile_name = profile_name
+
+ #set the global host prefs location
+ self.host_prefs_location = self.prefs_location + "/Hosts/" + profile_name
+
+ #set the other global vars
+ if profile_name == "":
+ print("Error: no host profile selected")
+ return
+
+ # set the vars to the local parameters
+ if profile_name == "local":
+ self.hostname = "local"
+ # enable controls for the local host
+ self.form.pb_edit_mesh.setEnabled(True)
+ self.form.pb_paraview.setEnabled(True)
+ self.form.pb_check_mesh.setEnabled(True)
+ self.form.pb_load_mesh.setEnabled(True)
+ self.form.pb_clear_mesh.setEnabled(True)
+
+ # the local code doesn't use these vars, setting them to be safe
+ self.username = ""
+ self.mesh_processes = 0
+ self.mesh_threads = 0
+ self.foam_processes = 0
+ self.foam_threads = 0
+ #self.foam_dir = FreeCAD.ParamGet(Prefs).GetString("FoamDir", "")
+ #self.output_path = FreeCAD.ParamGet(Prefs).GetString("OutputPath","")
+ self.output_path = ""
+ self.add_filename_to_output = False
+ # Not sure if this causes a side effect or not.
+ case_path = self.mesh_obj.Proxy.cart_mesh.meshCaseDir
+ self.form.pb_delete_mesh.setEnabled(os.path.exists(os.path.join(case_path, "Allmesh")))
+
+ else:
+ # set the vars to the remote host parameters
+ # most of these aren't used, at least not in this page
+ hostPrefs = self.host_prefs_location
+ self.hostname = FreeCAD.ParamGet(hostPrefs).GetString("Hostname", "")
+ self.username = FreeCAD.ParamGet(hostPrefs).GetString("Username", "")
+
+ # This is loading defaults. The user may change this number
+ self.mesh_processes = FreeCAD.ParamGet(hostPrefs).GetInt("MeshProcesses")
+ self.mesh_threads = FreeCAD.ParamGet(hostPrefs).GetInt("MeshThreads")
+
+ self.foam_processes = FreeCAD.ParamGet(hostPrefs).GetInt("FoamProcesses")
+ self.foam_threads = FreeCAD.ParamGet(hostPrefs).GetInt("FoamThreads")
+ self.foam_dir = FreeCAD.ParamGet(hostPrefs).GetString("FoamDir", "")
+
+ #self.output_path = FreeCAD.ParamGet(hostPrefs).GetString("OutputPath","")
+ self.output_path = CfdTools.getDefaultOutputPath(self.profile_name)
+ self.add_filename_to_output = FreeCAD.ParamGet(hostPrefs).GetBool("AddFilenameToOutput")
+ self.copy_back = FreeCAD.ParamGet(hostPrefs).GetBool("CopyBack")
+ self.delete_remote_results = FreeCAD.ParamGet(hostPrefs).GetBool("DeleteRemoteResults")
+
+ #now set the control values
+ #read these from the parameters in case they've been edited
+ self.mesh_obj.NumberOfProcesses = FreeCAD.ParamGet(hostPrefs).GetInt("MeshProcesses")
+ self.mesh_obj.NumberOfThreads = FreeCAD.ParamGet(hostPrefs).GetInt("MeshThreads")
+
+
+
+ # disable if using a remote host
+ self.form.pb_edit_mesh.setEnabled(False)
+ self.form.pb_paraview.setEnabled(False)
+ self.form.pb_check_mesh.setEnabled(False)
+ self.form.pb_load_mesh.setEnabled(False)
+ self.form.pb_clear_mesh.setEnabled(False)
+
+ # enable if using a remote host
+ case_path = self.mesh_obj.Proxy.cart_mesh.meshCaseDir
+ self.form.pb_copy_to_host.setEnabled(os.path.exists(os.path.join(case_path, "Allmesh")))
+ # TODO should check if there is a mesh case on the remote host and set accordingly
+ self.form.pb_delete_mesh.setEnabled(True)
+
+ #TODO: fix these, if we need to.
+ #self.form.le_mesh_processes.setText(str(self.mesh_processes))
+ #self.form.le_mesh_threads.setText(str(self.mesh_threads))
+
+ #self.form.le_hostname.setText(self.hostname)
+ #self.form.le_username.setText(self.username)
+
+ #self.form.le_foam_processes.setText(str(self.foam_processes))
+ #self.form.le_foam_threads.setText(str(self.foam_threads))
+ #self.form.le_foam_dir.setText(self.foam_dir)
+ #self.form.le_output_path.setText(self.output_path)
+ #self.form.cb_add_filename_to_output.setChecked(self.add_filename_to_output)
+
+
+ # this gets called when the user changes the profile
+ def profileChanged(self):
+ print("The profile was changed")
+ # change the global profile name
+ self.profile_name = self.form.cb_profile.currentText()
+ #load the values for the new profile
+ print ("New profile is ", self.profile_name)
+ self.loadProfile(self.profile_name)
+
+ # enable and disable the appropriate controls
+ self.updateUI
+
+ # test routine to run a mesh without a proxy
+ # The real routine is runMesh down below
+ def runRemoteMesh(self):
+ # run remote meshing directly, without a proxy
+ #profile_prefs = CfdTools.getPreferencesLocation() + '/Hosts/' + self.profile_name
+ remote_user = self.username
+ remote_hostname = self.hostname
+
+ # create the ssh connection command
+ # was ssh -tt
+ ssh_prefix = 'ssh -t ' + remote_user + '@' + remote_hostname + ' '
+
+ # Get the working directory for the mesh
+ working_dir = self.output_path
+ #TODO: add filename to the path if selected
+
+ # create the command to do the actual work
+ #command = 'EOT \n'
+ command = 'cd ' + working_dir + '/meshCase \n'
+ command += './Allmesh \n'
+ command += 'exit \n'
+ command += 'EOT \n'
+ command = ssh_prefix + ' << ' + command
+
+ self.consoleMessage("Starting remote meshing...")
+ try:
+ CfdTools.runFoamCommand(command)
+ print("Remote meshing is complete.")
+ self.consoleMessage("Remote meshing is complete.")
+ except Exception as error:
+ self.consoleMessage("Error meshing on remote host: " + str(error))
+
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Close)
# def reject() is called on close button
@@ -131,20 +364,53 @@ def load(self):
(self.mesh_obj.MeshUtility, self.mesh_obj.ElementDimension, self.mesh_obj.ConvertToDualMesh), 0)
self.form.cb_utility.setCurrentIndex(index_utility)
+ #TODO: this should be changed to reflect if the host is local or remote
def updateUI(self):
+
case_path = self.mesh_obj.Proxy.cart_mesh.meshCaseDir
- self.form.pb_edit_mesh.setEnabled(os.path.exists(case_path))
- self.form.pb_run_mesh.setEnabled(os.path.exists(os.path.join(case_path, "Allmesh")))
- self.form.pb_paraview.setEnabled(os.path.exists(os.path.join(case_path, "pv.foam")))
- self.form.pb_load_mesh.setEnabled(os.path.exists(os.path.join(case_path, "mesh_outside.stl")))
- self.form.pb_check_mesh.setEnabled(os.path.exists(os.path.join(case_path, "mesh_outside.stl")))
-
utility = CfdMesh.MESHERS[self.form.cb_utility.currentIndex()]
if utility == "snappyHexMesh":
self.form.snappySpecificProperties.setVisible(True)
else:
self.form.snappySpecificProperties.setVisible(False)
+ # enable the appropriate controls
+ # this is always an appropriate action
+ self.form.pb_write_mesh.setEnabled(True)
+
+ #enable these if the mesh is available locally
+ if self.profile_name == 'local' or self.copy_back :
+ self.form.pb_copy_to_host.setEnabled(False)
+ self.form.pb_delete_mesh.setEnabled(os.path.exists(os.path.join(case_path, "Allmesh")))
+ self.form.pb_edit_mesh.setEnabled(os.path.exists(case_path))
+ # TODO have to enable this for the case that there is no local meshcase but
+ # we have written one to a remote host We should be checking if the case is available on
+ # the remote host, not the local host
+ #self.form.pb_run_mesh.setEnabled(os.path.exists(os.path.join(case_path, "Allmesh")))
+ self.form.pb_run_mesh.setEnabled(True)
+
+ # TODO This enables as soon as the mesh case is written. It shouldn't.
+ self.form.pb_paraview.setEnabled(os.path.exists(os.path.join(case_path, "pv.foam")))
+ self.form.pb_load_mesh.setEnabled(os.path.exists(os.path.join(case_path, "mesh_outside.stl")))
+ self.form.pb_check_mesh.setEnabled(os.path.exists(os.path.join(case_path, "mesh_outside.stl")))
+ # TODO Should check that the mesh is loaded before enabling this. Not working right the way it is
+ self.form.pb_clear_mesh.setEnabled(True)
+
+ # remote host is being used without copy_back
+ # no local results to work on so disable these controls
+ else:
+ #self.form.pb_copy_to_host.setEnabled(os.path.exists(os.path.join(case_path, "Allmesh")))
+ local_dir = CfdTools.getDefaultOutputPath('local')
+ self.form.pb_copy_to_host.setEnabled(os.path.exists(os.path.join(local_dir, "meshCase")))
+ #TODO should check if the mesh is present on the remote host
+ self.form.pb_delete_mesh.setEnabled(True)
+ # remote hosts don't support these functions yet
+ self.form.pb_run_mesh.setEnabled(True)
+ self.form.pb_paraview.setEnabled(False)
+ self.form.pb_check_mesh.setEnabled(False)
+ self.form.pb_load_mesh.setEnabled(False)
+ self.form.pb_clear_mesh.setEnabled(False)
+
def store(self):
mesher_idx = self.form.cb_utility.currentIndex()
storeIfChanged(self.mesh_obj, 'CharacteristicLengthMax', getQuantity(self.form.if_max))
@@ -199,6 +465,10 @@ def writeMesh(self):
# Re-initialise CfdMeshTools with new parameters
self.store()
+ #get the host name we are writing the mesh case for
+ host_profile = self.profile_name
+ print ("Writing mesh for host profile " + host_profile + ".")
+
FreeCADGui.doCommand("from CfdOF.Mesh import CfdMeshTools")
FreeCADGui.doCommand("from CfdOF import CfdTools")
FreeCADGui.doCommand("cart_mesh = "
@@ -208,7 +478,10 @@ def writeMesh(self):
cart_mesh.progressCallback = self.progressCallback
# Start writing the mesh files
- self.consoleMessage("Preparing meshing ...")
+ if host_profile == "local":
+ self.consoleMessage("Preparing local mesh ...")
+ else:
+ self.consoleMessage("Preparing remote mesh for " + host_profile + "...")
try:
QApplication.setOverrideCursor(Qt.WaitCursor)
setQuantity(self.form.if_max, str(cart_mesh.getClmax()))
@@ -219,7 +492,12 @@ def writeMesh(self):
+ cart_mesh.part_obj.Label + ', ShapeType: '
+ cart_mesh.part_obj.Shape.ShapeType)
print(' CharacteristicLengthMax: ' + str(cart_mesh.clmax))
- FreeCADGui.doCommand("cart_mesh.writeMesh()")
+
+ if host_profile == "local":
+ FreeCADGui.doCommand("cart_mesh.writeMesh('local')")
+ else:
+ FreeCADGui.doCommand("cart_mesh.writeMesh('"+ host_profile +"')")
+
except Exception as ex:
self.consoleMessage("Error " + type(ex).__name__ + ": " + str(ex), 'Error')
raise
@@ -231,6 +509,7 @@ def writeMesh(self):
# Update the UI
self.updateUI()
+
def progressCallback(self, message):
self.consoleMessage(message)
@@ -261,6 +540,8 @@ def checkMeshClicked(self):
" proxy.check_mesh_process.waitForFinished()\n" +
"else:\n" +
" proxy.check_mesh_process.start(cmd, env_vars=env_vars)")
+
+
if self.mesh_obj.Proxy.check_mesh_process.waitForStarted():
self.consoleMessage("Mesh check started ...")
else:
@@ -278,12 +559,17 @@ def checkMeshClicked(self):
finally:
QApplication.restoreOverrideCursor()
+
def editMesh(self):
case_path = self.mesh_obj.Proxy.cart_mesh.meshCaseDir
self.consoleMessage("Please edit the case input files externally at: {}\n".format(case_path))
CfdTools.openFileManager(case_path)
def runMesh(self):
+
+ #disable the run button so a second instance isn't started
+ self.form.pb_run_mesh.setEnabled(False)
+
if CfdTools.getFoamRuntime() == "PosixDocker":
CfdTools.startDocker()
@@ -306,32 +592,97 @@ def runMesh(self):
try:
QApplication.setOverrideCursor(Qt.WaitCursor)
+
self.consoleMessage("Initializing {} ...".format(self.mesh_obj.MeshUtility))
FreeCADGui.doCommand("from CfdOF.Mesh import CfdMeshTools")
FreeCADGui.doCommand("from CfdOF import CfdTools")
FreeCADGui.doCommand("from CfdOF import CfdConsoleProcess")
- FreeCADGui.doCommand("cart_mesh = "
- " CfdMeshTools.CfdMeshTools(FreeCAD.ActiveDocument." + self.mesh_obj.Name + ")")
+
+ FreeCADGui.doCommand("cart_mesh = " +
+ "CfdMeshTools.CfdMeshTools(FreeCAD.ActiveDocument." + self.mesh_obj.Name + ")")
+
FreeCADGui.doCommand("proxy = FreeCAD.ActiveDocument." + self.mesh_obj.Name + ".Proxy")
FreeCADGui.doCommand("proxy.cart_mesh = cart_mesh")
FreeCADGui.doCommand("cart_mesh.error = False")
- FreeCADGui.doCommand("cmd = CfdTools.makeRunCommand('./Allmesh', cart_mesh.meshCaseDir, source_env=False)")
- FreeCADGui.doCommand("env_vars = CfdTools.getRunEnvironment()")
- FreeCADGui.doCommand("proxy.running_from_macro = True")
- self.mesh_obj.Proxy.running_from_macro = False
- FreeCADGui.doCommand("if proxy.running_from_macro:\n" +
- " mesh_process = CfdConsoleProcess.CfdConsoleProcess()\n" +
- " mesh_process.start(cmd, env_vars=env_vars)\n" +
- " mesh_process.waitForFinished()\n" +
- "else:\n" +
- " proxy.mesh_process.start(cmd, env_vars=env_vars)")
+
+ # run locally
+ if self.profile_name == "local":
+ FreeCADGui.doCommand("cmd = CfdTools.makeRunCommand('./Allmesh', cart_mesh.meshCaseDir, source_env=False)")
+ FreeCADGui.doCommand("env_vars = CfdTools.getRunEnvironment()")
+
+ FreeCADGui.doCommand("print('cmd:')")
+ FreeCADGui.doCommand("print(cmd)")
+ #FreeCADGui.doCommand("print('env_vars:' + env_vars)")
+
+ FreeCADGui.doCommand("proxy.running_from_macro = True")
+ self.mesh_obj.Proxy.running_from_macro = False
+
+ FreeCADGui.doCommand("if proxy.running_from_macro:\n" +
+ " mesh_process = CfdConsoleProcess.CfdConsoleProcess()\n" +
+ " mesh_process.start(cmd, env_vars=env_vars)\n" +
+ " mesh_process.waitForFinished()\n" +
+ "else:\n" +
+ " proxy.mesh_process.start(cmd, env_vars=env_vars)")
+
+
+ # run on remote host
+ else:
+ #self.runRemoteMesh() #For testing the non proxy function above
+
+ # Get the username and hostname for the remote host
+ prefsCmd = "profile_prefs = CfdTools.getPreferencesLocation() + " + '"/Hosts/' + self.profile_name + '"'
+ #print("prefsCmd:" + prefsCmd)
+ FreeCADGui.doCommand(prefsCmd)
+ FreeCADGui.doCommand("print('profile_prefs:' + profile_prefs)")
+
+ FreeCADGui.doCommand("remote_user = FreeCAD.ParamGet(profile_prefs).GetString('Username', '')")
+ FreeCADGui.doCommand("remote_hostname =FreeCAD.ParamGet(profile_prefs).GetString('Hostname', '')")
+
+ #FreeCADGui.doCommand("print('username:' + remote_user)")
+ #FreeCADGui.doCommand("print('hostname:' + remote_hostname)")
+
+ # create the ssh connection command
+ # was ssh -tt
+ FreeCADGui.doCommand("ssh_prefix = 'ssh -t ' + remote_user + '@' + remote_hostname + ' '")
+
+ # Get the working directory for the mesh
+ #FreeCADGui.doCommand("working_dir = FreeCAD.ParamGet(profile_prefs).GetString('OutputPath', '')")
+ FreeCADGui.doCommand("working_dir = CfdTools.getDefaultOutputPath('" + self.profile_name + "' )")
+ #FreeCADGui.doCommand("print('working directory:' + working_dir)")
+
+ # create the command to do the actual work
+ FreeCADGui.doCommand("command = 'EOT \\n' \n" +
+ "command += 'cd ' + working_dir + '/meshCase \\n' \n" +
+ "command += './Allmesh \\n' \n" +
+ "command += 'exit \\n' \n" +
+ "command += 'EOT' \n")
+ FreeCADGui.doCommand("command = ssh_prefix + ' << ' + command + '\\n'")
+
+ #FreeCADGui.doCommand("print(command)")
+
+ FreeCADGui.doCommand("runCommand = CfdTools.makeRunCommand(command, None)")
+
+ FreeCADGui.doCommand("proxy.running_from_macro = True")
+ self.mesh_obj.Proxy.running_from_macro = False
+
+ FreeCADGui.doCommand("if proxy.running_from_macro:\n" +
+ " mesh_process = CfdConsoleProcess.CfdConsoleProcess()\n" +
+ " mesh_process.start(runCommand)\n" +
+ " mesh_process.waitForFinished()\n" +
+ "else:\n" +
+ " proxy.mesh_process.start(runCommand)")
+
+
if self.mesh_obj.Proxy.mesh_process.waitForStarted():
- self.form.pb_run_mesh.setEnabled(False) # Prevent user running a second instance
+ # enable/disable the correct buttons
self.form.pb_stop_mesh.setEnabled(True)
+ self.form.pb_run_mesh.setEnabled(False)
self.form.pb_write_mesh.setEnabled(False)
+ self.form.pb_edit_mesh.setEnabled(False)
self.form.pb_check_mesh.setEnabled(False)
self.form.pb_paraview.setEnabled(False)
self.form.pb_load_mesh.setEnabled(False)
+ self.form.pb_clear_mesh.setEnabled(False)
self.consoleMessage("Mesher started ...")
else:
self.consoleMessage("Error starting meshing process", 'Error')
@@ -342,6 +693,7 @@ def runMesh(self):
finally:
QApplication.restoreOverrideCursor()
+
def killMeshProcess(self):
self.consoleMessage("Meshing manually stopped")
self.error_message = 'Meshing interrupted'
@@ -359,28 +711,178 @@ def gotErrorLines(self, lines):
self.consoleMessage(print_err, 'Error')
def meshFinished(self, exit_code):
+ if self.form.cb_notify.isChecked():
+ #print("Beeping now")
+ QApplication.beep()
if exit_code == 0:
self.consoleMessage('Meshing completed')
self.analysis_obj.NeedsMeshRerun = False
- self.form.pb_run_mesh.setEnabled(True)
- self.form.pb_stop_mesh.setEnabled(False)
- self.form.pb_paraview.setEnabled(True)
- self.form.pb_write_mesh.setEnabled(True)
- self.form.pb_check_mesh.setEnabled(True)
- self.form.pb_load_mesh.setEnabled(True)
+
+ #check if there is work to do on the remote host
+ if self.profile_name != 'local':
+ # copy the meshcase back to the workstation?
+ # this code is also used in reverse in CfdMeshTools.py for copying the mesh case to the server
+ if self.copy_back:
+ local_prefs = CfdTools.getPreferencesLocation()
+ profile_prefs = local_prefs +"/Hosts/" + self.profile_name
+
+ remote_user = FreeCAD.ParamGet(profile_prefs).GetString("Username", "")
+ remote_hostname = FreeCAD.ParamGet(profile_prefs).GetString("Hostname", "")
+ remote_output_path = CfdTools.getDefaultOutputPath(self.profile_name)
+ local_output_path = CfdTools.getDefaultOutputPath('local')
+
+ #case_path = os.path.abspath(self.mesh_obj.Proxy.cart_mesh.meshCaseDir)
+ # If this ^ is used as the destination dir, it will put the remote meshCase dir in the
+ # local meshCase directory, which is wrong (/tmp/CfdOF/meshCase/meshCase, for example
+
+ # if we are deleting the mesh case on the server
+ # don't delete the mesh case. In most cases we need it for the solver to run later
+ # it will be deleted after the solve if we elect to remove results
+
+ """
+ if self.delete_remote_results:
+ deleteStr = "--remove-source-files "
+ else:
+ deleteStr = ""
+ """
+ deleteStr = ""
+
+ # rsync the meshCase result on the server to the workstation's output directory
+ # Typical useage: rsync -r --delete --remove-source-files me@david/tmp/meshCase /tmp
+ # --remove-source-files removes the files that get transfered
+ # --delete removes files from the destination that didn't get transfered
+
+ try:
+ CfdTools.runFoamCommand("rsync -r --delete " + deleteStr + remote_user + "@" + remote_hostname + ":" + remote_output_path + "/meshCase " + \
+ local_output_path)
+ except Exception as e:
+ CfdTools.cfdMessage("Could not copy mesh to local computer: " + str(e))
+ if self.progressCallback:
+ self.progressCallback("Could not copy mesh to local computer: " + str(e))
+ else:
+ CfdTools.cfdMessage("Copied mesh case to " + local_output_path + " on local computer\n" )
+ if self.progressCallback:
+ self.progressCallback("Copied mesh case to " + local_output_path + "on local computer\n")
+ if self.progressCallback:
+ self.progressCallback("Mesh case copy back process is complete.")
+
+ # delete the result on the server ?
+ # if this got deleted then we'd have to remesh every run
+ # or copy a mesh from the local machine to the server
+ # not implemented for this reason
+ if self.delete_remote_results:
+ pass
+
else:
self.consoleMessage("Meshing exited with error", 'Error')
- self.form.pb_run_mesh.setEnabled(True)
- self.form.pb_stop_mesh.setEnabled(False)
- self.form.pb_write_mesh.setEnabled(True)
- self.form.pb_check_mesh.setEnabled(False)
- self.form.pb_paraview.setEnabled(False)
+ # update the controls
self.error_message = ''
# Get rid of any existing loaded mesh
self.pbClearMeshClicked()
self.updateUI()
+
+ # Delete the mesh case on the current host, including the local host if it is the current host
+ # TODO: Add a warning for the user so they can cancel if desired
+ def deleteMeshcase(self):
+ # local host
+ if self.hostname == 'local':
+ #TODO Check if the meshCase exists
+ local_output_path = CfdTools.getDefaultOutputPath('local')
+ print("Local output path:" + local_output_path)
+
+ # create the command to do the actual work
+ # TODO This won't work on a Windows or Mac machine Fix it
+ command = "rm -rf " + local_output_path + "/meshCase"
+
+ try:
+ CfdTools.runFoamCommand(command, "./")
+
+ except Exception as e:
+ CfdTools.cfdMessage("Could not delete the local mesh case:" + str(e))
+ self.consoleMessage("Could not delete the local mesh case:" + str(e))
+ else:
+ CfdTools.cfdMessage("Deleted the mesh case in " + local_output_path + "\n" )
+ self.consoleMessage("Deleted the mesh case in " + local_output_path + "\n" )
+ # now update the UI
+ self.updateUI
+
+ # remote host
+ # TODO uses a combination of looked up parameters and local vars ! Very dangerous. Fix this
+ # TODO check if mesh case exists
+ else:
+ local_prefs = CfdTools.getPreferencesLocation()
+ profile_prefs = local_prefs +"/Hosts/" + self.profile_name
+ #remote_user = FreeCAD.ParamGet(profile_prefs).GetString("Username", "")
+ remote_hostname = FreeCAD.ParamGet(profile_prefs).GetString("Hostname", "")
+ #remote_output_path = FreeCAD.ParamGet(profile_prefs).GetString("OutputPath","")
+ remote_output_path = CfdTools.getDefaultOutputPath(self.profile_name)
+
+ # create the command to do the actual work
+ command = 'ssh -tt ' + self.username + '@' + self.hostname + " " # was -tt
+ command += ' << EOT \n'
+ command += 'cd ' + remote_output_path + '\n'
+ command += 'rm -rf meshCase ' + '\n'
+ command += 'exit \n '
+ command += 'EOT \n'
+ #print("Code command:" + command)
+
+ try:
+ CfdTools.runFoamCommand(command)
+
+ except Exception as e:
+ CfdTools.cfdMessage("Could not delete mesh case on remote host:" + str(e))
+ self.consoleMessage("Could not delete mesh case on remote host:" + str(e))
+ else:
+ CfdTools.cfdMessage("Deleted mesh case in " + remote_hostname + ":" + remote_output_path + "\n" )
+ self.consoleMessage("Deleted mesh case in " + remote_hostname + ":" + remote_output_path + "\n" )
+ # now update the UI
+ self.updateUI
+
+
+
+ def copyMeshcaseToHost(self):
+ #check that a host is selected
+ if self.profile_name == 'local':
+ self.consoleMessage("Select a host to copy the mesh case to.")
+ else:
+ # copy the meshcase back to the workstation
+ # this code is also used in reverse in CfdMeshTools.py for copying the mesh case to the server
+ local_prefs = CfdTools.getPreferencesLocation()
+ profile_prefs = local_prefs +"/Hosts/" + self.profile_name
+
+ remote_user = FreeCAD.ParamGet(profile_prefs).GetString("Username", "")
+ remote_hostname = FreeCAD.ParamGet(profile_prefs).GetString("Hostname", "")
+
+ #remote_output_path = FreeCAD.ParamGet(profile_prefs).GetString("OutputPath","")
+ #local_output_path = FreeCAD.ParamGet(local_prefs).GetString("DefaultOutputPath","")
+
+ remote_output_path = CfdTools.getDefaultOutputPath(self.profile_name)
+ local_output_path = CfdTools.getDefaultOutputPath('local')
+
+
+ #case_path = os.path.abspath(self.mesh_obj.Proxy.cart_mesh.meshCaseDir)
+ # If this ^ is used as the destination dir, it will put the remote meshCase dir in the
+ # local meshCase directory, which is wrong (/tmp/CfdOF/meshCase/meshCase, for example
+
+ # rsync the meshCase result on the server to the workstation's output directory
+ # Typical useage: rsync -r --delete --remove-source-files me@david/tmp/meshCase /tmp
+ # --remove-source-files removes the files that get transfered
+ # --delete removes files from the destination that didn't get transfered
+
+ try:
+ CfdTools.runFoamCommand("rsync -r --delete " + local_output_path + "/meshCase " + remote_user + "@" + remote_hostname + ":" \
+ + remote_output_path)
+
+ except Exception as e:
+ CfdTools.cfdMessage("Could not copy mesh case to host: " + str(e))
+ self.consoleMessage("Could not copy mesh case to host: " + str(e))
+ else:
+ CfdTools.cfdMessage("Copied mesh case to " + remote_hostname + ":" + remote_output_path + "\n" )
+ self.consoleMessage("Copied mesh case to " + remote_hostname + ":" + remote_output_path + "\n" )
+
+
def openParaview(self):
QApplication.setOverrideCursor(Qt.WaitCursor)
case_path = os.path.abspath(self.mesh_obj.Proxy.cart_mesh.meshCaseDir)
diff --git a/CfdOF/Solve/CfdCaseWriterFoam.py b/CfdOF/Solve/CfdCaseWriterFoam.py
index 1d1668cf..b70051d8 100644
--- a/CfdOF/Solve/CfdCaseWriterFoam.py
+++ b/CfdOF/Solve/CfdCaseWriterFoam.py
@@ -24,7 +24,8 @@
import os
import os.path
-from FreeCAD import Units
+#import FreeCAD
+from FreeCAD import Units, ParamGet
from CfdOF import CfdTools
from CfdOF.TemplateBuilder import TemplateBuilder
from CfdOF.CfdTools import cfdMessage
@@ -51,12 +52,16 @@ def __init__(self, analysis_obj):
self.zone_objs = CfdTools.getZoneObjects(analysis_obj)
self.dynamic_mesh_refinement_obj = CfdTools.getDynamicMeshAdaptation(self.mesh_obj)
self.mesh_generated = False
+
+ # LG123 changed this for testing
self.working_dir = CfdTools.getOutputPath(self.analysis_obj)
+ #self.working_dir = CfdTools.getDefaultOutputPath('local')
+
self.progressCallback = None
self.settings = None
- def writeCase(self):
+ def writeCase(self, profile_name):
""" writeCase() will collect case settings, and finally build a runnable case. """
cfdMessage("Writing case to folder {}\n".format(self.working_dir))
if not os.path.exists(self.working_dir):
@@ -153,8 +158,44 @@ def writeCase(self):
cfdMessage("Successfully wrote case to folder {}\n".format(self.working_dir))
if self.progressCallback:
- self.progressCallback("Case written successfully")
-
+ self.progressCallback("Case written locally")
+
+ # if using a remote host, copy the case folder from the local case dir
+ # to the remote host's directory
+ if profile_name != "local":
+
+ profile_prefs = CfdTools.getPreferencesLocation() +"/Hosts/" + profile_name
+ remote_user = ParamGet(profile_prefs).GetString("Username", "")
+ remote_hostname = ParamGet(profile_prefs).GetString("Hostname", "")
+
+ #remote_output_path = ParamGet(profile_prefs).GetString("OutputPath","")
+ remote_output_path = CfdTools.getDefaultOutputPath(profile_name)
+
+ #print("remote_user:" + remote_user)
+ #print("remote_hostname:" + remote_hostname)
+ #print("remote_output_path:" + remote_output_path)
+ #print("self.case_folder:" + self.case_folder)
+ #print("self.working_dir:" + self.working_dir)
+
+ # rsync the meshCase directory to the remote host's output directory
+ # Typical useage: rsync -r --delete /tmp/ me@david:/tmp
+ # --remove-source-files removes the files that get transfered
+ # --delete removes files from the destination that didn't get transfered
+ #
+
+ try:
+ CfdTools.runFoamCommand("rsync -r --delete --remove-source-files " + self.case_folder + " " + remote_user + "@" + remote_hostname + \
+ ":" + remote_output_path)
+ except Exception as e:
+ CfdTools.cfdMessage("Could not move case to remote host: " + str(e))
+ if self.progressCallback:
+ self.progressCallback("Could not move case to remote host: " + str(e))
+ return False
+ else:
+ CfdTools.cfdMessage("Moved solver case to " + remote_hostname + ":" + remote_output_path + "\n" )
+ if self.progressCallback:
+ self.progressCallback("Moved solver case to " + remote_hostname + ":" + remote_output_path + "\n")
+
return True
def getSolverName(self):
diff --git a/CfdOF/Solve/TaskPanelCfdSolverControl.py b/CfdOF/Solve/TaskPanelCfdSolverControl.py
index ef0ae544..54760474 100644
--- a/CfdOF/Solve/TaskPanelCfdSolverControl.py
+++ b/CfdOF/Solve/TaskPanelCfdSolverControl.py
@@ -35,6 +35,28 @@
from PySide.QtCore import Qt
from PySide.QtGui import QApplication
+# ********************************************************************************************************
+# LinuxGuy123's Notes
+#
+# TODOs (there are some in the code as well)
+#
+# - the solver
+#
+# -makeRunCommand is using the local OF bash command to build the remote run command. It works but it isn't correct.
+# The remote run is not calling the source command to set up OF usage. It is relying on the bash shell on the remote host
+# to do that, through bashrc and OF working directly from the command line.
+#
+# -enable and disable buttons appropriately when running and when done
+#
+# global vars are used for stuff that should be passed via the solver object.
+#
+# -edit case doesn't copy the case back to the server after the edit is done.
+#
+# -remote solving has not been tested in macros
+#
+#- presently does not set the number of threads that the solver uses properly. Must be done manually. Not sure where
+# the default is set. It runs with the number of cores in the editor, but that should be set to the preferences value
+# when the host is changed. It doesn't. The field name is "ParallelCores" as far as I can tell.
class TaskPanelCfdSolverControl:
def __init__(self, solver_runner_obj):
@@ -61,9 +83,44 @@ def __init__(self, solver_runner_obj):
self.working_dir = CfdTools.getOutputPath(self.analysis_object)
+ #set the prefs and host prefs locations
+ self.prefs_location = CfdTools.getPreferencesLocation()
+ self.host_prefs_location = self.prefs_location + "/Hosts"
+ self.useRemoteProcessing = FreeCAD.ParamGet(self.prefs_location).GetBool('UseRemoteProcessing', 0)
+
+ #setting these here so they get created as globals
+ #they also get initiated in loadProfile()
+ # self.use_remote_processing = False <- this is set above before the control is loaded
+ self.profile_name = ""
+ self.hostname = ""
+ self.username = ""
+ #self.mesh_processes = 0
+ #self.mesh_threads = 0
+ #self.foam_processes = 0
+ #self.foam_threads = 0
+ self.foam_dir = ""
+ self.output_path = ""
+ #self.gmsh_path = ""
+ self.add_filename_to_output = False
+
+ #add a local host to cb_profile
+ self.form.cb_profile.addItem("local")
+
+ # if using remote processing, add the host profiles as well
+ if self.useRemoteProcessing:
+ self.loadProfileNames()
+ else:
+ #disable cb_profile so that users aren't trying to change the host
+ self.form.cb_profile.setEnabled(False)
+
+ # load the local profile
+ self.loadProfile("local")
+
self.updateUI()
# Connect Signals and Slots
+ # set up the profiles combo box connection
+ self.form.cb_profile.currentIndexChanged.connect(self.profileChanged)
self.form.pb_write_inp.clicked.connect(self.write_input_file_handler)
self.form.pb_edit_inp.clicked.connect(self.editSolverInputFile)
self.form.pb_run_solver.clicked.connect(self.runSolverProcess)
@@ -72,9 +129,102 @@ def __init__(self, solver_runner_obj):
self.Start = time.time()
self.Timer.start()
+ # loads the profiles names into the profile combo box
+ def loadProfileNames(self):
+ profileDir = self.prefs_location + "/Hosts"
+ profiles = FreeCAD.ParamGet(profileDir)
+ profileList = profiles.GetGroups()
+ for item in profileList:
+ self.form.cb_profile.addItem(item)
+
+ # load profile parameters into the controls and local vars
+ def loadProfile(self, profile_name):
+
+ #set the global profile name
+ self.profile_name = profile_name
+
+ #set the global host prefs location
+ self.host_prefs_location = self.prefs_location + "/Hosts/" + profile_name
+
+ #set the other global vars
+ if profile_name == "":
+ print("Error: no host profile selected")
+ return
+
+ # set the vars to the local parameters
+ if profile_name == "local":
+ self.hostname = "local"
+ self.output_path = CfdTools.getDefaultOutputPath('local')
+ # the local code doesn't use these vars, so don't set them
+ # dangerous.
+ """
+ self.username = ""
+ self.mesh_processes = 0
+ self.mesh_threads = 0
+ self.foam_processes = 0
+ self.foam_threads = 0
+ self.foam_dir = FreeCAD.ParamGet(hostPrefs).GetString("FoamDir", "")
+ self.output_path = FreeCAD.ParamGet(hostPrefs).GetString("OutputPath","")
+ self.output_path = ""
+ self.add_filename_to_output = False
+ """
+ else:
+ # set the vars to the remote host parameters
+ # most of these aren't used, at least not in this page
+ hostPrefs = self.host_prefs_location
+ self.hostname = FreeCAD.ParamGet(hostPrefs).GetString("Hostname", "")
+ self.username = FreeCAD.ParamGet(hostPrefs).GetString("Username", "")
+
+
+ #self.mesh_processes = FreeCAD.ParamGet(hostPrefs).GetInt("MeshProcesses")
+ #self.mesh_threads = FreeCAD.ParamGet(hostPrefs).GetInt("MeshThreads")
+ #self.foam_processes = FreeCAD.ParamGet(hostPrefs).GetInt("FoamProcesses")
+ #self.foam_threads = FreeCAD.ParamGet(hostPrefs).GetInt("FoamThreads")
+ self.foam_dir = FreeCAD.ParamGet(hostPrefs).GetString("FoamDir", "")
+
+ #self.output_path = FreeCAD.ParamGet(hostPrefs).GetString("OutputPath","")
+ self.output_path = CfdTools.getDefaultOutputPath(self.profile_name)
+
+ # these are used
+ #self.add_filename_to_output = FreeCAD.ParamGet(hostPrefs).GetBool("AddFilenameToOutput")
+ self.copy_back = FreeCAD.ParamGet(hostPrefs).GetBool("CopyBack")
+ self.delete_remote_results = FreeCAD.ParamGet(hostPrefs).GetBool("DeleteRemoteResults")
+
+
+ # now set the control values
+ # leaving these in in case we pass parameters like these to the solver
+ # object some day.
+
+ #self.mesh_obj.NumberOfProcesses = self.mesh_processes
+ #self.mesh_obj.NumberOfThreads = self.mesh_threads
+
+ # this gets called when the user changes the profile
+ def profileChanged(self):
+ print("The profile was changed")
+ # change the global profile name
+ self.profile_name = self.form.cb_profile.currentText()
+ #load the values for the new profile
+ print ("New profile is ", self.profile_name)
+ self.loadProfile(self.profile_name)
+ # TODO enable and disable the appropriate controls here
+ # Remote hosts can't edit the case nor Paraview, check mesh, etc.
+ # Nor load surface mesh nor clear surface mesh.
+ if self.profile_name == 'local':
+ self.form.pb_write_inp.enabled = True
+ else:
+ pass
+
def updateUI(self):
solverDirectory = os.path.join(self.working_dir, self.solver_object.InputCaseName)
- self.form.pb_edit_inp.setEnabled(os.path.exists(solverDirectory))
+
+ if self.profile_name == 'local':
+ self.form.pb_edit_inp.setEnabled(os.path.exists(solverDirectory))
+
+ # TODO: enable local editing of the solver case
+ else:
+ self.form.pb_edit_inp.setEnabled(False)
+
+ # TODO: Paraview is enabled even though the solver hasn't been run yet. Fix this ?
self.form.pb_paraview.setEnabled(os.path.exists(os.path.join(solverDirectory, "pv.foam")))
self.form.pb_run_solver.setEnabled(os.path.exists(os.path.join(solverDirectory, "Allrun")))
@@ -114,31 +264,35 @@ def write_input_file_handler(self):
self.consoleMessage("Case writer called")
self.form.pb_paraview.setEnabled(False)
self.form.pb_edit_inp.setEnabled(False)
- self.form.pb_run_solver.setEnabled(False)
+ self.form.pb_run_solver.setEnabled(False)
QApplication.setOverrideCursor(Qt.WaitCursor)
try:
- FreeCADGui.doCommand("FreeCAD.ActiveDocument." + self.solver_object.Name + ".Proxy.case_writer = "
+ FreeCADGui.doCommand("FreeCAD.ActiveDocument." + self.solver_object.Name + ".Proxy.case_writer = "
"CfdCaseWriterFoam.CfdCaseWriterFoam(FreeCAD.ActiveDocument." +
self.solver_runner.analysis.Name + ")")
- FreeCADGui.doCommand("writer = FreeCAD.ActiveDocument." +
+ FreeCADGui.doCommand("writer = FreeCAD.ActiveDocument." +
self.solver_object.Name + ".Proxy.case_writer")
- writer = self.solver_object.Proxy.case_writer
- writer.progressCallback = self.consoleMessage
- FreeCADGui.doCommand("writer.writeCase()")
+ writer = self.solver_object.Proxy.case_writer
+ writer.progressCallback = self.consoleMessage
+ FreeCADGui.doCommand("writer.writeCase('" + self.profile_name +"')")
+
except Exception as e:
- self.consoleMessage("Error writing case:", 'Error')
- self.consoleMessage(type(e).__name__ + ": " + str(e), 'Error')
- self.consoleMessage("Write case setup file failed", 'Error')
- raise
+ self.consoleMessage("Error writing case:", 'Error')
+ self.consoleMessage(type(e).__name__ + ": " + str(e), 'Error')
+ self.consoleMessage("Write case setup file failed", 'Error')
+ raise
else:
- self.analysis_object.NeedsCaseRewrite = False
+ self.analysis_object.NeedsCaseRewrite = False
finally:
- QApplication.restoreOverrideCursor()
+ QApplication.restoreOverrideCursor()
self.updateUI()
self.form.pb_run_solver.setEnabled(True)
+
else:
self.consoleMessage("Case check failed", 'Error')
+
+
def check_prerequisites_helper(self):
self.consoleMessage("Checking dependencies...")
@@ -153,10 +307,11 @@ def editSolverInputFile(self):
self.consoleMessage("Please edit the case input files externally at: {}\n".format(case_path))
CfdTools.openFileManager(case_path)
- def runSolverProcess(self):
+ def runSolverProcess(self, profileName):
self.Start = time.time()
# Check for changes that require remesh
+ # TODO: This will not run the mesher on a remote host. Fix this ?
if FreeCAD.GuiUp and (
self.analysis_object.NeedsMeshRewrite or
self.analysis_object.NeedsCaseRewrite or
@@ -180,7 +335,7 @@ def runSolverProcess(self):
if self.analysis_object.NeedsMeshRewrite or self.analysis_object.NeedsMeshRerun:
from CfdOF.Mesh import CfdMeshTools
- mesh_obj = CfdTools.getMeshObject(self.analysis_object)
+ mesh_obj = CfdTools.getMeshObject(self.analysis_object) #TODO: This won't work with remote hosts. Need to pass the host name into the mesh object
cart_mesh = CfdMeshTools.CfdMeshTools(mesh_obj)
cart_mesh.progressCallback = self.consoleMessage
if self.analysis_object.NeedsMeshRewrite:
@@ -202,15 +357,49 @@ def runSolverProcess(self):
cart_mesh.error = False
cmd = CfdTools.makeRunCommand('./Allmesh', cart_mesh.meshCaseDir, source_env=False)
env_vars = CfdTools.getRunEnvironment()
- self.solver_object.Proxy.solver_process.start(cmd, env_vars=env_vars)
- if self.solver_object.Proxy.solver_process.waitForStarted():
- # Setting solve button to inactive to ensure that two instances of the same simulation aren't started
- # simultaneously
- self.form.pb_write_inp.setEnabled(False)
- self.form.pb_run_solver.setEnabled(False)
- self.form.terminateSolver.setEnabled(True)
- self.consoleMessage("Mesher started ...")
+
+ # run locally
+ if profileName == 'local':
+ self.solver_object.Proxy.solver_process.start(cmd, env_vars=env_vars)
+ if self.solver_object.Proxy.solver_process.waitForStarted():
+ # Setting solve button to inactive to ensure that two instances of the same simulation aren't started
+ # simultaneously
+ self.form.pb_write_inp.setEnabled(False)
+ self.form.pb_run_solver.setEnabled(False)
+ self.form.terminateSolver.setEnabled(True)
+ self.consoleMessage("Mesher started ...")
+ return
+
+ # run remotely
+ # not implemented
+ else:
+ self.consoleMessage("Meshing from within the solver is not implemented for remote hosts.")
+ self.consoleMessage("Generate the mesh from the mesh object instead.")
return
+ """
+ remote_user = self.username
+ remote_hostname = self.hostname
+
+ # create the ssh connection command
+ # use ssh -t, not ssh -tt
+ ssh_prefix = 'ssh -t' + remote_user + '@' + remote_hostname + ' '
+
+ # Get the working directory for the mesh
+ working_dir = self.output_path
+ #TODO: add filename to the path if selected
+
+ # create the command to do the actual work
+ command = 'EOT \n'
+ command += 'cd ' + working_dir + '/meshCase \n'
+ command += './Allrun \n'
+ command += 'exit \n'
+ command += 'EOT \n'
+ command = ssh_prefix + ' << ' + command
+
+ cmd = CfdTools.makeRunCommand(command,None)
+ """
+
+ #Mesh is ready to solve, so run it.
else:
self.Start = time.time()
@@ -222,29 +411,120 @@ def runSolverProcess(self):
# This is a workaround to emit code into macro without actually running it
FreeCADGui.doCommand("proxy.running_from_macro = True")
self.solver_object.Proxy.running_from_macro = False
- FreeCADGui.doCommand(
- "if proxy.running_from_macro:\n" +
- " analysis_object = FreeCAD.ActiveDocument." + self.analysis_object.Name + "\n" +
- " solver_object = FreeCAD.ActiveDocument." + self.solver_object.Name + "\n" +
- " working_dir = CfdTools.getOutputPath(analysis_object)\n" +
- " case_name = solver_object.InputCaseName\n" +
- " solver_directory = os.path.abspath(os.path.join(working_dir, case_name))\n" +
- " from CfdOF.Solve.CfdRunnableFoam import CfdRunnableFoam\n" +
- " solver_runner = CfdRunnableFoam.CfdRunnableFoam(analysis_object, solver_object)\n" +
- " cmd = solver_runner.get_solver_cmd(solver_directory)\n" +
- " env_vars = solver_runner.getRunEnvironment()\n" +
- " solver_process = CfdConsoleProcess.CfdConsoleProcess(stdout_hook=solver_runner.process_output)\n" +
- " solver_process.start(cmd, env_vars=env_vars)\n" +
- " solver_process.waitForFinished()\n")
- working_dir = CfdTools.getOutputPath(self.analysis_object)
- case_name = self.solver_object.InputCaseName
- solver_directory = os.path.abspath(os.path.join(working_dir, case_name))
- cmd = self.solver_runner.get_solver_cmd(solver_directory)
- env_vars = self.solver_runner.getRunEnvironment()
- self.solver_object.Proxy.solver_process = CfdConsoleProcess(finished_hook=self.solverFinished,
- stdout_hook=self.gotOutputLines,
- stderr_hook=self.gotErrorLines)
- self.solver_object.Proxy.solver_process.start(cmd, env_vars=env_vars)
+
+ # if running on local host
+ if self.profile_name == "local":
+ # This must be kept in one doCommand because of the if statement
+ # TODO: check that the doCommand code is the same as the inline code
+ FreeCADGui.doCommand(
+ "if proxy.running_from_macro:\n" +
+ " analysis_object = FreeCAD.ActiveDocument." + self.analysis_object.Name + "\n" +
+ " solver_object = FreeCAD.ActiveDocument." + self.solver_object.Name + "\n" +
+
+ " working_dir = CfdTools.getOutputPath(analysis_object)\n" +
+ " case_name = solver_object.InputCaseName\n" +
+ " solver_directory = os.path.abspath(os.path.join(working_dir, case_name))\n" +
+ " from CfdOF.Solve.CfdRunnableFoam import CfdRunnableFoam\n" +
+ " solver_runner = CfdRunnableFoam.CfdRunnableFoam(analysis_object, solver_object)\n" +
+ " cmd = solver_runner.get_solver_cmd(solver_directory)\n" +
+ " env_vars = solver_runner.getRunEnvironment()\n" +
+ " solver_process = CfdConsoleProcess.CfdConsoleProcess(stdout_hook=solver_runner.process_output)\n" +
+ " solver_process.start(cmd,env_vars= env_vars)\n" +
+ " solver_process.waitForFinished()")
+
+ working_dir = CfdTools.getOutputPath(self.analysis_object)
+ case_name = self.solver_object.InputCaseName
+ solver_directory = os.path.abspath(os.path.join(working_dir, case_name))
+ cmd = self.solver_runner.get_solver_cmd(solver_directory)
+ env_vars = self.solver_runner.getRunEnvironment()
+ self.solver_object.Proxy.solver_process = CfdConsoleProcess(finished_hook=self.solverFinished,
+ stdout_hook=self.gotOutputLines,
+ stderr_hook=self.gotErrorLines)
+ self.solver_object.Proxy.solver_process.start(cmd, env_vars=env_vars)
+
+ # running remotely
+ else:
+
+ # This must be kept in one doCommand because of the if statement
+ # The only difference between this command and the local command is " cmd = CfdTools.makeRunCommand('" + command + "',None)"
+ # TODO: Test the macro code.
+ FreeCADGui.doCommand(
+ "if proxy.running_from_macro:\n" +
+ " analysis_object = FreeCAD.ActiveDocument." + self.analysis_object.Name + "\n" +
+ " solver_object = FreeCAD.ActiveDocument." + self.solver_object.Name + "\n" +
+
+ # TODO: this might be wrong
+ " working_dir = CfdTools.getOutputPath(analysis_object)\n" +
+ " case_name = solver_object.InputCaseName\n" +
+ " solver_directory = os.path.abspath(os.path.join(working_dir, case_name))\n" +
+ " from CfdOF.Solve.CfdRunnableFoam import CfdRunnableFoam\n" +
+ " solver_runner = CfdRunnableFoam.CfdRunnableFoam(analysis_object, solver_object)\n" +
+
+ # create the command to do the actual work
+ # was ssh -tt but then the shell wouldn't exit
+ " ssh_prefix = 'ssh -t ' + '" + self.username + "'+ '@' +'" + self.hostname + "'\n" +
+ " #command = 'EOT \\n' \n" +
+ " command = 'cd ' + '" + self.working_dir + "' + '/case \\n' \n" +
+ " command += './Allrun \\n' \n" +
+ " command += 'exit \\n' \n" +
+ " command += 'EOT' \n" +
+ " command = ssh_prefix + ' << ' + command + '\\n' \n" +
+ " print('doCommand command:' + command) \n" +
+ " cmd = CfdTools.makeRunCommand(command,None)\n" + # was " cmd = solver_runner.get_solver_cmd(solver_directory)\n" +
+ " print('doCommand cmd:')\n" +
+ " print(cmd)\n" +
+ " env_vars = solver_runner.getRunEnvironment()\n" +
+ " solver_process = CfdConsoleProcess.CfdConsoleProcess(stdout_hook=solver_runner.process_output)\n" +
+ " solver_process.start(cmd,env_vars= env_vars)\n" +
+ " solver_process.waitForFinished()")
+
+
+ """
+ # This was used for testing
+ # create the command to do the actual work
+ ssh_prefix = 'ssh -tt ' + self.username + '@' + self.hostname
+ command = 'EOT \n'
+ command += 'cd ' + self.working_dir + '/case \n'
+ command += './Allrun \n'
+ command += 'exit \n '
+ command += 'EOT \n'
+ command = ssh_prefix + ' << ' + command + ' \n'
+ print("Code command:" + command)
+ cmd = CfdTools.makeRunCommand(command,None) # was cmd = solver_runner.get_solver_cmd(solver_directory)\n" +
+ print("Code cmd:")
+ print(cmd)
+ """
+
+ # create the command to do the actual work
+ remote_working_dir = CfdTools.getDefaultOutputPath(self.profile_name)
+
+ command = 'ssh -t ' + self.username + '@' + self.hostname # was -tt
+ command += '<< EOT \n'
+ command += ' cd ' + remote_working_dir + '/case \n'
+ command += './Allrun \n'
+ command += 'exit \n '
+ command += 'EOT \n'
+ print("Code command:" + command)
+
+ #cmd = ['bash', '-c', command]
+ #print("Code cmd:")
+ #print(cmd)
+
+ cmd = CfdTools.makeRunCommand(command,None)
+
+ # TODO not sure this is correct anymore
+ working_dir = CfdTools.getOutputPath(self.analysis_object)
+ case_name = self.solver_object.InputCaseName
+ solver_directory = os.path.abspath(os.path.join(working_dir, case_name))
+
+
+ #cmd = self.solver_runner.get_solver_cmd(solver_directory)
+ env_vars = self.solver_runner.getRunEnvironment()
+ self.solver_object.Proxy.solver_process = CfdConsoleProcess(finished_hook=self.solverFinished,
+ stdout_hook=self.gotOutputLines,
+ stderr_hook=self.gotErrorLines)
+ self.solver_object.Proxy.solver_process.start(cmd, env_vars=env_vars)
+
if self.solver_object.Proxy.solver_process.waitForStarted():
# Setting solve button to inactive to ensure that two instances of the same simulation aren't started
# simultaneously
@@ -257,14 +537,66 @@ def runSolverProcess(self):
self.consoleMessage("Error starting solver", 'Error')
QApplication.restoreOverrideCursor()
+
def killSolverProcess(self):
self.consoleMessage("Solver manually stopped")
self.solver_object.Proxy.solver_process.terminate()
+ # TODO: kill the process on the server if we are running locally
# Note: solverFinished will still be called
def solverFinished(self, exit_code):
+ if self.form.cb_notify.isChecked():
+ #print("Beeping now")
+ QApplication.beep()
if exit_code == 0:
- self.consoleMessage("Simulation finished successfully")
+ self.consoleMessage("Simulation finished")
+
+ #check if there is work to do on the remote host
+ if self.profile_name != 'local':
+ # copy the solver case back to the workstation?
+ # this code is also used in TaskPanelCfdMesh.py for copying the mesh case to the workstation
+ if self.copy_back:
+ local_prefs = CfdTools.getPreferencesLocation()
+ profile_prefs = local_prefs +"/Hosts/" + self.profile_name
+
+ remote_user = FreeCAD.ParamGet(profile_prefs).GetString("Username", "")
+ remote_hostname = FreeCAD.ParamGet(profile_prefs).GetString("Hostname", "")
+ #remote_output_path = FreeCAD.ParamGet(profile_prefs).GetString("OutputPath","")
+ #local_output_path = FreeCAD.ParamGet(profile_prefs).GetString("OutputPath","")
+
+ remote_output_path = CfdTools.getDefaultOutputPath(self.profile_name)
+ local_output_path = CfdTools.getDefaultOutputPath('local')
+
+ # if we are deleting the solver and mesh case on the server
+ # if we delete the mesh case we'll need to remesh before running the solver
+
+ if self.delete_remote_results:
+ deleteStr = "--remove-source-files "
+ else:
+ deleteStr = ""
+
+ # rsync the solver case result on the server to the workstation's output directory
+ # Typical useage: rsync -r --delete --remove-source-files me@david/tmp/case /tmp
+ # --remove-source-files removes the files that get transfered
+ # --delete removes files from the destination that didn't get transfered
+
+ try:
+ CfdTools.runFoamCommand("rsync -r --delete " + deleteStr + remote_user + "@" + remote_hostname + ":" + remote_output_path + "/case " + \
+ local_output_path)
+ except Exception as e:
+ CfdTools.cfdMessage("Could not copy solver case back to local computer: " + str(e))
+ self.consoleMessage("Could not copy solver case back to local computer: " + str(e))
+
+ else:
+ CfdTools.cfdMessage("Copied solver case to " + local_output_path + "\n" )
+ self.consoleMessage("Copied solver case to " + local_output_path + "\n" )
+
+ # the mesh case is still on the server
+ # delete the mesh case result on the server ?
+ # for now we'll leave it there
+ if self.delete_remote_results:
+ pass
+
else:
self.consoleMessage("Simulation exited with error", 'Error')
self.solver_runner.solverFinished()
diff --git a/Gui/CfdPreferencePage.ui b/Gui/CfdPreferencePage.ui
index 3ecc267e..a62c6ee7 100644
--- a/Gui/CfdPreferencePage.ui
+++ b/Gui/CfdPreferencePage.ui
@@ -7,7 +7,7 @@
00560
- 909
+ 1158
@@ -34,6 +34,19 @@
6
+
+
+
+ true
+
+
+ The directory to which case folders are written. Used unless overridden on a per-analysis basis.
+
+
+ false
+
+
+
@@ -47,21 +60,21 @@
-
-
-
- true
-
-
- The OpenFOAM install folder (e.g. 'OpenFOAM-xxx'). Leave blank to use $WM_PROJECT_DIR environment setting or search standard locations.
+
+
+
+
+ 75
+ true
+
-
- false
+
+ ParaView executable path
-
-
+
+ true
@@ -70,8 +83,8 @@
-
-
+
+ 75
@@ -79,12 +92,22 @@
- Default output directory
+ gmsh executable path
-
-
+
+
+
+ true
+
+
+ ...
+
+
+
+
+ 75
@@ -92,7 +115,7 @@
- ParaView executable path
+ Default output directory
@@ -109,11 +132,8 @@
-
-
-
- true
-
+
+ ...
@@ -129,39 +149,26 @@
-
-
+
+ true
- The directory to which case folders are written. Used unless overridden on a per-analysis basis.
+ The OpenFOAM install folder (e.g. 'OpenFOAM-xxx'). Leave blank to use $WM_PROJECT_DIR environment setting or search standard locations.false
-
-
-
-
- 75
- true
-
-
-
- gmsh executable path
-
-
-
-
-
+
+
- ...
+ Add filename to output path ?
@@ -417,14 +424,14 @@
Docker Container
-
+ Use docker:
-
-
-
+
+
+
@@ -446,7 +453,7 @@
-
+
diff --git a/Gui/CfdRemotePreferencePage.ui b/Gui/CfdRemotePreferencePage.ui
new file mode 100644
index 00000000..f3aa1f28
--- /dev/null
+++ b/Gui/CfdRemotePreferencePage.ui
@@ -0,0 +1,870 @@
+
+
+ CfdRemotePreferencePage
+
+
+
+ 0
+ 0
+ 488
+ 1531
+
+
+
+ Remote Processing
+
+
+
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+
+
+
+ 12
+ 75
+ true
+
+
+
+
+
+
+ 1
+
+
+ Use Remote Processing
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ About Remote Processing
+
+
+
+
+
+
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ Add Profile
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ Delete Profile
+
+
+
+
+
+
+
+ 75
+ true
+
+
+
+ Host profile:
+
+
+
+
+
+
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ Username on remote host:
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ Remote hostname or IP address:
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ Ping Remote Host
+
+
+
+
+
+
+ Test SSH
+
+
+
+
+
+
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+ 0
+
+
+
+
+
+ 75
+ true
+
+
+
+ Meshing
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+
+ 75
+ true
+
+
+
+ OpenFOAM
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+
+
+ 8
+
+
+ 0
+
+
+ 8
+
+
+ 8
+
+
+
+
+ Processes
+
+
+
+
+
+
+ Threads
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+ Processes
+
+
+
+
+
+
+ Threads
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+ 14
+
+
+ 6
+
+
+
+
+ ...
+
+
+
+
+
+
+ Add filename to output path ?
+
+
+
+
+
+
+ true
+
+
+ The directory to which case folders are written. Used unless overridden on a per-analysis basis.
+
+
+ false
+
+
+
+
+
+
+
+ 75
+ true
+
+
+
+ Remote gmsh executable path
+
+
+
+
+
+
+ true
+
+
+ ...
+
+
+
+
+
+
+
+ 75
+ true
+
+
+
+ Remote ParaView executable path
+
+
+
+
+
+
+ true
+
+
+ The full path of the ParaView executable. Leave blank to use search path.
+
+
+ false
+
+
+
+
+
+
+ Copy mesh and solver results back to workstation ?
+
+
+
+
+
+
+ true
+
+
+ ...
+
+
+
+
+
+
+ true
+
+
+ The OpenFOAM install folder (e.g. 'OpenFOAM-xxx'). Leave blank to use $WM_PROJECT_DIR environment setting or search standard locations.
+
+
+ false
+
+
+
+
+
+
+ true
+
+
+ ...
+
+
+
+
+
+
+
+
+
+
+ 75
+ true
+
+
+
+ Remote OpenFOAM executable directory
+
+
+
+
+
+
+
+ 75
+ true
+
+
+
+ Remote output path
+
+
+
+
+
+
+ Delete mesh and server results on remote host ?
+
+
+
+
+
+
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+ 6
+
+
+ 14
+
+
+ 8
+
+
+
+
+ 0
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+ 75
+ true
+
+
+
+ Remote Software dependencies
+
+
+
+
+
+
+ true
+
+
+ Run remote host dependency checker
+
+
+
+
+
+
+
+
+
+ OpenFOAM
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ Choose existing file ...
+
+
+
+
+
+
+ Install OpenFOAM
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ URL:
+
+
+
+
+
+
+
+
+
+
+
+
+ ParaView
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ Choose existing file ...
+
+
+
+
+
+
+ Install ParaView
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ URL:
+
+
+
+
+
+
+
+
+
+
+
+
+ cfMesh
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ URL:
+
+
+
+
+
+
+ Install cfMesh on remote host
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ Choose existing file ...
+
+
+
+
+
+
+
+
+
+
+
+
+ HiSA
+
+
+
+
+
+ Install HiSA
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ URL:
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ Choose existing file ...
+
+
+
+
+
+
+
+
+
+
+
+
+ Docker Container
+
+
+
+
+
+ Use docker:
+
+
+
+
+
+
+
+
+
+ Download from URL:
+
+
+
+
+
+
+
+
+
+ Install Docker Container
+
+
+
+
+
+
+
+
+
+
+
+ 8
+
+
+ 5
+
+
+ 8
+
+
+ 8
+
+
+
+
+
+ 75
+ true
+
+
+
+ Output
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 120
+
+
+
+ QTextEdit::NoWrap
+
+
+
+
+
+
+
+
+ le_foam_dir
+ tb_choose_remote_foam_dir
+ le_paraview_path
+ tb_choose_paraview_path
+ le_gmsh_path
+ tb_choose_remote_gmsh_path
+ le_output_path
+ tb_choose_remote_output_dir
+ pb_run_dependency_checker
+ pb_download_install_openfoam
+ le_openfoam_url
+ tb_pick_openfoam_file
+ le_paraview_url
+ pb_download_install_paraview
+ tb_pick_paraview_file
+ le_cfmesh_url
+ pb_download_install_cfMesh
+ tb_pick_cfmesh_file
+ le_hisa_url
+ pb_download_install_hisa
+ tb_pick_hisa_file
+
+
+
+
diff --git a/Gui/Icons/preferences-cfdof.svg b/Gui/Icons/preferences-cfdof.svg
index 1f471823..b0298dda 100644
--- a/Gui/Icons/preferences-cfdof.svg
+++ b/Gui/Icons/preferences-cfdof.svg
@@ -1,62 +1,23 @@
-
-
-
-
-
-
- 75
- true
-
-
-
- Mesh Parameters
-
-
-
-
-
-
- Mesh utility:
-
-
-
@@ -112,6 +92,26 @@
+
+
+
+ Mesh utility:
+
+
+
+
+
+
+
+ 75
+ true
+
+
+
+ Mesh Parameters
+
+
+
@@ -132,6 +132,42 @@
0
+
+
+
+ Search
+
+
+
+
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 5
+
+
+
+
+
+
+
+
+ 50
+ false
+
+
+
+ Point in mesh
+
+
+
@@ -156,37 +192,6 @@
0
-
-
-
- Edge detection
-
-
-
-
-
-
- Implicit
-
-
-
-
-
-
- Explicit
-
-
-
-
-
-
- 1
-
-
- 100
-
-
-
@@ -217,45 +222,40 @@
+
+
+
+ Edge detection
+
+
+
+
+
+
+ 1
+
+
+ 100
+
+
+
+
+
+
+ Implicit
+
+
+
+
+
+
+ Explicit
+
+
+
-
-
-
-
- 50
- false
-
-
-
- Point in mesh
-
-
-
-
-
-
- Qt::Vertical
-
-
- QSizePolicy::Fixed
-
-
-
- 20
- 5
-
-
-
-
-
-
-
- Search
-
-
-
@@ -380,7 +380,52 @@
-
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ Host
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+ 8
@@ -396,44 +441,52 @@
8
-
-
+
+
- Stop
+ Delete mesh case on host
-
-
+
+
- Run mesher
+ Copy local mesh case to host
-
-
-
-
- 75
- true
-
-
+
+
- Meshing
+ Run mesh case
-
+ Write mesh case
-
+
- Edit
+ Edit mesh case
+
+
+
+
+
+
+ Stop
+
+
+
+
+
+
+ Notify when Run mesh is complete
@@ -462,7 +515,7 @@
- Clear
+ Clear surface mesh
diff --git a/Gui/TaskPanelCfdSolverControl.ui b/Gui/TaskPanelCfdSolverControl.ui
index 1f003858..889a357b 100644
--- a/Gui/TaskPanelCfdSolverControl.ui
+++ b/Gui/TaskPanelCfdSolverControl.ui
@@ -6,8 +6,8 @@
00
- 366
- 563
+ 612
+ 1067
@@ -15,29 +15,20 @@
-
-
- 8
-
-
- 8
-
-
- 8
+
+
+
+ 75
+ true
+
-
- 8
-
-
- 14
-
-
- 6
+
+ OpenFOAM Solver
-
+
-
+ 8
@@ -48,33 +39,22 @@
8
- 6
+ 814
- 8
+ 6
-
-
-
- Write
-
-
-
-
-
-
- false
-
-
- Edit
-
-
-
-
+
+
+
+ 0
+ 0
+
+ 75
@@ -82,14 +62,33 @@
- Case setup
+ Host
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+
+
+
+ -1
-
+ 8
@@ -108,94 +107,47 @@
8
-
-
-
- false
-
+
+
- Stop
+ Write OF case
-
-
+
+
- false
+ true
- Run
-
-
-
-
-
-
-
- 75
- true
-
-
-
- Solver
+ Stop
-
-
-
-
-
- 8
-
-
- 8
-
-
- 8
-
-
- 6
-
-
- 14
-
-
- 8
-
-
-
+
+
- false
+ true
- Paraview
+ Edit
-
-
-
-
- 75
- true
-
+
+
+
+ true
-
+ Run OF case
-
-
-
-
- 75
- true
-
-
+
+
- Results
+ Notify when Run OF case is done
@@ -215,6 +167,64 @@
8
+
+
+
+ 8
+
+
+ 8
+
+
+ 8
+
+
+ 6
+
+
+ 14
+
+
+ 8
+
+
+
+
+
+ 75
+ true
+
+
+
+ Results
+
+
+
+
+
+
+
+ 75
+ true
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ Paraview
+
+
+
+
+
diff --git a/InitGui.py b/InitGui.py
index e5a69d63..28f97b29 100644
--- a/InitGui.py
+++ b/InitGui.py
@@ -31,6 +31,7 @@ def __init__(self):
from CfdOF import CfdTools
from PySide import QtCore
from CfdOF.CfdPreferencePage import CfdPreferencePage
+ from CfdOF.CfdRemotePreferencePage import CfdRemotePreferencePage
icon_path = os.path.join(CfdTools.getModulePath(), "Gui", "Icons", "cfd.svg")
self.__class__.Icon = icon_path
@@ -40,6 +41,7 @@ def __init__(self):
icons_path = os.path.join(CfdTools.getModulePath(), "Gui", "Icons")
QtCore.QDir.addSearchPath("icons", icons_path)
FreeCADGui.addPreferencePage(CfdPreferencePage, "CfdOF")
+ FreeCADGui.addPreferencePage(CfdRemotePreferencePage, "CfdOF")
def Initialize(self):
# Must import QtCore in this function, not at the beginning of this file for translation support
diff --git a/TestCfdOF.py b/TestCfdOF.py
old mode 100644
new mode 100755