| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | __title__ = "FreeCAD FEM solver calculix ccx tools task panel for the document object" |
| | __author__ = "Bernd Hahnebach" |
| | __url__ = "https://www.freecad.org" |
| |
|
| | |
| | |
| | |
| |
|
| | import os |
| | import time |
| | from PySide import QtCore |
| | from PySide import QtGui |
| | from PySide.QtCore import Qt |
| | from PySide.QtGui import QApplication |
| |
|
| | import FreeCAD |
| | import FreeCADGui |
| |
|
| | import FemGui |
| | from femtools.femutils import getOutputWinColor |
| |
|
| |
|
| | def unicode(text, *args): |
| | return str(text) |
| |
|
| |
|
| | class _TaskPanel: |
| | """ |
| | The TaskPanel for CalculiX ccx tools solver object |
| | """ |
| |
|
| | PREFS_PATH = "User parameter:BaseApp/Preferences/Mod/Fem/Ccx" |
| |
|
| | def __init__(self, solver_object): |
| | self.form = FreeCADGui.PySideUic.loadUi( |
| | FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/SolverCcxTools.ui" |
| | ) |
| |
|
| | from femtools.ccxtools import CcxTools as ccx |
| |
|
| | |
| | |
| | |
| | |
| | |
| | self.fea = ccx(solver_object) |
| | self.fea.setup_working_dir() |
| | try: |
| | self.fea.setup_ccx() |
| | except FileNotFoundError as e: |
| | FreeCAD.Console.PrintWarning(e.args[0]) |
| |
|
| | self.Calculix = QtCore.QProcess() |
| | self.Timer = QtCore.QTimer() |
| | self.Timer.start(300) |
| |
|
| | self.fem_console_message = "" |
| |
|
| | self.CCX_pipeline = None |
| | self.CCX_mesh_visibility = False |
| |
|
| | |
| | CCX_mesh = self.fea.analysis.Document.getObject("CCX_Results_Mesh") |
| | if CCX_mesh is not None: |
| | self.CCX_mesh_visibility = CCX_mesh.ViewObject.Visibility |
| |
|
| | |
| | QtCore.QObject.connect( |
| | self.form.tb_choose_working_dir, QtCore.SIGNAL("clicked()"), self.choose_working_dir |
| | ) |
| | QtCore.QObject.connect( |
| | self.form.pb_write_inp, QtCore.SIGNAL("clicked()"), self.write_input_file_handler |
| | ) |
| | QtCore.QObject.connect( |
| | self.form.pb_edit_inp, QtCore.SIGNAL("clicked()"), self.editCalculixInputFile |
| | ) |
| | |
| | |
| | QtCore.QObject.connect(self.form.pb_run_ccx, QtCore.SIGNAL("clicked()"), self.stopCalculix) |
| | QtCore.QObject.connect(self.form.pb_run_ccx, QtCore.SIGNAL("clicked()"), self.runCalculix) |
| | QtCore.QObject.connect( |
| | self.form.rb_static_analysis, QtCore.SIGNAL("clicked()"), self.select_static_analysis |
| | ) |
| | QtCore.QObject.connect( |
| | self.form.rb_frequency_analysis, |
| | QtCore.SIGNAL("clicked()"), |
| | self.select_frequency_analysis, |
| | ) |
| | QtCore.QObject.connect( |
| | self.form.rb_thermomech_analysis, |
| | QtCore.SIGNAL("clicked()"), |
| | self.select_thermomech_analysis, |
| | ) |
| | QtCore.QObject.connect( |
| | self.form.rb_check_mesh, QtCore.SIGNAL("clicked()"), self.select_check_mesh |
| | ) |
| | QtCore.QObject.connect( |
| | self.form.rb_buckling_analysis, |
| | QtCore.SIGNAL("clicked()"), |
| | self.select_buckling_analysis, |
| | ) |
| | QtCore.QObject.connect(self.Calculix, QtCore.SIGNAL("started()"), self.calculixStarted) |
| | QtCore.QObject.connect( |
| | self.Calculix, |
| | QtCore.SIGNAL("stateChanged(QProcess::ProcessState)"), |
| | self.calculixStateChanged, |
| | ) |
| | QtCore.QObject.connect( |
| | self.Calculix, QtCore.SIGNAL("error(QProcess::ProcessError)"), self.calculixError |
| | ) |
| | QtCore.QObject.connect( |
| | self.Calculix, |
| | QtCore.SIGNAL("finished(int, QProcess::ExitStatus)"), |
| | self.calculixFinished, |
| | ) |
| | QtCore.QObject.connect(self.Timer, QtCore.SIGNAL("timeout()"), self.UpdateText) |
| |
|
| | self.update() |
| |
|
| | def getStandardButtons(self): |
| | |
| | |
| | return QtGui.QDialogButtonBox.Close |
| |
|
| | def reject(self): |
| | FreeCADGui.ActiveDocument.resetEdit() |
| |
|
| | def update(self): |
| | "fills the widgets" |
| | self.form.le_working_dir.setText(self.fea.working_dir) |
| | if self.fea.solver.AnalysisType == "static": |
| | self.form.rb_static_analysis.setChecked(True) |
| | elif self.fea.solver.AnalysisType == "frequency": |
| | self.form.rb_frequency_analysis.setChecked(True) |
| | elif self.fea.solver.AnalysisType == "thermomech": |
| | self.form.rb_thermomech_analysis.setChecked(True) |
| | elif self.fea.solver.AnalysisType == "check": |
| | self.form.rb_check_mesh.setChecked(True) |
| | elif self.fea.solver.AnalysisType == "buckling": |
| | self.form.rb_buckling_analysis.setChecked(True) |
| | return |
| |
|
| | def femConsoleMessage(self, message="", outputwin_color_type=None): |
| | self.fem_console_message = self.fem_console_message + ( |
| | '<font color="{}"><b>{:4.1f}:</b></font> '.format( |
| | getOutputWinColor("Logging"), time.time() - self.Start |
| | ) |
| | ) |
| | if outputwin_color_type: |
| | if outputwin_color_type == "#00AA00": |
| | self.fem_console_message += '<font color="{}">{}</font><br>'.format( |
| | outputwin_color_type, message |
| | ) |
| | else: |
| | self.fem_console_message += '<font color="{}">{}</font><br>'.format( |
| | getOutputWinColor(outputwin_color_type), message |
| | ) |
| | else: |
| | self.fem_console_message += message + "<br>" |
| | self.form.textEdit_Output.setText(self.fem_console_message) |
| | self.form.textEdit_Output.moveCursor(QtGui.QTextCursor.End) |
| |
|
| | def printCalculiXstdout(self): |
| |
|
| | out = self.Calculix.readAllStandardOutput() |
| | |
| | |
| |
|
| | if out.isEmpty(): |
| | self.femConsoleMessage("CalculiX stdout is empty", "Error") |
| | return False |
| |
|
| | |
| | |
| | out = out.data().decode() |
| | out = os.linesep.join([s for s in out.splitlines() if s]) |
| | out = out.replace("\n", "<br>") |
| | |
| | self.femConsoleMessage(out) |
| |
|
| | if "*ERROR in e_c3d: nonpositive jacobian" in out: |
| | error_message = ( |
| | "\n\nCalculiX returned an error due to " |
| | "nonpositive jacobian determinant in at least one element\n" |
| | "Use the run button on selected solver to get a better error output.\n" |
| | ) |
| | FreeCAD.Console.PrintError(error_message) |
| |
|
| | if "*ERROR" in out: |
| | return False |
| | else: |
| | return True |
| |
|
| | def UpdateText(self): |
| | if self.Calculix.state() == QtCore.QProcess.ProcessState.Running: |
| | self.form.l_time.setText(f"Time: {time.time() - self.Start:4.1f}: ") |
| |
|
| | def calculixError(self, error=""): |
| | print(f"Error() {error}") |
| | self.femConsoleMessage(f"CalculiX execute error: {error}", "Error") |
| |
|
| | def calculixNoError(self): |
| | print("CalculiX done without error!") |
| | self.femConsoleMessage( |
| | "CalculiX done without error!", "#00AA00" |
| | ) |
| |
|
| | def calculixStarted(self): |
| | |
| | FreeCAD.Console.PrintLog(f"calculix state: {self.Calculix.state()}\n") |
| | self.form.pb_run_ccx.setText("Stop CalculiX") |
| |
|
| | def calculixStateChanged(self, newState): |
| | if newState == QtCore.QProcess.ProcessState.Starting: |
| | self.femConsoleMessage("Starting CalculiX...") |
| | elif newState == QtCore.QProcess.ProcessState.Running: |
| | self.femConsoleMessage("CalculiX is running...") |
| | elif newState == QtCore.QProcess.ProcessState.NotRunning: |
| | self.femConsoleMessage("CalculiX stopped.") |
| | else: |
| | self.femConsoleMessage("Problems.", "Error") |
| |
|
| | def calculixFinished(self, exitCode, exitStatus): |
| | |
| | FreeCAD.Console.PrintLog(f"calculix state: {self.Calculix.state()}\n") |
| |
|
| | |
| | QtCore.QDir.setCurrent(self.cwd) |
| |
|
| | self.Timer.stop() |
| |
|
| | self.form.pb_run_ccx.setText("Re-run CalculiX") |
| |
|
| | if exitStatus != QtCore.QProcess.ExitStatus.NormalExit: |
| | return |
| |
|
| | if self.printCalculiXstdout(): |
| | self.calculixNoError() |
| | else: |
| | self.calculixError() |
| |
|
| | self.femConsoleMessage("Loading result sets...") |
| | self.form.l_time.setText(f"Time: {time.time() - self.Start:4.1f}: ") |
| | self.fea.reset_mesh_purge_results_checked() |
| | self.fea.inp_file_name = self.fea.inp_file_name |
| |
|
| | |
| | |
| | |
| | |
| | |
| | majorVersion, minorVersion = self.fea.get_ccx_version() |
| | if majorVersion == 2 and minorVersion <= 10: |
| | message = ( |
| | "The used CalculiX version {}.{} creates broken output files. " |
| | "The result file will not be read by FreeCAD FEM. " |
| | "You still can try to read it stand alone with FreeCAD, but it is " |
| | "strongly recommended to upgrade CalculiX to a newer version.\n".format( |
| | majorVersion, minorVersion |
| | ) |
| | ) |
| | QtGui.QMessageBox.warning(None, "Upgrade CalculiX", message) |
| | raise |
| |
|
| | QApplication.setOverrideCursor(Qt.WaitCursor) |
| | try: |
| | self.fea.load_results() |
| | except Exception as e: |
| | FreeCAD.Console.PrintError("loading results failed\n") |
| | FreeCAD.Console.PrintError(e) |
| |
|
| | QApplication.restoreOverrideCursor() |
| | self.form.l_time.setText(f"Time: {time.time() - self.Start:4.1f}: ") |
| |
|
| | |
| | CCX_mesh = self.fea.analysis.Document.getObject("ResultMesh") |
| | if CCX_mesh is not None: |
| | CCX_mesh.ViewObject.Visibility = self.CCX_mesh_visibility |
| |
|
| | def choose_working_dir(self): |
| | wd = QtGui.QFileDialog.getExistingDirectory( |
| | None, "Choose CalculiX working directory", self.fea.working_dir |
| | ) |
| | if os.path.isdir(wd): |
| | self.fea.setup_working_dir(wd) |
| | self.form.le_working_dir.setText(self.fea.working_dir) |
| |
|
| | def write_input_file_handler(self): |
| | self.Start = time.time() |
| | self.form.l_time.setText(f"Time: {time.time() - self.Start:4.1f}: ") |
| | QApplication.restoreOverrideCursor() |
| | if self.check_prerequisites_helper(): |
| | QApplication.setOverrideCursor(Qt.WaitCursor) |
| | self.fea.write_inp_file() |
| | if self.fea.inp_file_name != "": |
| | self.femConsoleMessage("Write completed.") |
| | self.form.pb_edit_inp.setEnabled(True) |
| | self.form.pb_run_ccx.setEnabled(True) |
| | else: |
| | self.femConsoleMessage("Write .inp file failed!", "Error") |
| | QApplication.restoreOverrideCursor() |
| | self.form.l_time.setText(f"Time: {time.time() - self.Start:4.1f}: ") |
| |
|
| | def check_prerequisites_helper(self): |
| | self.Start = time.time() |
| | self.femConsoleMessage("Check dependencies...") |
| | self.form.l_time.setText(f"Time: {time.time() - self.Start:4.1f}: ") |
| |
|
| | self.fea.update_objects() |
| | message = self.fea.check_prerequisites() |
| | if message != "": |
| | QtGui.QMessageBox.critical(None, "Missing prerequisite(s)", message) |
| | return False |
| | return True |
| |
|
| | def start_ext_editor(self, ext_editor_path, filename): |
| | if not hasattr(self, "ext_editor_process"): |
| | self.ext_editor_process = QtCore.QProcess() |
| | if self.ext_editor_process.state() != QtCore.QProcess.Running: |
| | self.ext_editor_process.start(ext_editor_path, [filename]) |
| |
|
| | def editCalculixInputFile(self): |
| | print(f"editCalculixInputFile {self.fea.inp_file_name}") |
| | ccx_prefs = FreeCAD.ParamGet(self.PREFS_PATH) |
| | if ccx_prefs.GetBool("UseInternalEditor", True): |
| | FemGui.open(self.fea.inp_file_name) |
| | else: |
| | ext_editor_path = ccx_prefs.GetString("ExternalEditorPath", "") |
| | if ext_editor_path: |
| | self.start_ext_editor(ext_editor_path, self.fea.inp_file_name) |
| | else: |
| | print( |
| | "External editor is not defined in FEM preferences. " |
| | "Falling back to internal editor" |
| | ) |
| | FemGui.open(self.fea.inp_file_name) |
| |
|
| | def runCalculix(self): |
| | if self.Calculix.state() == QtCore.QProcess.ProcessState.NotRunning: |
| | if self.fea.ccx_binary_present is False: |
| | self.femConsoleMessage( |
| | "CalculiX can not be started. Missing or incorrect CalculiX binary: {}".format( |
| | self.fea.ccx_binary |
| | ) |
| | ) |
| | |
| | return |
| | |
| | self.Start = time.time() |
| |
|
| | self.femConsoleMessage(f"CalculiX binary: {self.fea.ccx_binary}") |
| | self.femConsoleMessage(f"CalculiX input file: {self.fea.inp_file_name}") |
| | self.femConsoleMessage("Run CalculiX...") |
| |
|
| | FreeCAD.Console.PrintMessage( |
| | f"run CalculiX at: {self.fea.ccx_binary} with: {self.fea.inp_file_name}\n" |
| | ) |
| | |
| | |
| |
|
| | |
| | ccx_prefs = FreeCAD.ParamGet(self.PREFS_PATH) |
| | env = QtCore.QProcessEnvironment.systemEnvironment() |
| | num_cpu_pref = ccx_prefs.GetInt("AnalysisNumCPUs", QtCore.QThread.idealThreadCount()) |
| | env.insert("OMP_NUM_THREADS", str(num_cpu_pref)) |
| | pastix_prec = "1" if self.fea.solver.PastixMixedPrecision else "0" |
| | env.insert("PASTIX_MIXED_PRECISION", pastix_prec) |
| | self.Calculix.setProcessEnvironment(env) |
| |
|
| | self.cwd = QtCore.QDir.currentPath() |
| | fi = QtCore.QFileInfo(self.fea.inp_file_name) |
| | QtCore.QDir.setCurrent(fi.path()) |
| | self.Calculix.start(self.fea.ccx_binary, ["-i", fi.baseName()]) |
| |
|
| | QApplication.restoreOverrideCursor() |
| |
|
| | def stopCalculix(self): |
| | if self.Calculix.state() == QtCore.QProcess.ProcessState.Running: |
| | self.Calculix.kill() |
| |
|
| | def select_analysis_type(self, analysis_type): |
| | if self.fea.solver.AnalysisType != analysis_type: |
| | self.fea.solver.AnalysisType = analysis_type |
| | self.form.pb_edit_inp.setEnabled(False) |
| | self.form.pb_run_ccx.setEnabled(False) |
| |
|
| | def select_static_analysis(self): |
| | self.select_analysis_type("static") |
| |
|
| | def select_frequency_analysis(self): |
| | self.select_analysis_type("frequency") |
| |
|
| | def select_thermomech_analysis(self): |
| | self.select_analysis_type("thermomech") |
| |
|
| | def select_check_mesh(self): |
| | self.select_analysis_type("check") |
| |
|
| | def select_buckling_analysis(self): |
| | self.select_analysis_type("buckling") |
| |
|