freecad-cam/Mod/Fem/femtools/checksanalysis.py
2026-02-01 01:59:24 +01:00

379 lines
17 KiB
Python

# ***************************************************************************
# * Copyright (c) 2020 Przemo Firszt <przemo@firszt.eu> *
# * Copyright (c) 2020 Bernd Hahnebach <bernd@bimstatik.org> *
# * *
# * 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__ = "Analysis Checks"
__author__ = "Przemo Firszt, Bernd Hahnebach"
__url__ = "https://www.freecad.org"
## \addtogroup FEM
# @{
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):
message = ""
# solver
if solver.AnalysisType not in ANALYSIS_TYPES:
message += f"Unknown analysis type: {solver.AnalysisType}\n"
if solver.AnalysisType == "frequency":
if not hasattr(solver, "EigenmodeHighLimit"):
message += "Frequency analysis: Solver has no EigenmodeHighLimit.\n"
elif not hasattr(solver, "EigenmodeLowLimit"):
message += "Frequency analysis: Solver has no EigenmodeLowLimit.\n"
elif not hasattr(solver, "EigenmodesCount"):
message += "Frequency analysis: Solver has no EigenmodesCount.\n"
if hasattr(solver, "MaterialNonlinearity") and solver.MaterialNonlinearity == "nonlinear":
if not member.mats_nonlinear:
message += (
"Solver is set to nonlinear materials, "
"but there is no nonlinear material in the analysis.\n"
)
# mesh
if not mesh:
message += "No mesh object defined in the analysis.\n"
if mesh:
if (
mesh.FemMesh.VolumeCount == 0
and mesh.FemMesh.FaceCount > 0
and not member.geos_shellthickness
):
message += (
"FEM mesh has no volume elements, "
"either define a shell thicknesses or "
"provide a FEM mesh with volume elements.\n"
)
if (
mesh.FemMesh.VolumeCount == 0
and mesh.FemMesh.FaceCount == 0
and mesh.FemMesh.EdgeCount > 0
and not member.geos_beamsection
and not member.geos_fluidsection
):
message += (
"FEM mesh has no volume and no shell elements, "
"either define a beam/fluid section or provide "
"a FEM mesh with volume elements.\n"
)
if (
mesh.FemMesh.VolumeCount == 0
and mesh.FemMesh.FaceCount == 0
and mesh.FemMesh.EdgeCount == 0
):
message += (
"FEM mesh has neither volume nor shell or edge elements. "
"Provide a FEM mesh with elements.\n"
)
# material linear and nonlinear
if not member.mats_linear:
message += "No material object defined in the analysis.\n"
has_no_references = False
for m in member.mats_linear:
if len(m["Object"].References) == 0:
if has_no_references is True:
message += (
"More than one material has an empty references list "
"(Only one empty references list is allowed!).\n"
)
has_no_references = True
mat_ref_shty = ""
for m in member.mats_linear:
ref_shty = femutils.get_refshape_type(m["Object"])
if ref_shty == "Compound":
ref_shty = "Solid"
if not mat_ref_shty:
mat_ref_shty = ref_shty
if mat_ref_shty and ref_shty and ref_shty != mat_ref_shty:
# mat_ref_shty could be empty in one material
# only the not empty ones should have the same shape type
message += (
"Some material objects do not have the same reference shape type "
"(all material objects must have the same reference shape type, "
"at the moment).\n"
)
for m in member.mats_linear:
mat_map = m["Object"].Material
mat_obj = m["Object"]
if mat_obj.Category == "Solid":
if "YoungsModulus" in mat_map:
# print(Units.Quantity(mat_map["YoungsModulus"]).Value)
if not Units.Quantity(mat_map["YoungsModulus"]).Value:
message += "Value of YoungsModulus is set to 0.0.\n"
else:
message += "No YoungsModulus defined for at least one material.\n"
if "PoissonRatio" not in mat_map:
# PoissonRatio is allowed to be 0.0 (in ccx), but it should be set anyway.
message += "No PoissonRatio defined for at least one material.\n"
if solver.AnalysisType == "frequency" or member.cons_selfweight:
if "Density" not in mat_map:
message += "No Density defined for at least one material.\n"
if solver.AnalysisType == "thermomech":
if "ThermalConductivity" in mat_map:
if not Units.Quantity(mat_map["ThermalConductivity"]).Value:
message += "Value of ThermalConductivity is set to 0.0.\n"
else:
message += (
"Thermomechanical analysis: No ThermalConductivity defined "
"for at least one material.\n"
)
if "ThermalExpansionCoefficient" not in mat_map and mat_obj.Category == "Solid":
message += (
"Thermomechanical analysis: No ThermalExpansionCoefficient defined "
"for at least one material.\n" # allowed to be 0.0 (in ccx)
)
if "SpecificHeat" not in mat_map:
message += (
"Thermomechanical analysis: No SpecificHeat "
"defined for at least one material.\n" # allowed to be 0.0 (in ccx)
)
if femutils.is_of_type(mat_obj, "Fem::MaterialReinforced"):
# additional tests for reinforced materials,
# they are needed for result calculation not for ccx analysis
mat_map_m = mat_obj.Material
if "AngleOfFriction" in mat_map_m:
# print(Units.Quantity(mat_map_m["AngleOfFriction"]).Value)
if not Units.Quantity(mat_map_m["AngleOfFriction"]).Value:
message += (
"Value of AngleOfFriction is set to 0.0 "
"for the matrix of a reinforced material.\n"
)
else:
message += (
"No AngleOfFriction defined for the matrix "
"of at least one reinforced material.\n"
)
if "CompressiveStrength" in mat_map_m:
# print(Units.Quantity(mat_map_m["CompressiveStrength"]).Value)
if not Units.Quantity(mat_map_m["CompressiveStrength"]).Value:
message += (
"Value of CompressiveStrength is set to 0.0 "
"for the matrix of a reinforced material.\n"
)
else:
message += (
"No CompressiveStrength defined for the matrinx "
"of at least one reinforced material.\n"
)
mat_map_r = mat_obj.Reinforcement
if "YieldStrength" in mat_map_r:
# print(Units.Quantity(mat_map_r["YieldStrength"]).Value)
if not Units.Quantity(mat_map_r["YieldStrength"]).Value:
message += (
"Value of YieldStrength is set to 0.0 "
"for the reinforcement of a reinforced material.\n"
)
else:
message += (
"No YieldStrength defined for the reinforcement "
"of at least one reinforced material.\n"
)
if len(member.mats_linear) == 1:
mobj = member.mats_linear[0]["Object"]
if hasattr(mobj, "References") and mobj.References:
FreeCAD.Console.PrintError(
"Only one material object, but this one has a reference shape. "
"The reference shape will be ignored.\n"
)
for m in member.mats_linear:
has_nonlinear_material = False
for nlm in member.mats_nonlinear:
if nlm["Object"].LinearBaseMaterial == m["Object"]:
if has_nonlinear_material is False:
has_nonlinear_material = True
else:
message += (
"At least two nonlinear materials use the same linear base material. "
"Only one nonlinear material for each linear material allowed.\n"
)
# which analysis needs which constraints
# no check in the regard of loads existence (constraint force, pressure, self weight)
# is done, because an analysis without loads at all is a valid analysis too
if solver.AnalysisType == "static":
if not (member.cons_fixed or member.cons_displacement or member.cons_rigidbody):
message += "Static analysis: No mechanical boundary conditions defined.\n"
if solver.AnalysisType == "thermomech":
if not member.cons_initialtemperature:
if not member.geos_fluidsection:
message += "Thermomechanical analysis: No initial temperature defined.\n"
if len(member.cons_initialtemperature) > 1:
message += "Thermomechanical analysis: Only one initial temperature is allowed.\n"
# constraints
# fixed
if member.cons_fixed:
for c in member.cons_fixed:
if len(c["Object"].References) == 0:
message += "{} has empty references.".format(c["Object"].Name)
# displacement
if member.cons_displacement:
for di in member.cons_displacement:
if len(di["Object"].References) == 0:
message += "{} has empty references.".format(c["Object"].Name)
# plane rotation
if member.cons_planerotation:
for c in member.cons_planerotation:
if len(c["Object"].References) == 0:
message += "{} has empty references.".format(c["Object"].Name)
# contact
if member.cons_contact:
for c in member.cons_contact:
if len(c["Object"].References) == 0:
message += "{} has empty references.".format(c["Object"].Name)
# tie
if member.cons_tie:
for c in member.cons_tie:
items = 0
for reference in c["Object"].References:
items += len(reference[1])
if items != 2:
message += "{} doesn't reference exactly two needed faces.\n".format(
c["Object"].Name
)
# sectionprint
if member.cons_sectionprint:
for c in member.cons_sectionprint:
items = 0
for reference in c["Object"].References:
items += len(reference[1])
if items != 1:
message += "{} doesn't reference exactly one needed face.\n".format(
c["Object"].Name
)
# transform
if member.cons_transform:
for c in member.cons_transform:
if len(c["Object"].References) == 0:
message += "{} has empty references.".format(c["Object"].Name)
# pressure
if member.cons_pressure:
for c in member.cons_pressure:
if len(c["Object"].References) == 0:
message += "{} has empty references.".format(c["Object"].Name)
# force
if member.cons_force:
for c in member.cons_force:
if len(c["Object"].References) == 0:
message += "{} has empty references.".format(c["Object"].Name)
# temperature
if member.cons_temperature:
for c in member.cons_temperature:
if len(c["Object"].References) == 0:
message += "{} has empty references.".format(c["Object"].Name)
# heat flux
if member.cons_heatflux:
for c in member.cons_heatflux:
if len(c["Object"].References) == 0:
message += "{} has empty references.".format(c["Object"].Name)
# geometries
# beam section
if member.geos_beamsection:
if member.geos_shellthickness:
# this needs to be checked only once either here or in shell_thicknesses
message += (
"Beam sections and shell thicknesses in one analysis "
"is not supported at the moment.\n"
)
if member.geos_fluidsection:
# this needs to be checked only once either here or in shell_thicknesses
message += (
"Beam sections and fluid sections in one analysis "
"is not supported at the moment.\n"
)
has_no_references = False
for b in member.geos_beamsection:
if len(b["Object"].References) == 0:
if has_no_references is True:
message += (
"More than one beam section has an empty references "
"list (Only one empty references list is allowed!).\n"
)
has_no_references = True
if mesh:
if mesh.FemMesh.FaceCount > 0 or mesh.FemMesh.VolumeCount > 0:
message += "Beam sections defined but FEM mesh has volume or shell elements.\n"
if mesh.FemMesh.EdgeCount == 0:
message += "Beam sections defined but FEM mesh has no edge elements.\n"
if not (hasattr(mesh, "Shape") or hasattr(mesh, "Part")):
message += (
"Mesh without geometry link. "
"The mesh needs to know its geometry for the beam rotations.\n"
)
if len(member.geos_beamrotation) > 1:
message += "Multiple beam rotations in one analysis are not supported at the moment.\n"
# beam rotations
if member.geos_beamrotation and not member.geos_beamsection:
message += "Beam rotations in the analysis but no beam sections defined.\n"
# shell thickness
if member.geos_shellthickness:
has_no_references = False
for s in member.geos_shellthickness:
if len(s["Object"].References) == 0:
if has_no_references is True:
message += (
"More than one shell thickness has an empty references "
"list (Only one empty references list is allowed!).\n"
)
has_no_references = True
if mesh:
if mesh.FemMesh.VolumeCount > 0:
message += "Shell thicknesses defined but FEM mesh has volume elements.\n"
if mesh.FemMesh.FaceCount == 0:
message += "Shell thicknesses defined but FEM mesh has no shell elements.\n"
# fluid section
if member.geos_fluidsection:
if not member.cons_selfweight:
message += "A fluid network analysis requires self weight constraint to be applied\n"
if solver.AnalysisType != "thermomech":
message += "A fluid network analysis can only be done in a thermomech analysis\n"
has_no_references = False
for f in member.geos_fluidsection:
if len(f["Object"].References) == 0:
if has_no_references is True:
message += (
"More than one fluid section has an empty references list "
"(Only one empty references list is allowed!).\n"
)
has_no_references = True
if mesh:
if mesh.FemMesh.FaceCount > 0 or mesh.FemMesh.VolumeCount > 0:
message += "Fluid sections defined but FEM mesh has volume or shell elements.\n"
if mesh.FemMesh.EdgeCount == 0:
message += "Fluid sections defined but FEM mesh has no edge elements.\n"
return message
## @}