From 2f307487c95d991eaa18572694118d45e5bfd0fe Mon Sep 17 00:00:00 2001 From: marioalexis Date: Sat, 30 Aug 2025 21:09:25 -0300 Subject: [PATCH] Fem: Enable time increments for non-transient analysis --- src/Mod/Fem/femobjects/solver_calculix.py | 56 ++++++++------- src/Mod/Fem/femobjects/solver_ccxtools.py | 40 ++++++----- .../femsolver/calculix/write_step_equation.py | 68 ++++++------------- 3 files changed, 74 insertions(+), 90 deletions(-) diff --git a/src/Mod/Fem/femobjects/solver_calculix.py b/src/Mod/Fem/femobjects/solver_calculix.py index 1da4e97cbb..adfbf7db4f 100644 --- a/src/Mod/Fem/femobjects/solver_calculix.py +++ b/src/Mod/Fem/femobjects/solver_calculix.py @@ -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 diff --git a/src/Mod/Fem/femobjects/solver_ccxtools.py b/src/Mod/Fem/femobjects/solver_ccxtools.py index 2e9c06f148..138893d42a 100644 --- a/src/Mod/Fem/femobjects/solver_ccxtools.py +++ b/src/Mod/Fem/femobjects/solver_ccxtools.py @@ -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 diff --git a/src/Mod/Fem/femsolver/calculix/write_step_equation.py b/src/Mod/Fem/femsolver/calculix/write_step_equation.py index 83a97692b5..a76c96fe56 100644 --- a/src/Mod/Fem/femsolver/calculix/write_step_equation.py +++ b/src/Mod/Fem/femsolver/calculix/write_step_equation.py @@ -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")