Fem: Enable time increments for non-transient analysis

This commit is contained in:
marioalexis
2025-08-30 21:09:25 -03:00
parent 3e73005653
commit a98500cc05
3 changed files with 74 additions and 90 deletions

View File

@@ -53,7 +53,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
_PropHelper(
type="App::PropertyEnumeration",
name="AnalysisType",
group="Solver",
group="AnalysisType",
doc="Type of the analysis",
value=["static", "frequency", "thermomech", "check", "buckling", "electromagnetic"],
)
@@ -125,7 +125,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
_PropHelper(
type="App::PropertyTime",
name="TimeInitialStep",
group="Solver",
group="TimeIncrement",
doc="Initial time steps",
value=0.01,
)
@@ -134,7 +134,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
_PropHelper(
type="App::PropertyTime",
name="TimeEnd",
group="Solver",
group="TimeIncrement",
doc="End time analysis",
value=1.0,
)
@@ -143,7 +143,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
_PropHelper(
type="App::PropertyTime",
name="TimeMinimumStep",
group="Solver",
group="TimeIncrement",
doc="Minimum time step",
value=0.00001,
)
@@ -152,7 +152,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
_PropHelper(
type="App::PropertyTime",
name="TimeMaximumStep",
group="Solver",
group="TimeIncrement",
doc="Maximum time step",
value=1.0,
)
@@ -161,7 +161,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
_PropHelper(
type="App::PropertyBool",
name="ThermoMechSteadyState",
group="Solver",
group="AnalysisType",
doc="Choose between steady state thermo mech or transient thermo mech analysis",
value=True,
)
@@ -225,21 +225,12 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
prop.append(
_PropHelper(
type="App::PropertyBool",
name="IterationsUserDefinedIncrementations",
group="Solver",
doc="Set to True to switch off the ccx automatic incrementation completely\n"
+ "(ccx parameter DIRECT). Use with care. Analysis may not converge!",
value=False,
)
)
prop.append(
_PropHelper(
type="App::PropertyBool",
name="IterationsUserDefinedTimeStepLength",
group="Solver",
doc="Set to True to use the user defined time steps.\n"
+ "They are set with TimeInitialStep, TimeEnd, TimeMinimum and TimeMaximum",
value=False,
name="AutomaticIncrementation",
group="TimeIncrement",
doc="If False, switch off automatic incrementation via CalculiX\n"
+ "`DIRECT` parameter and ignore minimum and maximum time increments.\n"
+ "Analysis may not converge!",
value=True,
)
)
prop.append(
@@ -271,7 +262,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
_PropHelper(
type="App::PropertyBool",
name="BeamReducedIntegration",
group="Solver",
group="ElementModel",
doc="Set to True to use beam elements with reduced integration",
value=True,
)
@@ -289,7 +280,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
_PropHelper(
type="App::PropertyEnumeration",
name="ModelSpace",
group="Solver",
group="ElementModel",
doc="Type of model space",
value=["3D", "plane stress", "plane strain", "axisymmetric"],
)
@@ -298,7 +289,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
_PropHelper(
type="App::PropertyEnumeration",
name="ThermoMechType",
group="Solver",
group="AnalysisType",
doc="Type of thermomechanical analysis",
value=["coupled", "uncoupled", "pure heat transfer"],
)
@@ -316,7 +307,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
_PropHelper(
type="App::PropertyEnumeration",
name="ElectromagneticMode",
group="Solver",
group="AnalysisType",
doc="Electromagnetic mode",
value=["electrostatic"],
)
@@ -325,7 +316,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
_PropHelper(
type="App::PropertyBool",
name="ExcludeBendingStiffness",
group="Solver",
group="ElementModel",
doc="Exclude bending stiffness to replace shells with membranes or beams with trusses",
value=False,
)
@@ -339,3 +330,16 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject):
obj.getPropertyByName(prop.name)
except Base.PropertyError:
prop.add_to_object(obj)
# remove old properties
try:
obj.AutomaticIncrementation = not obj.getPropertyByName(
"IterationsUserDefinedIncrementations"
)
obj.setPropertyStatus("IterationsUserDefinedIncrementations", "-LockDynamic")
obj.removeProperty("IterationsUserDefinedIncrementations")
obj.setPropertyStatus("IterationsUserDefinedTimeStepLength", "-LockDynamic")
obj.removeProperty("IterationsUserDefinedTimeStepLength")
except Base.PropertyError:
pass

View File

@@ -29,13 +29,11 @@ __url__ = "https://www.freecad.org"
# \ingroup FEM
# \brief solver calculix ccx tools object
import FreeCAD
from . import base_fempythonobject
from femsolver.calculix.solver import _BaseSolverCalculix
from .base_fempythonobject import _PropHelper
from .solver_calculix import SolverCalculiX
class SolverCcxTools(base_fempythonobject.BaseFemPythonObject, _BaseSolverCalculix):
class SolverCcxTools(SolverCalculiX):
"""The Fem::FemSolver's Proxy python type, add solver specific properties"""
Type = "Fem::SolverCcxTools"
@@ -43,18 +41,26 @@ class SolverCcxTools(base_fempythonobject.BaseFemPythonObject, _BaseSolverCalcul
def __init__(self, obj):
super().__init__(obj)
# implemented in framework calculix solver module
self.add_attributes(obj)
def _get_properties(self):
prop = super()._get_properties()
obj.addProperty(
"App::PropertyPath",
"WorkingDir",
"Fem",
"Working directory for calculations, will only be used it is left blank in preferences",
# set analysis types supported by CcxTools solver
for p in prop:
if p.name == "AnalysisType":
p.value = ["static", "frequency", "thermomech", "check", "buckling"]
# remove unused properties
prop = list(filter(lambda p: p.name != "ElectromagneticMode", prop))
prop.append(
_PropHelper(
type="App::PropertyPath",
name="WorkingDir",
group="Solver",
doc="Working directory for calculations.\n"
+ "Will only be used it is left blank in preferences",
value="",
)
)
obj.setPropertyStatus("WorkingDir", "LockDynamic")
# the working directory is not set, the solver working directory is
# only used if the preferences working directory is left blank
def onDocumentRestored(self, obj):
self.on_restore_of_document(obj)
return prop

View File

@@ -37,7 +37,7 @@ def write_step_equation(f, ccxwriter):
# build STEP line
step = "*STEP"
if ccxwriter.solver_obj.GeometricalNonlinearity == "nonlinear":
if ccxwriter.analysis_type == "static" or ccxwriter.analysis_type == "thermomech":
if ccxwriter.analysis_type in ["static", "thermomech"]:
# https://www.comsol.com/blogs/what-is-geometric-nonlinearity
step += ", NLGEOM"
elif ccxwriter.analysis_type == "frequency":
@@ -45,12 +45,11 @@ def write_step_equation(f, ccxwriter):
"Analysis type frequency and geometrical nonlinear "
"analysis are not allowed together, linear is used instead!\n"
)
if ccxwriter.solver_obj.IterationsMaximum:
if ccxwriter.analysis_type == "thermomech" or ccxwriter.analysis_type == "static":
if ccxwriter.analysis_type in ["static", "thermomech", "electrostatic"]:
step += f", INC={ccxwriter.solver_obj.IterationsMaximum}"
elif ccxwriter.analysis_type == "frequency" or ccxwriter.analysis_type == "buckling":
# parameter is for thermomechanical analysis only, see ccx manual *STEP
pass
# write STEP line
f.write(step + "\n")
@@ -63,6 +62,7 @@ def write_step_equation(f, ccxwriter):
# ANALYSIS type line
# analysis line --> analysis type
analysis_type = ""
if ccxwriter.analysis_type == "static":
analysis_type = "*STATIC"
elif ccxwriter.analysis_type == "frequency":
@@ -74,12 +74,15 @@ def write_step_equation(f, ccxwriter):
analysis_type = "*UNCOUPLED TEMPERATURE-DISPLACEMENT"
elif ccxwriter.solver_obj.ThermoMechType == "pure heat transfer":
analysis_type = "*HEAT TRANSFER"
if ccxwriter.solver_obj.ThermoMechSteadyState:
analysis_type += ", STEADY STATE"
elif ccxwriter.analysis_type == "check":
analysis_type = "*NO ANALYSIS"
elif ccxwriter.analysis_type == "buckling":
analysis_type = "*BUCKLE"
elif ccxwriter.analysis_type == "electromagnetic":
analysis_type = "*HEAT TRANSFER, STEADY STATE"
# analysis line --> solver type
# https://forum.freecad.org/viewtopic.php?f=18&t=43178
if ccxwriter.solver_obj.MatrixSolverType == "default":
@@ -94,45 +97,22 @@ def write_step_equation(f, ccxwriter):
analysis_type += ", SOLVER=ITERATIVE SCALING"
elif ccxwriter.solver_obj.MatrixSolverType == "iterativecholesky":
analysis_type += ", SOLVER=ITERATIVE CHOLESKY"
# analysis line --> user defined incrementations --> parameter DIRECT
# --> completely switch off ccx automatic incrementation
if ccxwriter.solver_obj.IterationsUserDefinedIncrementations:
# analysis line --> automatic incrementation --> parameter DIRECT
# completely switch off ccx automatic incrementation
if not ccxwriter.solver_obj.AutomaticIncrementation:
if ccxwriter.analysis_type in ["static", "thermomech", "electromagnetic"]:
analysis_type += ", DIRECT"
elif ccxwriter.analysis_type == "frequency":
FreeCAD.Console.PrintMessage(
"Analysis type frequency and IterationsUserDefinedIncrementations "
"are not allowed together, it is ignored\n"
)
# analysis line --> steadystate --> thermomech only
if ccxwriter.solver_obj.ThermoMechSteadyState:
# bernd: I do not know if STEADY STATE is allowed with DIRECT
# but since time steps are 1.0 it makes no sense IMHO
if ccxwriter.analysis_type == "thermomech":
analysis_type += ", STEADY STATE"
# Set time to 1 and ignore user inputs for steady state
ccxwriter.solver_obj.TimeInitialStep = 1.0
ccxwriter.solver_obj.TimeEnd = 1.0
elif (
ccxwriter.analysis_type == "static"
or ccxwriter.analysis_type == "frequency"
or ccxwriter.analysis_type == "buckling"
):
pass # not supported for static and frequency!
# ANALYSIS parameter line
analysis_parameter = ""
if ccxwriter.analysis_type == "static" or ccxwriter.analysis_type == "check":
if (
ccxwriter.solver_obj.IterationsUserDefinedIncrementations is True
or ccxwriter.solver_obj.IterationsUserDefinedTimeStepLength is True
):
analysis_parameter = "{},{},{},{}".format(
ccxwriter.solver_obj.TimeInitialStep.getValueAs("s").Value,
ccxwriter.solver_obj.TimeEnd.getValueAs("s").Value,
ccxwriter.solver_obj.TimeMinimumStep.getValueAs("s").Value,
ccxwriter.solver_obj.TimeMaximumStep.getValueAs("s").Value,
)
if ccxwriter.analysis_type in ["static", "thermomech", "electromagnetic"]:
analysis_parameter = "{},{},{},{}".format(
ccxwriter.solver_obj.TimeInitialStep.getValueAs("s").Value,
ccxwriter.solver_obj.TimeEnd.getValueAs("s").Value,
ccxwriter.solver_obj.TimeMinimumStep.getValueAs("s").Value,
ccxwriter.solver_obj.TimeMaximumStep.getValueAs("s").Value,
)
elif ccxwriter.analysis_type == "frequency":
if (
ccxwriter.solver_obj.EigenmodeLowLimit == 0.0
@@ -145,19 +125,13 @@ def write_step_equation(f, ccxwriter):
ccxwriter.solver_obj.EigenmodeLowLimit.getValueAs("Hz").Value,
ccxwriter.solver_obj.EigenmodeHighLimit.getValueAs("Hz").Value,
)
elif ccxwriter.analysis_type == "thermomech":
# OvG: 1.0 increment, total time 1 for steady state will cut back automatically
analysis_parameter = "{},{},{},{}".format(
ccxwriter.solver_obj.TimeInitialStep.getValueAs("s").Value,
ccxwriter.solver_obj.TimeEnd.getValueAs("s").Value,
ccxwriter.solver_obj.TimeMinimumStep.getValueAs("s").Value,
ccxwriter.solver_obj.TimeMaximumStep.getValueAs("s").Value,
)
elif ccxwriter.analysis_type == "buckling":
analysis_parameter = "{},{}".format(
ccxwriter.solver_obj.BucklingFactors,
ccxwriter.solver_obj.BucklingAccuracy,
)
elif ccxwriter.analysis_type == "check":
analysis_parameter = ""
# write analysis type line, analysis parameter line
f.write(analysis_type + "\n")