Fem: SolverCalculiX object refactor
This commit is contained in:
@@ -203,6 +203,7 @@ SET(FemObjects_SRCS
|
|||||||
femobjects/mesh_region.py
|
femobjects/mesh_region.py
|
||||||
femobjects/mesh_result.py
|
femobjects/mesh_result.py
|
||||||
femobjects/result_mechanical.py
|
femobjects/result_mechanical.py
|
||||||
|
femobjects/solver_calculix.py
|
||||||
femobjects/solver_ccxtools.py
|
femobjects/solver_ccxtools.py
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -227,6 +228,7 @@ SET(FemSolver_SRCS
|
|||||||
|
|
||||||
SET(FemSolverCalculix_SRCS
|
SET(FemSolverCalculix_SRCS
|
||||||
femsolver/calculix/__init__.py
|
femsolver/calculix/__init__.py
|
||||||
|
femsolver/calculix/calculixtools.py
|
||||||
femsolver/calculix/solver.py
|
femsolver/calculix/solver.py
|
||||||
femsolver/calculix/tasks.py
|
femsolver/calculix/tasks.py
|
||||||
femsolver/calculix/write_constraint_bodyheatsource.py
|
femsolver/calculix/write_constraint_bodyheatsource.py
|
||||||
@@ -603,6 +605,7 @@ SET(FemGuiTaskPanels_SRCS
|
|||||||
femtaskpanels/task_mesh_region.py
|
femtaskpanels/task_mesh_region.py
|
||||||
femtaskpanels/task_mesh_netgen.py
|
femtaskpanels/task_mesh_netgen.py
|
||||||
femtaskpanels/task_result_mechanical.py
|
femtaskpanels/task_result_mechanical.py
|
||||||
|
femtaskpanels/task_solver_calculix.py
|
||||||
femtaskpanels/task_solver_ccxtools.py
|
femtaskpanels/task_solver_ccxtools.py
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -652,6 +655,7 @@ SET(FemGuiViewProvider_SRCS
|
|||||||
femviewprovider/view_mesh_region.py
|
femviewprovider/view_mesh_region.py
|
||||||
femviewprovider/view_mesh_result.py
|
femviewprovider/view_mesh_result.py
|
||||||
femviewprovider/view_result_mechanical.py
|
femviewprovider/view_result_mechanical.py
|
||||||
|
femviewprovider/view_solver_calculix.py
|
||||||
femviewprovider/view_solver_ccxtools.py
|
femviewprovider/view_solver_ccxtools.py
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -429,6 +429,7 @@ SET(FemGuiPythonUI_SRCS
|
|||||||
Resources/ui/ResultHints.ui
|
Resources/ui/ResultHints.ui
|
||||||
Resources/ui/ResultShow.ui
|
Resources/ui/ResultShow.ui
|
||||||
Resources/ui/SolverCalculix.ui
|
Resources/ui/SolverCalculix.ui
|
||||||
|
Resources/ui/SolverCalculiX.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
ADD_CUSTOM_TARGET(FemPythonUi ALL
|
ADD_CUSTOM_TARGET(FemPythonUi ALL
|
||||||
|
|||||||
144
src/Mod/Fem/Gui/Resources/ui/SolverCalculiX.ui
Normal file
144
src/Mod/Fem/Gui/Resources/ui/SolverCalculiX.ui
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>SolverCalculiX</class>
|
||||||
|
<widget class="QWidget" name="SolverCalculiX">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>475</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Solver CalculiX Control</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="ckb_working_directory">
|
||||||
|
<property name="text">
|
||||||
|
<string>Working Directory</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="gpb_working_directory">
|
||||||
|
<property name="title">
|
||||||
|
<string></string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="vbl_working_directory">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="hbl_working_directory">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pb_write_input">
|
||||||
|
<property name="text">
|
||||||
|
<string>Write</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pb_edit_input">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Edit</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="hbl_working_directory">
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="let_working_directory">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Path to working directory</string>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="pb_working_directory">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="gpb_solver_params">
|
||||||
|
<property name="title">
|
||||||
|
<string>Solver Parameters</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout_1">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="lbl_analysis_type">
|
||||||
|
<property name="text">
|
||||||
|
<string>Analysis Type:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="cb_analysis_type">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="gb_solver_log">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||||
|
<item>
|
||||||
|
<widget class="QTextEdit" name="te_output">
|
||||||
|
<property name="lineWrapMode">
|
||||||
|
<enum>QTextEdit::NoWrap</enum>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="l_time">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>12</pointsize>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Time:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pb_solver_version">
|
||||||
|
<property name="text">
|
||||||
|
<string>Solver Version</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -795,12 +795,17 @@ def makeSolverCalculiXCcxTools(doc, name="SolverCcxTools"):
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def makeSolverCalculix(doc, name="SolverCalculix"):
|
def makeSolverCalculiX(doc, name="SolverCalculiX"):
|
||||||
"""makeSolverCalculix(document, [name]):
|
"""makeSolverCalculiX(document, [name]):
|
||||||
makes a Calculix solver object"""
|
makes a Calculix solver object"""
|
||||||
import femsolver.calculix.solver
|
obj = doc.addObject("Fem::FemSolverObjectPython", name)
|
||||||
|
from femobjects import solver_calculix
|
||||||
|
|
||||||
obj = femsolver.calculix.solver.create(doc, name)
|
solver_calculix.SolverCalculiX(obj)
|
||||||
|
if FreeCAD.GuiUp:
|
||||||
|
from femviewprovider import view_solver_calculix
|
||||||
|
|
||||||
|
view_solver_calculix.VPSolverCalculiX(obj.ViewObject)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -630,7 +630,7 @@ class _MaterialMechanicalNonlinear(CommandManager):
|
|||||||
# CalculiX solver or new frame work CalculiX solver
|
# CalculiX solver or new frame work CalculiX solver
|
||||||
if solver_object and (
|
if solver_object and (
|
||||||
is_of_type(solver_object, "Fem::SolverCcxTools")
|
is_of_type(solver_object, "Fem::SolverCcxTools")
|
||||||
or is_of_type(solver_object, "Fem::SolverCalculix")
|
or is_of_type(solver_object, "Fem::SolverCalculiX")
|
||||||
):
|
):
|
||||||
FreeCAD.Console.PrintMessage(
|
FreeCAD.Console.PrintMessage(
|
||||||
f"Set MaterialNonlinearity to nonlinear for {solver_object.Label}\n"
|
f"Set MaterialNonlinearity to nonlinear for {solver_object.Label}\n"
|
||||||
@@ -938,7 +938,7 @@ class _SolverCalculixContextManager:
|
|||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
ccx_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Ccx")
|
ccx_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Ccx")
|
||||||
FreeCAD.ActiveDocument.openTransaction("Create SolverCalculix")
|
FreeCAD.ActiveDocument.openTransaction("Create SolverCalculiX")
|
||||||
FreeCADGui.addModule("ObjectsFem")
|
FreeCADGui.addModule("ObjectsFem")
|
||||||
FreeCADGui.addModule("FemGui")
|
FreeCADGui.addModule("FemGui")
|
||||||
FreeCADGui.doCommand(
|
FreeCADGui.doCommand(
|
||||||
@@ -1051,8 +1051,8 @@ class _SolverCcxTools(CommandManager):
|
|||||||
FreeCADGui.doCommand(f"{cm.cli_name}.MaterialNonlinearity = 'nonlinear'")
|
FreeCADGui.doCommand(f"{cm.cli_name}.MaterialNonlinearity = 'nonlinear'")
|
||||||
|
|
||||||
|
|
||||||
class _SolverCalculix(CommandManager):
|
class _SolverCalculiX(CommandManager):
|
||||||
"The FEM_SolverCalculix command definition"
|
"The FEM_SolverCalculiX command definition"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -1068,7 +1068,13 @@ class _SolverCalculix(CommandManager):
|
|||||||
self.is_active = "with_analysis"
|
self.is_active = "with_analysis"
|
||||||
|
|
||||||
def Activated(self):
|
def Activated(self):
|
||||||
with _SolverCalculixContextManager("makeSolverCalculix", "solver") as cm:
|
ccx_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Ccx")
|
||||||
|
if ccx_prefs.GetBool("ResultAsPipeline", False):
|
||||||
|
make_solver = "makeSolverCalculiX"
|
||||||
|
else:
|
||||||
|
make_solver = "makeSolverCalculiXCcxTools"
|
||||||
|
|
||||||
|
with _SolverCalculixContextManager(make_solver, "solver") as cm:
|
||||||
has_nonlinear_material_obj = False
|
has_nonlinear_material_obj = False
|
||||||
for m in self.active_analysis.Group:
|
for m in self.active_analysis.Group:
|
||||||
if is_of_type(m, "Fem::MaterialMechanicalNonlinear"):
|
if is_of_type(m, "Fem::MaterialMechanicalNonlinear"):
|
||||||
@@ -1227,7 +1233,7 @@ FreeCADGui.addCommand("FEM_MeshRegion", _MeshRegion())
|
|||||||
FreeCADGui.addCommand("FEM_ResultShow", _ResultShow())
|
FreeCADGui.addCommand("FEM_ResultShow", _ResultShow())
|
||||||
FreeCADGui.addCommand("FEM_ResultsPurge", _ResultsPurge())
|
FreeCADGui.addCommand("FEM_ResultsPurge", _ResultsPurge())
|
||||||
FreeCADGui.addCommand("FEM_SolverCalculiXCcxTools", _SolverCcxTools())
|
FreeCADGui.addCommand("FEM_SolverCalculiXCcxTools", _SolverCcxTools())
|
||||||
FreeCADGui.addCommand("FEM_SolverCalculiX", _SolverCalculix())
|
FreeCADGui.addCommand("FEM_SolverCalculiX", _SolverCalculiX())
|
||||||
FreeCADGui.addCommand("FEM_SolverControl", _SolverControl())
|
FreeCADGui.addCommand("FEM_SolverControl", _SolverControl())
|
||||||
FreeCADGui.addCommand("FEM_SolverElmer", _SolverElmer())
|
FreeCADGui.addCommand("FEM_SolverElmer", _SolverElmer())
|
||||||
FreeCADGui.addCommand("FEM_SolverMystran", _SolverMystran())
|
FreeCADGui.addCommand("FEM_SolverMystran", _SolverMystran())
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class MeshSetsGetter:
|
|||||||
# TODO somehow this is not smart, pur mesh objects might be used often
|
# TODO somehow this is not smart, pur mesh objects might be used often
|
||||||
if self.member.geos_beamsection and (
|
if self.member.geos_beamsection and (
|
||||||
type_of_obj(self.solver_obj) == "Fem::SolverCcxTools"
|
type_of_obj(self.solver_obj) == "Fem::SolverCcxTools"
|
||||||
or type_of_obj(self.solver_obj) == "Fem::SolverCalculix"
|
or type_of_obj(self.solver_obj) == "Fem::SolverCalculiX"
|
||||||
):
|
):
|
||||||
FreeCAD.Console.PrintError(
|
FreeCAD.Console.PrintError(
|
||||||
"The mesh does not know the geometry it is made from. "
|
"The mesh does not know the geometry it is made from. "
|
||||||
|
|||||||
46
src/Mod/Fem/femobjects/solver_calculix.py
Normal file
46
src/Mod/Fem/femobjects/solver_calculix.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
|
||||||
|
# ***************************************************************************
|
||||||
|
# * Copyright (c) 2017 Bernd Hahnebach <bernd@bimstatik.org> *
|
||||||
|
# * Copyright (c) 2025 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
|
||||||
|
# * *
|
||||||
|
# * This file is part of FreeCAD. *
|
||||||
|
# * *
|
||||||
|
# * FreeCAD 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 2.1 of the *
|
||||||
|
# * License, or (at your option) any later version. *
|
||||||
|
# * *
|
||||||
|
# * FreeCAD 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 FreeCAD. If not, see *
|
||||||
|
# * <https://www.gnu.org/licenses/>. *
|
||||||
|
# * *
|
||||||
|
# ***************************************************************************
|
||||||
|
|
||||||
|
__title__ = "FreeCAD FEM solver CalculiX document object"
|
||||||
|
__author__ = "Bernd Hahnebach, Mario Passaglia"
|
||||||
|
__url__ = "https://www.freecad.org"
|
||||||
|
|
||||||
|
## @package solver_calculix
|
||||||
|
# \ingroup FEM
|
||||||
|
# \brief solver CalculiX object
|
||||||
|
|
||||||
|
from . import base_fempythonobject
|
||||||
|
from femsolver.calculix.solver import _BaseSolverCalculix
|
||||||
|
|
||||||
|
|
||||||
|
class SolverCalculiX(base_fempythonobject.BaseFemPythonObject, _BaseSolverCalculix):
|
||||||
|
|
||||||
|
Type = "Fem::SolverCalculiX"
|
||||||
|
|
||||||
|
def __init__(self, obj):
|
||||||
|
super().__init__(obj)
|
||||||
|
self.add_attributes(obj)
|
||||||
|
|
||||||
|
def onDocumentRestored(self, obj):
|
||||||
|
self.on_restore_of_document(obj)
|
||||||
190
src/Mod/Fem/femsolver/calculix/calculixtools.py
Normal file
190
src/Mod/Fem/femsolver/calculix/calculixtools.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
|
||||||
|
# ***************************************************************************
|
||||||
|
# * Copyright (c) 2025 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
|
||||||
|
# * *
|
||||||
|
# * This file is part of FreeCAD. *
|
||||||
|
# * *
|
||||||
|
# * FreeCAD 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 2.1 of the *
|
||||||
|
# * License, or (at your option) any later version. *
|
||||||
|
# * *
|
||||||
|
# * FreeCAD 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 FreeCAD. If not, see *
|
||||||
|
# * <https://www.gnu.org/licenses/>. *
|
||||||
|
# * *
|
||||||
|
# ***************************************************************************
|
||||||
|
|
||||||
|
__title__ = "Tools for the work with CalculiX solver"
|
||||||
|
__author__ = "Mario Passaglia"
|
||||||
|
__url__ = "https://www.freecad.org"
|
||||||
|
|
||||||
|
|
||||||
|
from PySide.QtCore import QProcess, QThread
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
import FreeCAD
|
||||||
|
import Fem
|
||||||
|
|
||||||
|
from . import writer
|
||||||
|
from .. import settings
|
||||||
|
|
||||||
|
# from feminout import importCcxDatResults
|
||||||
|
from femmesh import meshsetsgetter
|
||||||
|
from femtools import membertools
|
||||||
|
|
||||||
|
|
||||||
|
class CalculiXTools:
|
||||||
|
|
||||||
|
frd_var_conversion = {
|
||||||
|
"CONTACT": "Contact Displacement",
|
||||||
|
"PE": "Plastic Strain",
|
||||||
|
"CELS": "Contact Energy",
|
||||||
|
"ECD": "Current Density",
|
||||||
|
"EMFB": "Magnetic Field",
|
||||||
|
"EMFE": "Electric Field",
|
||||||
|
"ENER": "Internal Energy Density",
|
||||||
|
"FLUX": "Heat Flux",
|
||||||
|
"DISP": "Displacement",
|
||||||
|
"T": "Temperature",
|
||||||
|
"TOSTRAIN": "Strain",
|
||||||
|
"STRESS": "Stress",
|
||||||
|
"STR(%)": "Error",
|
||||||
|
}
|
||||||
|
|
||||||
|
name = "CalculiX"
|
||||||
|
|
||||||
|
def __init__(self, obj):
|
||||||
|
self.obj = obj
|
||||||
|
self.process = QProcess()
|
||||||
|
self.model_file = ""
|
||||||
|
self.analysis = obj.getParentGroup()
|
||||||
|
self.fem_param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem")
|
||||||
|
self._create_working_directory(obj)
|
||||||
|
|
||||||
|
def _create_working_directory(self, obj):
|
||||||
|
"""
|
||||||
|
Create working directory according to preferences
|
||||||
|
"""
|
||||||
|
if not os.path.isdir(obj.WorkingDirectory):
|
||||||
|
gen_param = self.fem_param.GetGroup("General")
|
||||||
|
if gen_param.GetBool("UseTempDirectory"):
|
||||||
|
self.obj.WorkingDirectory = tempfile.mkdtemp(prefix="fem_")
|
||||||
|
elif gen_param.GetBool("UseBesideDirectory"):
|
||||||
|
root, ext = os.path.splitext(obj.Document.FileName)
|
||||||
|
if root:
|
||||||
|
self.obj.WorkingDirectory = os.path.join(root, obj.Label)
|
||||||
|
os.makedirs(self.obj.WorkingDirectory, exist_ok=True)
|
||||||
|
else:
|
||||||
|
# file not saved, use temporary
|
||||||
|
self.obj.WorkingDirectory = tempfile.mkdtemp(prefix="fem_")
|
||||||
|
elif gen_param.GetBool("UseCustomDirectory"):
|
||||||
|
self.obj.WorkingDirectory = gen_param.GetString("CustomDirectoryPath")
|
||||||
|
os.makedirs(self.obj.WorkingDirectory, exist_ok=True)
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
from femtools.checksanalysis import check_member_for_solver_calculix
|
||||||
|
|
||||||
|
self._clear_results()
|
||||||
|
|
||||||
|
message = check_member_for_solver_calculix(
|
||||||
|
self.analysis,
|
||||||
|
self.obj,
|
||||||
|
membertools.get_mesh_to_solve(self.analysis)[0],
|
||||||
|
membertools.AnalysisMember(self.analysis),
|
||||||
|
)
|
||||||
|
|
||||||
|
mesh_obj = membertools.get_mesh_to_solve(self.analysis)[0]
|
||||||
|
meshdatagetter = meshsetsgetter.MeshSetsGetter(
|
||||||
|
self.analysis,
|
||||||
|
self.obj,
|
||||||
|
mesh_obj,
|
||||||
|
membertools.AnalysisMember(self.analysis),
|
||||||
|
)
|
||||||
|
meshdatagetter.get_mesh_sets()
|
||||||
|
|
||||||
|
# write solver input
|
||||||
|
w = writer.FemInputWriterCcx(
|
||||||
|
self.analysis,
|
||||||
|
self.obj,
|
||||||
|
mesh_obj,
|
||||||
|
meshdatagetter.member,
|
||||||
|
self.obj.WorkingDirectory,
|
||||||
|
meshdatagetter.mat_geo_sets,
|
||||||
|
)
|
||||||
|
self.model_file = w.write_solver_input()
|
||||||
|
# report to user if task succeeded
|
||||||
|
self.input_deck = os.path.splitext(os.path.basename(self.model_file))[0]
|
||||||
|
|
||||||
|
def compute(self):
|
||||||
|
self._clear_results()
|
||||||
|
ccx_bin = settings.get_binary("Calculix")
|
||||||
|
env = self.process.processEnvironment()
|
||||||
|
num_cpu = self.fem_param.GetGroup("Ccx").GetInt(
|
||||||
|
"AnalysisNumCPUs", QThread.idealThreadCount()
|
||||||
|
)
|
||||||
|
env.insert("OMP_NUM_THREADS", str(num_cpu))
|
||||||
|
self.process.setProcessEnvironment(env)
|
||||||
|
self.process.setWorkingDirectory(self.obj.WorkingDirectory)
|
||||||
|
|
||||||
|
command_list = ["-i", os.path.join(self.obj.WorkingDirectory, self.input_deck)]
|
||||||
|
self.process.start(ccx_bin, command_list)
|
||||||
|
|
||||||
|
return self.process
|
||||||
|
|
||||||
|
def update_properties(self):
|
||||||
|
# TODO at the moment, only one .vtm file is assumed
|
||||||
|
if not self.obj.Results:
|
||||||
|
pipeline = self.obj.Document.addObject("Fem::FemPostPipeline", self.obj.Name + "Result")
|
||||||
|
self.analysis.addObject(pipeline)
|
||||||
|
self.obj.Results = [pipeline]
|
||||||
|
self._load_ccxfrd_results()
|
||||||
|
# default display mode
|
||||||
|
pipeline.ViewObject.DisplayMode = "Surface"
|
||||||
|
pipeline.ViewObject.SelectionStyle = "BoundBox"
|
||||||
|
if self.obj.AnalysisType in ["static", "frequency", "buckling"]:
|
||||||
|
pipeline.ViewObject.Field = "Displacement"
|
||||||
|
elif self.obj.AnalysisType in ["thermomech"]:
|
||||||
|
pipeline.ViewObject.Field = "Temperature"
|
||||||
|
else:
|
||||||
|
self._load_ccxfrd_results()
|
||||||
|
|
||||||
|
def _clear_results(self):
|
||||||
|
# result is a 'Result.vtm' file and a 'Result' directory
|
||||||
|
# with the .vtu files
|
||||||
|
dir_content = os.listdir(self.obj.WorkingDirectory)
|
||||||
|
for f in dir_content:
|
||||||
|
path = os.path.join(self.obj.WorkingDirectory, f)
|
||||||
|
base, ext = os.path.splitext(path)
|
||||||
|
if ext == ".vtm":
|
||||||
|
# remove .vtm file
|
||||||
|
os.remove(path)
|
||||||
|
# remove dir with .vtu files
|
||||||
|
shutil.rmtree(base)
|
||||||
|
|
||||||
|
def _load_ccxfrd_results(self):
|
||||||
|
frd_result_prefix = os.path.join(self.obj.WorkingDirectory, self.input_deck)
|
||||||
|
Fem.frdToVTK(frd_result_prefix + ".frd")
|
||||||
|
files = os.listdir(self.obj.WorkingDirectory)
|
||||||
|
for f in files:
|
||||||
|
if f.endswith(".vtm"):
|
||||||
|
res = os.path.join(self.obj.WorkingDirectory, f)
|
||||||
|
self.obj.Results[0].read(res)
|
||||||
|
self.obj.Results[0].renameArrays(self.frd_var_conversion)
|
||||||
|
break
|
||||||
|
|
||||||
|
def version(self):
|
||||||
|
p = QProcess()
|
||||||
|
ccx_bin = settings.get_binary("Calculix")
|
||||||
|
p.start(ccx_bin, ["-v"])
|
||||||
|
p.waitForFinished()
|
||||||
|
info = p.readAll().data().decode()
|
||||||
|
return info
|
||||||
168
src/Mod/Fem/femtaskpanels/task_solver_calculix.py
Normal file
168
src/Mod/Fem/femtaskpanels/task_solver_calculix.py
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
|
||||||
|
# ***************************************************************************
|
||||||
|
# * Copyright (c) 2025 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
|
||||||
|
# * *
|
||||||
|
# * This file is part of FreeCAD. *
|
||||||
|
# * *
|
||||||
|
# * FreeCAD 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 2.1 of the *
|
||||||
|
# * License, or (at your option) any later version. *
|
||||||
|
# * *
|
||||||
|
# * FreeCAD 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 FreeCAD. If not, see *
|
||||||
|
# * <https://www.gnu.org/licenses/>. *
|
||||||
|
# * *
|
||||||
|
# ***************************************************************************
|
||||||
|
|
||||||
|
__title__ = "FreeCAD task panel for CalculiX solver"
|
||||||
|
__author__ = "Mario Passaglia"
|
||||||
|
__url__ = "https://www.freecad.org"
|
||||||
|
|
||||||
|
## @package task_solver_calculix
|
||||||
|
# \ingroup FEM
|
||||||
|
# \brief task panel for CalculiX solver
|
||||||
|
|
||||||
|
from PySide import QtCore
|
||||||
|
from PySide import QtGui
|
||||||
|
|
||||||
|
import FreeCAD
|
||||||
|
import FreeCADGui
|
||||||
|
|
||||||
|
import FemGui
|
||||||
|
|
||||||
|
from femsolver.calculix import calculixtools
|
||||||
|
|
||||||
|
from . import base_femlogtaskpanel
|
||||||
|
|
||||||
|
|
||||||
|
class _TaskPanel(base_femlogtaskpanel._BaseLogTaskPanel):
|
||||||
|
"""
|
||||||
|
The TaskPanel for run CalculiX solver
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, obj):
|
||||||
|
super().__init__(obj, calculixtools.CalculiXTools(obj))
|
||||||
|
|
||||||
|
self.form = FreeCADGui.PySideUic.loadUi(
|
||||||
|
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/SolverCalculiX.ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.text_log = self.form.te_output
|
||||||
|
self.text_time = self.form.l_time
|
||||||
|
self.prepared = False
|
||||||
|
self.run_complete = False
|
||||||
|
|
||||||
|
self.setup_connections()
|
||||||
|
|
||||||
|
def setup_connections(self):
|
||||||
|
super().setup_connections()
|
||||||
|
|
||||||
|
QtCore.QObject.connect(
|
||||||
|
self.form.ckb_working_directory,
|
||||||
|
QtCore.SIGNAL("toggled(bool)"),
|
||||||
|
self.working_directory_toggled,
|
||||||
|
)
|
||||||
|
QtCore.QObject.connect(
|
||||||
|
self.form.cb_analysis_type,
|
||||||
|
QtCore.SIGNAL("currentIndexChanged(int)"),
|
||||||
|
self.analysis_type_changed,
|
||||||
|
)
|
||||||
|
QtCore.QObject.connect(
|
||||||
|
self.form.pb_write_input, QtCore.SIGNAL("clicked()"), self.write_input_clicked
|
||||||
|
)
|
||||||
|
QtCore.QObject.connect(
|
||||||
|
self.form.pb_edit_input, QtCore.SIGNAL("clicked()"), self.edit_input_clicked
|
||||||
|
)
|
||||||
|
QtCore.QObject.connect(
|
||||||
|
self.form.pb_working_directory,
|
||||||
|
QtCore.SIGNAL("clicked()"),
|
||||||
|
self.working_directory_clicked,
|
||||||
|
)
|
||||||
|
QtCore.QObject.connect(
|
||||||
|
self.form.pb_solver_version, QtCore.SIGNAL("clicked()"), self.get_version
|
||||||
|
)
|
||||||
|
QtCore.QObject.connect(
|
||||||
|
self.form.let_working_directory,
|
||||||
|
QtCore.SIGNAL("editingFinished()"),
|
||||||
|
self.working_directory_edited,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.get_object_params()
|
||||||
|
self.set_widgets()
|
||||||
|
|
||||||
|
def preparation_finished(self):
|
||||||
|
# override base class method to not auto compute
|
||||||
|
self.prepared = True
|
||||||
|
if not self.run_complete:
|
||||||
|
self.timer.stop()
|
||||||
|
self.form.pb_edit_input.setEnabled(True)
|
||||||
|
else:
|
||||||
|
super().preparation_finished()
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
self.text_log.clear()
|
||||||
|
self.elapsed.restart()
|
||||||
|
if self.prepared:
|
||||||
|
self.timer.start(100)
|
||||||
|
self.tool.compute()
|
||||||
|
else:
|
||||||
|
# run complete process if 'Apply' is pressed without
|
||||||
|
# previously write the input files
|
||||||
|
self.run_complete = True
|
||||||
|
super().apply()
|
||||||
|
|
||||||
|
def get_object_params(self):
|
||||||
|
self.analysis_type = self.obj.AnalysisType
|
||||||
|
|
||||||
|
def set_object_params(self):
|
||||||
|
self.obj.AnalysisType = self.analysis_type
|
||||||
|
|
||||||
|
def set_widgets(self):
|
||||||
|
"fills the widgets"
|
||||||
|
self.analysis_type_enum = self.obj.getEnumerationsOfProperty("AnalysisType")
|
||||||
|
index = self.analysis_type_enum.index(self.analysis_type)
|
||||||
|
self.form.cb_analysis_type.addItems(self.analysis_type_enum)
|
||||||
|
self.form.cb_analysis_type.setCurrentIndex(index)
|
||||||
|
|
||||||
|
self.form.let_working_directory.setText(self.obj.WorkingDirectory)
|
||||||
|
self.form.ckb_working_directory.setChecked(False)
|
||||||
|
self.form.gpb_working_directory.setVisible(False)
|
||||||
|
|
||||||
|
def analysis_type_changed(self, index):
|
||||||
|
self.analysis_type = self.analysis_type_enum[index]
|
||||||
|
self.obj.AnalysisType = self.analysis_type
|
||||||
|
|
||||||
|
def working_directory_clicked(self):
|
||||||
|
directory = QtGui.QFileDialog.getExistingDirectory(dir=self.obj.WorkingDirectory)
|
||||||
|
if directory:
|
||||||
|
self.form.let_working_directory.setText(directory)
|
||||||
|
self.form.let_working_directory.editingFinished.emit()
|
||||||
|
|
||||||
|
def working_directory_edited(self):
|
||||||
|
self.obj.WorkingDirectory = self.form.let_working_directory.text()
|
||||||
|
|
||||||
|
def write_input_clicked(self):
|
||||||
|
self.prepared = False
|
||||||
|
self.run_complete = False
|
||||||
|
self.run_process()
|
||||||
|
|
||||||
|
def edit_input_clicked(self):
|
||||||
|
ccx_param = self.tool.fem_param.GetGroup("Ccx")
|
||||||
|
internal = ccx_param.GetBool("UseInternalEditor", True)
|
||||||
|
ext_editor_path = ccx_param.GetString("ExternalEditorPath", "")
|
||||||
|
if internal or not ext_editor_path:
|
||||||
|
FemGui.open(self.tool.model_file)
|
||||||
|
else:
|
||||||
|
ext_editor_process = QtCore.QProcess()
|
||||||
|
ext_editor_process.start(ext_editor_path, [self.tool.model_file])
|
||||||
|
ext_editor_process.waitForFinished()
|
||||||
|
|
||||||
|
def working_directory_toggled(self, bool_value):
|
||||||
|
self.form.gpb_working_directory.setVisible(bool_value)
|
||||||
50
src/Mod/Fem/femviewprovider/view_solver_calculix.py
Normal file
50
src/Mod/Fem/femviewprovider/view_solver_calculix.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
|
||||||
|
# ***************************************************************************
|
||||||
|
# * Copyright (c) 2025 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
|
||||||
|
# * *
|
||||||
|
# * This file is part of FreeCAD. *
|
||||||
|
# * *
|
||||||
|
# * FreeCAD 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 2.1 of the *
|
||||||
|
# * License, or (at your option) any later version. *
|
||||||
|
# * *
|
||||||
|
# * FreeCAD 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 FreeCAD. If not, see *
|
||||||
|
# * <https://www.gnu.org/licenses/>. *
|
||||||
|
# * *
|
||||||
|
# ***************************************************************************
|
||||||
|
|
||||||
|
__title__ = "FreeCAD FEM solver CalculiX view provider"
|
||||||
|
__author__ = "Mario Passaglia"
|
||||||
|
__url__ = "https://www.freecad.org"
|
||||||
|
|
||||||
|
## @package view_calculix
|
||||||
|
# \ingroup FEM
|
||||||
|
# \brief solver CalculiX view provider
|
||||||
|
|
||||||
|
import FreeCADGui
|
||||||
|
|
||||||
|
from femtaskpanels import task_solver_calculix
|
||||||
|
from femviewprovider import view_base_femobject
|
||||||
|
|
||||||
|
|
||||||
|
class VPSolverCalculiX(view_base_femobject.VPBaseFemObject):
|
||||||
|
|
||||||
|
def __init__(self, vobj):
|
||||||
|
super().__init__(vobj)
|
||||||
|
|
||||||
|
def getIcon(self):
|
||||||
|
return ":/icons/FEM_SolverStandard.svg"
|
||||||
|
|
||||||
|
def setEdit(self, vobj, mode=0):
|
||||||
|
task = task_solver_calculix._TaskPanel(vobj.Object)
|
||||||
|
FreeCADGui.Control.showDialog(task)
|
||||||
|
|
||||||
|
return True
|
||||||
Reference in New Issue
Block a user