From 0a392c2a3ff4603f9941fcf3b1f056f1a45fb7cb Mon Sep 17 00:00:00 2001 From: FEA-eng <59876896+FEA-eng@users.noreply.github.com> Date: Mon, 1 Sep 2025 17:37:44 +0200 Subject: [PATCH] FEM: Add support for CalculiX truss elements (#23224) * FEM: Update element_geometry1D.py * FEM: Update write_femelement_geometry.py * FEM: Update write_mesh.py * FEM: Update solver.py * FEM: Update solver_calculix.py * FEM: Update element_geometry1D.py --- src/Mod/Fem/femobjects/element_geometry1D.py | 11 ++- src/Mod/Fem/femobjects/solver_calculix.py | 2 +- src/Mod/Fem/femsolver/calculix/solver.py | 2 +- .../calculix/write_femelement_geometry.py | 92 ++++++++++--------- src/Mod/Fem/femsolver/calculix/write_mesh.py | 18 ++-- 5 files changed, 72 insertions(+), 53 deletions(-) diff --git a/src/Mod/Fem/femobjects/element_geometry1D.py b/src/Mod/Fem/femobjects/element_geometry1D.py index 4d30631b8c..4cfe7812d3 100644 --- a/src/Mod/Fem/femobjects/element_geometry1D.py +++ b/src/Mod/Fem/femobjects/element_geometry1D.py @@ -175,7 +175,16 @@ class ElementGeometry1D(base_femelement.BaseFemElement): value=["Rectangular", "Circular", "Pipe", "Elliptical", "Box"], ) ) - + prop.append( + _PropHelper( + type="App::PropertyArea", + name="TrussArea", + group="TrussSection", + doc="Set cross-sectional area of truss elements\n" + + "(used if bending stiffness is excluded in the solver)", + value=10.0, + ) + ) return prop def onDocumentRestored(self, obj): diff --git a/src/Mod/Fem/femobjects/solver_calculix.py b/src/Mod/Fem/femobjects/solver_calculix.py index 5adde76140..1da4e97cbb 100644 --- a/src/Mod/Fem/femobjects/solver_calculix.py +++ b/src/Mod/Fem/femobjects/solver_calculix.py @@ -326,7 +326,7 @@ class SolverCalculiX(base_fempythonobject.BaseFemPythonObject): type="App::PropertyBool", name="ExcludeBendingStiffness", group="Solver", - doc="Exclude bending stiffness to replace shells with membranes", + doc="Exclude bending stiffness to replace shells with membranes or beams with trusses", value=False, ) ) diff --git a/src/Mod/Fem/femsolver/calculix/solver.py b/src/Mod/Fem/femsolver/calculix/solver.py index 64b3da0b9b..4c627276f2 100644 --- a/src/Mod/Fem/femsolver/calculix/solver.py +++ b/src/Mod/Fem/femsolver/calculix/solver.py @@ -428,7 +428,7 @@ class _BaseSolverCalculix: "App::PropertyBool", "ExcludeBendingStiffness", "Fem", - "Exclude bending stiffness to replace shells with membranes", + "Exclude bending stiffness to replace shells with membranes or beams with trusses", locked=True, ) obj.ExcludeBendingStiffness = False diff --git a/src/Mod/Fem/femsolver/calculix/write_femelement_geometry.py b/src/Mod/Fem/femsolver/calculix/write_femelement_geometry.py index a839093bab..0c75303bfa 100644 --- a/src/Mod/Fem/femsolver/calculix/write_femelement_geometry.py +++ b/src/Mod/Fem/femsolver/calculix/write_femelement_geometry.py @@ -45,52 +45,58 @@ def write_femelement_geometry(f, ccxwriter): section_nor = "{:.13G}, {:.13G}, {:.13G}\n".format( beam_axis_m[0], beam_axis_m[1], beam_axis_m[2] ) - if beamsec_obj.SectionType == "Rectangular": - # see meshtools.get_beam_main_axis_m(beam_direction, defined_angle) - # the method get_beam_main_axis_m() which calculates the beam_axis_m vector - # unless rotated, this vector points towards +y axis - # doesn't follow 1,2-direction order of CalculiX - # ^ (n, 2-direction) - # | - # | - # .----> (m, 1-direction) - # - len_beam_axis_n = beamsec_obj.RectHeight.getValueAs("mm").Value - len_beam_axis_m = beamsec_obj.RectWidth.getValueAs("mm").Value - section_type = ", SECTION=RECT" - section_geo = f"{len_beam_axis_m:.13G},{len_beam_axis_n:.13G}\n" - section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n" - elif beamsec_obj.SectionType == "Circular": - diameter = beamsec_obj.CircDiameter.getValueAs("mm").Value - section_type = ", SECTION=CIRC" - section_geo = f"{diameter:.13G}\n" - section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n" - elif beamsec_obj.SectionType == "Elliptical": - axis1 = beamsec_obj.Axis1Length.getValueAs("mm").Value - axis2 = beamsec_obj.Axis2Length.getValueAs("mm").Value - section_type = ", SECTION=CIRC" - section_geo = f"{axis1:.13G},{axis2:.13G}\n" - section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n" - elif beamsec_obj.SectionType == "Pipe": - radius = 0.5 * beamsec_obj.PipeDiameter.getValueAs("mm").Value - thickness = beamsec_obj.PipeThickness.getValueAs("mm").Value - section_type = ", SECTION=PIPE" - section_geo = f"{radius:.13G},{thickness:.13G}\n" - section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n" - elif beamsec_obj.SectionType == "Box": - box_width = beamsec_obj.BoxWidth.getValueAs("mm").Value - box_height = beamsec_obj.BoxHeight.getValueAs("mm").Value - box_t1 = beamsec_obj.BoxT1.getValueAs("mm").Value - box_t2 = beamsec_obj.BoxT2.getValueAs("mm").Value - box_t3 = beamsec_obj.BoxT3.getValueAs("mm").Value - box_t4 = beamsec_obj.BoxT4.getValueAs("mm").Value - section_type = ", SECTION=BOX" - section_geo = f"{box_width:.13G},{box_height:.13G},{box_t1:.13G},{box_t2:.13G},{box_t3:.13G},{box_t4:.13G}\n" - section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n" + if ccxwriter.solver_obj.ExcludeBendingStiffness: + area = beamsec_obj.TrussArea.getValueAs("mm^2").Value + section_def = f"*SOLID SECTION, {elsetdef}{material}\n" + section_geo = f"{area:.13G}\n" + else: + if beamsec_obj.SectionType == "Rectangular": + # see meshtools.get_beam_main_axis_m(beam_direction, defined_angle) + # the method get_beam_main_axis_m() which calculates the beam_axis_m vector + # unless rotated, this vector points towards +y axis + # doesn't follow 1,2-direction order of CalculiX + # ^ (n, 2-direction) + # | + # | + # .----> (m, 1-direction) + # + len_beam_axis_n = beamsec_obj.RectHeight.getValueAs("mm").Value + len_beam_axis_m = beamsec_obj.RectWidth.getValueAs("mm").Value + section_type = ", SECTION=RECT" + section_geo = f"{len_beam_axis_m:.13G},{len_beam_axis_n:.13G}\n" + section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n" + elif beamsec_obj.SectionType == "Circular": + diameter = beamsec_obj.CircDiameter.getValueAs("mm").Value + section_type = ", SECTION=CIRC" + section_geo = f"{diameter:.13G}\n" + section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n" + elif beamsec_obj.SectionType == "Elliptical": + axis1 = beamsec_obj.Axis1Length.getValueAs("mm").Value + axis2 = beamsec_obj.Axis2Length.getValueAs("mm").Value + section_type = ", SECTION=CIRC" + section_geo = f"{axis1:.13G},{axis2:.13G}\n" + section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n" + elif beamsec_obj.SectionType == "Pipe": + radius = 0.5 * beamsec_obj.PipeDiameter.getValueAs("mm").Value + thickness = beamsec_obj.PipeThickness.getValueAs("mm").Value + section_type = ", SECTION=PIPE" + section_geo = f"{radius:.13G},{thickness:.13G}\n" + section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n" + elif beamsec_obj.SectionType == "Box": + box_width = beamsec_obj.BoxWidth.getValueAs("mm").Value + box_height = beamsec_obj.BoxHeight.getValueAs("mm").Value + box_t1 = beamsec_obj.BoxT1.getValueAs("mm").Value + box_t2 = beamsec_obj.BoxT2.getValueAs("mm").Value + box_t3 = beamsec_obj.BoxT3.getValueAs("mm").Value + box_t4 = beamsec_obj.BoxT4.getValueAs("mm").Value + section_type = ", SECTION=BOX" + section_geo = f"{box_width:.13G},{box_height:.13G},{box_t1:.13G},{box_t2:.13G},{box_t3:.13G},{box_t4:.13G}\n" + section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n" f.write(section_def) f.write(section_geo) - f.write(section_nor) + if not ccxwriter.solver_obj.ExcludeBendingStiffness: + f.write(section_nor) elif "fluidsection_obj" in matgeoset: # fluid mesh fluidsec_obj = matgeoset["fluidsection_obj"] if fluidsec_obj.SectionType == "Liquid": diff --git a/src/Mod/Fem/femsolver/calculix/write_mesh.py b/src/Mod/Fem/femsolver/calculix/write_mesh.py index 859c5b3cc9..3d454196f7 100644 --- a/src/Mod/Fem/femsolver/calculix/write_mesh.py +++ b/src/Mod/Fem/femsolver/calculix/write_mesh.py @@ -37,14 +37,18 @@ def write_mesh(ccxwriter): element_param = 1 # highest element order only group_param = False # do not write mesh group data - # Use reduced integration beam elements if this option is enabled in ccx solver settings + # Use reduced integration beam elements or truss elements if this is enabled in ccx solver settings vol_variant = "standard" - edge_variant = "beam" - if ccxwriter.solver_obj.BeamReducedIntegration: - edge_variant = "beam reduced" - # Check to see if fluid sections are in analysis and use D network element type - if ccxwriter.member.geos_fluidsection: - edge_variant = "network" + if ccxwriter.solver_obj.ExcludeBendingStiffness: + edge_variant = "truss" + else: + if ccxwriter.solver_obj.BeamReducedIntegration: + edge_variant = "beam reduced" + else: + edge_variant = "beam" + # Check to see if fluid sections are in analysis and use D network element type + if ccxwriter.member.geos_fluidsection: + edge_variant = "network" # Use 2D elements if model space is not set to 3D if ccxwriter.solver_obj.ModelSpace == "3D":