Fem: Remove deprecated Calculix implementation

(cherry picked from commit 617db3a2d4)
This commit is contained in:
marioalexis
2026-03-02 20:30:49 +00:00
committed by github-actions[bot]
parent c8547e1e9f
commit e21dff1dec
5 changed files with 10 additions and 692 deletions
-2
View File
@@ -253,8 +253,6 @@ SET(FemSolver_SRCS
SET(FemSolverCalculix_SRCS
femsolver/calculix/__init__.py
femsolver/calculix/calculixtools.py
femsolver/calculix/solver.py
femsolver/calculix/tasks.py
femsolver/calculix/write_constraint_bodyheatsource.py
femsolver/calculix/write_constraint_centrif.py
femsolver/calculix/write_constraint_contact.py
-498
View File
@@ -1,498 +0,0 @@
# ***************************************************************************
# * Copyright (c) 2017 Bernd Hahnebach <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * 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 Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
__title__ = "FreeCAD FEM solver object CalculiX"
__author__ = "Bernd Hahnebach"
__url__ = "https://www.freecad.org"
## @package SolverCalculix
# \ingroup FEM
import glob
import os
import FreeCAD
from . import tasks
from .. import run
from .. import solverbase
from femtools import femutils
if FreeCAD.GuiUp:
import FemGui
ANALYSIS_TYPES = ["static", "frequency", "thermomech", "check", "buckling"]
def create(doc, name="SolverCalculiX"):
return femutils.createObject(doc, name, Proxy, ViewProxy)
class _BaseSolverCalculix:
def on_restore_of_document(self, obj):
temp_analysis_type = obj.AnalysisType
obj.AnalysisType = ANALYSIS_TYPES
if temp_analysis_type in ANALYSIS_TYPES:
obj.AnalysisType = temp_analysis_type
else:
FreeCAD.Console.PrintWarning(
f"Analysis type {temp_analysis_type} not found. Standard is used.\n"
)
obj.AnalysisType = ANALYSIS_TYPES[0]
self.add_attributes(obj)
def add_attributes(self, obj):
if not hasattr(obj, "AnalysisType"):
obj.addProperty(
"App::PropertyEnumeration",
"AnalysisType",
"Fem",
"Type of the analysis",
locked=True,
)
obj.AnalysisType = ANALYSIS_TYPES
obj.AnalysisType = ANALYSIS_TYPES[0]
if not hasattr(obj, "GeometricalNonlinearity"):
choices_geom_nonlinear = ["linear", "nonlinear"]
obj.addProperty(
"App::PropertyEnumeration",
"GeometricalNonlinearity",
"Fem",
"Set geometrical nonlinearity",
locked=True,
)
obj.GeometricalNonlinearity = choices_geom_nonlinear
obj.GeometricalNonlinearity = choices_geom_nonlinear[0]
if not hasattr(obj, "MaterialNonlinearity"):
choices_material_nonlinear = ["linear", "nonlinear"]
obj.addProperty(
"App::PropertyEnumeration",
"MaterialNonlinearity",
"Fem",
"Set material nonlinearity",
locked=True,
)
obj.MaterialNonlinearity = choices_material_nonlinear
obj.MaterialNonlinearity = choices_material_nonlinear[0]
if not hasattr(obj, "EigenmodesCount"):
obj.addProperty(
"App::PropertyIntegerConstraint",
"EigenmodesCount",
"Fem",
"Number of modes for frequency calculations",
locked=True,
)
obj.EigenmodesCount = (10, 1, 100, 1)
low_limit = 0.0
if (
hasattr(obj, "EigenmodeLowLimit")
and obj.getTypeIdOfProperty("EigenmodeLowLimit") == "App::PropertyFloatConstraint"
):
low_limit = obj.EigenmodeLowLimit
obj.setPropertyStatus("EigenmodeLowLimit", "-LockDynamic")
obj.removeProperty("EigenmodeLowLimit")
if not hasattr(obj, "EigenmodeLowLimit"):
obj.addProperty(
"App::PropertyFrequency",
"EigenmodeLowLimit",
"Fem",
"Low frequency limit for eigenmode calculations",
locked=True,
)
obj.EigenmodeLowLimit = low_limit
high_limit = 1000000.0
if (
hasattr(obj, "EigenmodeHighLimit")
and obj.getTypeIdOfProperty("EigenmodeHighLimit") == "App::PropertyFloatConstraint"
):
high_limit = obj.EigenmodeHighLimit
obj.setPropertyStatus("EigenmodeHighLimit", "-LockDynamic")
obj.removeProperty("EigenmodeHighLimit")
if not hasattr(obj, "EigenmodeHighLimit"):
obj.addProperty(
"App::PropertyFrequency",
"EigenmodeHighLimit",
"Fem",
"High frequency limit for eigenmode calculations",
locked=True,
)
obj.EigenmodeHighLimit = high_limit
if not hasattr(obj, "IterationsMaximum"):
help_string_IterationsMaximum = (
"Maximum Number of iterations in each time step before stopping jobs"
)
obj.addProperty(
"App::PropertyIntegerConstraint",
"IterationsMaximum",
"Fem",
help_string_IterationsMaximum,
locked=True,
)
obj.IterationsMaximum = 2000
if hasattr(obj, "IterationsThermoMechMaximum"):
obj.IterationsMaximum = obj.IterationsThermoMechMaximum
obj.removeProperty("IterationsThermoMechMaximum")
if not hasattr(obj, "BucklingFactors"):
obj.addProperty(
"App::PropertyIntegerConstraint",
"BucklingFactors",
"Fem",
"Calculates the lowest buckling modes to the corresponding buckling factors",
locked=True,
)
obj.BucklingFactors = 1
time_initial_step = 0.01
if (
hasattr(obj, "TimeInitialStep")
and obj.getTypeIdOfProperty("TimeInitialStep") == "App::PropertyFloatConstraint"
):
time_initial_step = obj.TimeInitialStep
obj.setPropertyStatus("TimeInitialStep", "-LockDynamic")
obj.removeProperty("TimeInitialStep")
if not hasattr(obj, "TimeInitialStep"):
obj.addProperty(
"App::PropertyTime",
"TimeInitialStep",
"Fem",
"Initial time steps",
locked=True,
)
obj.TimeInitialStep = time_initial_step
time_end = 1.0
if (
hasattr(obj, "TimeEnd")
and obj.getTypeIdOfProperty("TimeEnd") == "App::PropertyFloatConstraint"
):
time_end = obj.TimeEnd
obj.setPropertyStatus("TimeEnd", "-LockDynamic")
obj.removeProperty("TimeEnd")
if not hasattr(obj, "TimeEnd"):
obj.addProperty("App::PropertyTime", "TimeEnd", "Fem", "End time analysis", locked=True)
obj.TimeEnd = time_end
time_minimum_step = 0.00001
if (
hasattr(obj, "TimeMinimumStep")
and obj.getTypeIdOfProperty("TimeMinimumStep") == "App::PropertyFloatConstraint"
):
time_minimum_step = obj.TimeMinimumStep
obj.setPropertyStatus("TimeMinimumStep", "-LockDynamic")
obj.removeProperty("TimeMinimumStep")
if not hasattr(obj, "TimeMinimumStep"):
obj.addProperty(
"App::PropertyTime",
"TimeMinimumStep",
"Fem",
"Minimum time step",
locked=True,
)
obj.TimeMinimumStep = time_minimum_step
time_maximum_step = 1.0
if (
hasattr(obj, "TimeMaximumStep")
and obj.getTypeIdOfProperty("TimeMaximumStep") == "App::PropertyFloatConstraint"
):
time_maximum_step = obj.TimeMaximumStep
obj.setPropertyStatus("TimeMaximumStep", "-LockDynamic")
obj.removeProperty("TimeMaximumStep")
if not hasattr(obj, "TimeMaximumStep"):
obj.addProperty(
"App::PropertyTime",
"TimeMaximumStep",
"Fem",
"Maximum time step",
locked=True,
)
obj.TimeMaximumStep = time_maximum_step
if not hasattr(obj, "ThermoMechSteadyState"):
obj.addProperty(
"App::PropertyBool",
"ThermoMechSteadyState",
"Fem",
"Choose between steady state thermo mech or transient thermo mech analysis",
locked=True,
)
obj.ThermoMechSteadyState = True
if not hasattr(obj, "IterationsControlParameterTimeUse"):
obj.addProperty(
"App::PropertyBool",
"IterationsControlParameterTimeUse",
"Fem",
"Use the user defined time incrementation control parameter",
locked=True,
)
obj.IterationsControlParameterTimeUse = False
if not hasattr(obj, "SplitInputWriter"):
obj.addProperty(
"App::PropertyBool",
"SplitInputWriter",
"Fem",
"Split writing of ccx input file",
locked=True,
)
obj.SplitInputWriter = False
if not hasattr(obj, "IterationsControlParameterIter"):
control_parameter_iterations = (
"{I_0},{I_R},{I_P},{I_C},{I_L},{I_G},{I_S},{I_A},{I_J},{I_T}".format(
I_0=4,
I_R=8,
I_P=9,
I_C=200, # ccx default = 16
I_L=10,
I_G=400, # ccx default = 4
I_S="",
I_A=200, # ccx default = 5
I_J="",
I_T="",
)
)
obj.addProperty(
"App::PropertyString",
"IterationsControlParameterIter",
"Fem",
"User defined time incrementation iterations control parameter",
locked=True,
)
obj.IterationsControlParameterIter = control_parameter_iterations
if not hasattr(obj, "IterationsControlParameterCutb"):
control_parameter_cutback = "{D_f},{D_C},{D_B},{D_A},{D_S},{D_H},{D_D},{W_G}".format(
D_f=0.25,
D_C=0.5,
D_B=0.75,
D_A=0.85,
D_S="",
D_H="",
D_D=1.5,
W_G="",
)
obj.addProperty(
"App::PropertyString",
"IterationsControlParameterCutb",
"Fem",
"User defined time incrementation cutbacks control parameter",
locked=True,
)
obj.IterationsControlParameterCutb = control_parameter_cutback
if not hasattr(obj, "IterationsUserDefinedIncrementations"):
stringIterationsUserDefinedIncrementations = (
"Set to True to switch off the ccx automatic incrementation completely "
"(ccx parameter DIRECT). Use with care. Analysis may not converge!"
)
obj.addProperty(
"App::PropertyBool",
"IterationsUserDefinedIncrementations",
"Fem",
stringIterationsUserDefinedIncrementations,
locked=True,
)
obj.IterationsUserDefinedIncrementations = False
if not hasattr(obj, "IterationsUserDefinedTimeStepLength"):
help_string_IterationsUserDefinedTimeStepLength = (
"Set to True to use the user defined time steps. "
"They are set with TimeInitialStep, TimeEnd, TimeMinimum and TimeMaximum"
)
obj.addProperty(
"App::PropertyBool",
"IterationsUserDefinedTimeStepLength",
"Fem",
help_string_IterationsUserDefinedTimeStepLength,
locked=True,
)
obj.IterationsUserDefinedTimeStepLength = False
if not hasattr(obj, "MatrixSolverType"):
known_ccx_solver_types = [
"default",
"pastix",
"pardiso",
"spooles",
"iterativescaling",
"iterativecholesky",
]
obj.addProperty(
"App::PropertyEnumeration",
"MatrixSolverType",
"Fem",
"Type of solver to use",
locked=True,
)
obj.MatrixSolverType = known_ccx_solver_types
obj.MatrixSolverType = known_ccx_solver_types[0]
if not hasattr(obj, "BeamShellResultOutput3D"):
obj.addProperty(
"App::PropertyBool",
"BeamShellResultOutput3D",
"Fem",
"Output 3D results for 1D and 2D analysis ",
locked=True,
)
obj.BeamShellResultOutput3D = True
if not hasattr(obj, "BeamReducedIntegration"):
obj.addProperty(
"App::PropertyBool",
"BeamReducedIntegration",
"Fem",
"Set to True to use beam elements with reduced integration",
locked=True,
)
obj.BeamReducedIntegration = True
if not hasattr(obj, "OutputFrequency"):
obj.addProperty(
"App::PropertyIntegerConstraint",
"OutputFrequency",
"Fem",
"Set the output frequency in increments",
locked=True,
)
obj.OutputFrequency = 1
if not hasattr(obj, "ModelSpace"):
model_space_types = ["3D", "plane stress", "plane strain", "axisymmetric"]
obj.addProperty(
"App::PropertyEnumeration", "ModelSpace", "Fem", "Type of model space", locked=True
)
obj.ModelSpace = model_space_types
if not hasattr(obj, "ThermoMechType"):
thermomech_types = ["coupled", "uncoupled", "pure heat transfer"]
obj.addProperty(
"App::PropertyEnumeration",
"ThermoMechType",
"Fem",
"Type of thermomechanical analysis",
locked=True,
)
obj.ThermoMechType = thermomech_types
if not hasattr(obj, "BucklingAccuracy"):
obj.addProperty(
"App::PropertyFloatConstraint",
"BucklingAccuracy",
"Fem",
"Accuracy for buckling analysis",
locked=True,
)
obj.BucklingAccuracy = 0.01
if not hasattr(obj, "ExcludeBendingStiffness"):
obj.addProperty(
"App::PropertyBool",
"ExcludeBendingStiffness",
"Fem",
"Exclude bending stiffness to replace shells with membranes or beams with trusses",
locked=True,
)
obj.ExcludeBendingStiffness = False
if not hasattr(obj, "PastixMixedPrecision"):
obj.addProperty(
"App::PropertyBool",
"PastixMixedPrecision",
"Fem",
"Mixed precision for the PaStiX matrix solver",
locked=True,
)
obj.PastixMixedPrecision = False
class Proxy(solverbase.Proxy, _BaseSolverCalculix):
"""The Fem::FemSolver's Proxy python type, add solver specific properties"""
Type = "Fem::SolverCalculix"
def __init__(self, obj):
super().__init__(obj)
obj.Proxy = self
self.add_attributes(obj)
def onDocumentRestored(self, obj):
self.on_restore_of_document(obj)
def createMachine(self, obj, directory, testmode=False):
return run.Machine(
solver=obj,
directory=directory,
check=tasks.Check(),
prepare=tasks.Prepare(),
solve=tasks.Solve(),
results=tasks.Results(),
testmode=testmode,
)
def editSupported(self):
return True
def edit(self, directory):
pattern = os.path.join(directory, "*.inp")
FreeCAD.Console.PrintMessage(f"{pattern}\n")
f = glob.glob(pattern)[0]
FemGui.open(f)
def execute(self, obj):
return
class ViewProxy(solverbase.ViewProxy):
pass
"""
Should there be some equation object for Calculix too?
Necessarily yes! The properties GeometricalNonlinearity,
MaterialNonlinearity, ThermoMechSteadyState might be moved
to the appropriate equation.
Furthermore the material Category should not be used in writer.
See common material object for more information. The equation
should used instead to get this information needed in writer.
"""
-191
View File
@@ -1,191 +0,0 @@
# ***************************************************************************
# * Copyright (c) 2017 Markus Hovorka <[email protected]> *
# * Copyright (c) 2017 Bernd Hahnebach <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * 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 Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
__title__ = "FreeCAD FEM solver CalculiX tasks"
__author__ = "Markus Hovorka, Bernd Hahnebach"
__url__ = "https://www.freecad.org"
## \addtogroup FEM
# @{
import os
import os.path
import subprocess
import FreeCAD
from . import writer
from .. import run
from .. import settings
from feminout import importCcxDatResults
from feminout import importCcxFrdResults
from femmesh import meshsetsgetter
from femtools import femutils
from femtools import membertools
_inputFileName = None
class Check(run.Check):
def run(self):
self.pushStatus("Checking analysis member...\n")
self.check_mesh_exists()
# workaround use Calculix ccxtools pre checks
from femtools.checksanalysis import check_member_for_solver_calculix
message = check_member_for_solver_calculix(
self.analysis,
self.solver,
membertools.get_mesh_to_solve(self.analysis)[0],
membertools.AnalysisMember(self.analysis),
)
if message:
text = "CalculiX can not be started...\n"
self.report.error(f"{text}{message}")
self.fail()
return
class Prepare(run.Prepare):
def run(self):
global _inputFileName
self.pushStatus("Preparing input...\n")
# get mesh set data
# TODO evaluate if it makes sense to add new task
# between check and prepare to the solver frame work
mesh_obj = membertools.get_mesh_to_solve(self.analysis)[0] # pre check done already
meshdatagetter = meshsetsgetter.MeshSetsGetter(
self.analysis,
self.solver,
mesh_obj,
membertools.AnalysisMember(self.analysis),
)
meshdatagetter.get_mesh_sets()
# write solver input
w = writer.FemInputWriterCcx(
self.analysis,
self.solver,
mesh_obj,
meshdatagetter.member,
self.directory,
meshdatagetter.mat_geo_sets,
)
path = w.write_solver_input()
# report to user if task succeeded
if path != "" and os.path.isfile(path):
self.pushStatus("Writing solver input completed.")
else:
self.pushStatus("Writing solver input failed.")
self.fail()
_inputFileName = os.path.splitext(os.path.basename(path))[0]
class Solve(run.Solve):
def run(self):
self.pushStatus("Executing solver...\n")
# get solver binary
self.pushStatus("Get solver binary...\n")
binary = settings.get_binary("Calculix")
if binary is None:
self.pushStatus("Error: The Calculix binary has not been found!")
self.fail()
return
# run solver
self._process = subprocess.Popen(
[binary, "-i", _inputFileName],
cwd=self.directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
self.signalAbort.add(self._process.terminate)
# output = self._observeSolver(self._process)
self._process.communicate()
self.signalAbort.remove(self._process.terminate)
# if not self.aborted:
# self._updateOutput(output)
# del output # get flake8 quiet
class Results(run.Results):
def run(self):
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General")
if not prefs.GetBool("KeepResultsOnReRun", False):
self.purge_results()
self.load_results()
def purge_results(self):
self.pushStatus("Purge existing results...\n")
# TODO dat file will not be removed
# TODO implement a generic purge method
# TODO results from other solvers will be removed too
# the user should decide if purge should only
# delete this solver results or results from all solvers
for m in membertools.get_member(self.analysis, "Fem::FemResultObject"):
if m.Mesh and femutils.is_of_type(m.Mesh, "Fem::MeshResult"):
self.analysis.Document.removeObject(m.Mesh.Name)
self.analysis.Document.removeObject(m.Name)
self.analysis.Document.recompute()
def load_results(self):
self.pushStatus("Import new results...\n")
self.load_ccxfrd_results()
self.load_ccxdat_results()
def load_ccxfrd_results(self):
frd_result_file = os.path.join(self.directory, _inputFileName + ".frd")
if os.path.isfile(frd_result_file):
result_name_prefix = "CalculiX_" + self.solver.AnalysisType + "_"
importCcxFrdResults.importFrd(frd_result_file, self.analysis, result_name_prefix)
else:
# TODO: use solver framework status message system
FreeCAD.Console.PrintError(f"FEM: No results found at {frd_result_file}!\n")
self.fail()
def load_ccxdat_results(self):
dat_result_file = os.path.join(self.directory, _inputFileName + ".dat")
if os.path.isfile(dat_result_file):
mode_frequencies = importCcxDatResults.import_dat(dat_result_file, self.analysis)
else:
# TODO: use solver framework status message system
FreeCAD.Console.PrintError(f"FEM: No results found at {dat_result_file}!\n")
self.fail()
if mode_frequencies:
for m in membertools.get_member(self.analysis, "Fem::FemResultObject"):
if m.Eigenmode > 0:
for mf in mode_frequencies:
if m.Eigenmode == mf["eigenmode"]:
m.EigenmodeFrequency = mf["frequency"]
## @}
-1
View File
@@ -34,7 +34,6 @@ import FreeCAD
from FreeCAD import Units
from . import femutils
from femsolver.calculix.solver import ANALYSIS_TYPES
def check_member_for_solver_calculix(analysis, solver, mesh, member):
+10
View File
@@ -42,6 +42,7 @@ class FemMigrateApp:
"femsolver.elmer.equations",
"femsolver.elmer.equations.fluxsolver",
"femsolver.elmer.solver",
"femsolver.calculix.solver",
"femobjects",
"femobjects._FemConstraintBodyHeatSource",
"femobjects._FemConstraintElectrostaticPotential",
@@ -133,6 +134,15 @@ class FemMigrateApp:
module.ViewProxy = VPSolverElmer
if module.__name__ == "femsolver.calculix.solver":
from femobjects.solver_calculix import SolverCalculiX
module.Proxy = SolverCalculiX
if FreeCAD.GuiUp:
from femviewprovider.view_solver_calculix import VPSolverCalculiX
module.ViewProxy = VPSolverCalculiX
if module.__name__ == "femobjects":
module.__path__ = "femobjects"
if module.__name__ == "femobjects._FemConstraintBodyHeatSource":