From fd3f17ea4575fecb81b05e5506eb069e6fe3c754 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Thu, 15 Jul 2021 17:17:28 +0200 Subject: [PATCH] FEM: separate getting the mesh sets from writer class --- src/Mod/Fem/CMakeLists.txt | 1 + src/Mod/Fem/femmesh/meshsetsgetter.py | 1047 ++++++++++++++++++++++ src/Mod/Fem/femsolver/calculix/writer.py | 55 +- src/Mod/Fem/femsolver/writerbase.py | 1018 ++------------------- 4 files changed, 1165 insertions(+), 956 deletions(-) create mode 100644 src/Mod/Fem/femmesh/meshsetsgetter.py diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index f597274816..4ff7364a4d 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -133,6 +133,7 @@ SET(FemMesh_SRCS femmesh/__init__.py femmesh/femmesh2mesh.py femmesh/gmshtools.py + femmesh/meshsetsgetter.py femmesh/meshtools.py ) diff --git a/src/Mod/Fem/femmesh/meshsetsgetter.py b/src/Mod/Fem/femmesh/meshsetsgetter.py new file mode 100644 index 0000000000..06f8a575c8 --- /dev/null +++ b/src/Mod/Fem/femmesh/meshsetsgetter.py @@ -0,0 +1,1047 @@ +# *************************************************************************** +# * Copyright (c) 2021 Bernd Hahnebach * +# * * +# * 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 sets getter" +__author__ = "Bernd Hahnebach" +__url__ = "https://www.freecadweb.org" + +## \addtogroup FEM +# @{ + +import FreeCAD + +from femmesh import meshtools +from femtools.femutils import type_of_obj + + +class MeshSetsGetter(): + def __init__( + self, + analysis_obj, + solver_obj, + mesh_obj, + member, + ): + # class attributes from parameter values + self.analysis = analysis_obj + self.solver_obj = solver_obj # TODO without _obj + self.mesh_object = mesh_obj # TODO without _object + self.member = member + + # more attributes + self.analysis_type = self.solver_obj.AnalysisType + self.document = self.analysis.Document + self.fc_ver = FreeCAD.Version() + self.ccx_nall = "Nall" + self.ccx_eall = "Eall" + self.ccx_evolumes = "Evolumes" + self.ccx_efaces = "Efaces" + self.ccx_eedges = "Eedges" + self.mat_geo_sets = [] + if self.mesh_object: + if hasattr(self.mesh_object, "Shape"): + self.theshape = self.mesh_object.Shape + elif hasattr(self.mesh_object, "Part"): + self.theshape = self.mesh_object.Part + else: + FreeCAD.Console.PrintWarning( + "A finite mesh without a link to a Shape was given. " + "Happen on pure mesh objects. " + "Not all methods do work without this link.\n" + ) + # ATM only used in meshtools.get_femelement_direction1D_set + # TODO somehow this is not smart, rare meshes might be used often + self.femmesh = self.mesh_object.FemMesh + else: + FreeCAD.Console.PrintWarning( + "No finite element mesh object was given to the writer class. " + "In rare cases this might not be an error. " + ) + self.femnodes_mesh = {} + self.femelement_table = {} + self.constraint_conflict_nodes = [] + self.femnodes_ele_table = {} + self.femelements_edges_only = [] + self.femelements_faces_only = [] + self.femelement_volumes_table = {} + self.femelement_faces_table = {} + self.femelement_edges_table = {} + self.femelement_count_test = True + + # ******************************************************************************************** + # ******************************************************************************************** + # use set for node sets to be sure all nodes are unique + # use sorted to be sure the order is the same on different runs + # be aware a sorted set returns a list, because set are not sorted by default + # - done in return value of meshtools.get_femnodes_by_femobj_with_references + # TODO FIXME might be appropriate for element sets and surfaceface sets too + + # ******************************************************************************************** + # ******************************************************************************************** + # get all known sets + def get_mesh_sets(self): + FreeCAD.Console.PrintMessage( + "!!!!!!!!!!!!!!!!!!!!!!!!! MeshSetsGetter class :-) !!!!!!!!!!!!!!!!!!!!!!!!!\n" + "Get mesh data for " + "node sets (groups), surface sets (groups) and element sets (groups)\n" + ) + + # materials and element geometry element sets getter + self.get_element_sets_material_and_femelement_geometry() + + # constraints element sets getter + self.get_constraints_centrif_elements() + + # constraints node sets getter + self.get_constraints_fixed_nodes() + self.get_constraints_displacement_nodes() + self.get_constraints_planerotation_nodes() + + # constraints surface sets getter + self.get_constraints_contact_faces() + self.get_constraints_tie_faces() + self.get_constraints_sectionprint_faces() + self.get_constraints_transform_nodes() + self.get_constraints_temperature_nodes() + + # constraints sets with constraint data + self.get_constraints_force_nodeloads() + self.get_constraints_pressure_faces() + self.get_constraints_heatflux_faces() + + # ******************************************************************************************** + # ******************************************************************************************** + # node sets + def get_constraints_fixed_nodes(self): + if not self.member.cons_fixed: + return + # get nodes + for femobj in self.member.cons_fixed: + # femobj --> dict, FreeCAD document object is femobj["Object"] + print_obj_info(femobj["Object"]) + femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( + self.femmesh, + femobj + ) + # add nodes to constraint_conflict_nodes, needed by constraint plane rotation + for node in femobj["Nodes"]: + self.constraint_conflict_nodes.append(node) + # if mixed mesh with solids the node set needs to be split + # because solid nodes do not have rotational degree of freedom + if ( + self.femmesh.Volumes + and ( + len(self.member.geos_shellthickness) > 0 + or len(self.member.geos_beamsection) > 0 + ) + ): + FreeCAD.Console.PrintMessage("We need to find the solid nodes.\n") + if not self.femelement_volumes_table: + self.femelement_volumes_table = meshtools.get_femelement_volumes_table( + self.femmesh + ) + for femobj in self.member.cons_fixed: + # femobj --> dict, FreeCAD document object is femobj["Object"] + nds_solid = [] + nds_faceedge = [] + for n in femobj["Nodes"]: + solid_node = False + for ve in self.femelement_volumes_table: + if n in self.femelement_volumes_table[ve]: + solid_node = True + nds_solid.append(n) + break + if not solid_node: + nds_faceedge.append(n) + femobj["NodesSolid"] = set(nds_solid) + femobj["NodesFaceEdge"] = set(nds_faceedge) + + def get_constraints_displacement_nodes(self): + if not self.member.cons_displacement: + return + # get nodes + for femobj in self.member.cons_displacement: + # femobj --> dict, FreeCAD document object is femobj["Object"] + print_obj_info(femobj["Object"]) + femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( + self.femmesh, + femobj + ) + # add nodes to constraint_conflict_nodes, needed by constraint plane rotation + for node in femobj["Nodes"]: + self.constraint_conflict_nodes.append(node) + + def get_constraints_planerotation_nodes(self): + if not self.member.cons_planerotation: + return + # get nodes + for femobj in self.member.cons_planerotation: + # femobj --> dict, FreeCAD document object is femobj["Object"] + print_obj_info(femobj["Object"]) + femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( + self.femmesh, + femobj + ) + + def get_constraints_transform_nodes(self): + if not self.member.cons_transform: + return + # get nodes + for femobj in self.member.cons_transform: + # femobj --> dict, FreeCAD document object is femobj["Object"] + print_obj_info(femobj["Object"]) + femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( + self.femmesh, + femobj + ) + + def get_constraints_temperature_nodes(self): + if not self.member.cons_temperature: + return + # get nodes + for femobj in self.member.cons_temperature: + # femobj --> dict, FreeCAD document object is femobj["Object"] + print_obj_info(femobj["Object"]) + femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( + self.femmesh, + femobj + ) + + def get_constraints_fluidsection_nodes(self): + if not self.member.geos_fluidsection: + return + # get nodes + for femobj in self.member.geos_fluidsection: + # femobj --> dict, FreeCAD document object is femobj["Object"] + print_obj_info(femobj["Object"]) + femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( + self.femmesh, + femobj + ) + + def get_constraints_force_nodeloads(self): + if not self.member.cons_force: + return + # check shape type of reference shape + for femobj in self.member.cons_force: + # femobj --> dict, FreeCAD document object is femobj["Object"] + print_obj_info(femobj["Object"], log=True) + if femobj["RefShapeType"] == "Vertex": + FreeCAD.Console.PrintLog( + " load on vertices --> The femelement_table " + "and femnodes_mesh are not needed for node load calculation.\n" + ) + elif femobj["RefShapeType"] == "Face" \ + and meshtools.is_solid_femmesh(self.femmesh) \ + and not meshtools.has_no_face_data(self.femmesh): + FreeCAD.Console.PrintLog( + " solid_mesh with face data --> The femelement_table is not " + "needed but the femnodes_mesh is needed for node load calculation.\n" + ) + if not self.femnodes_mesh: + self.femnodes_mesh = self.femmesh.Nodes + else: + FreeCAD.Console.PrintLog( + " mesh without needed data --> The femelement_table " + "and femnodes_mesh are not needed for node load calculation.\n" + ) + if not self.femnodes_mesh: + self.femnodes_mesh = self.femmesh.Nodes + if not self.femelement_table: + self.femelement_table = meshtools.get_femelement_table( + self.femmesh + ) + # get node loads + FreeCAD.Console.PrintLog( + " Finite element mesh nodes will be retrieved by searching " + "the appropriate nodes in the finite element mesh.\n" + ) + FreeCAD.Console.PrintLog( + " The appropriate finite element mesh node load values will " + "be calculated according to the finite element definition.\n" + ) + for femobj in self.member.cons_force: + # femobj --> dict, FreeCAD document object is femobj["Object"] + frc_obj = femobj["Object"] + print_obj_info(frc_obj) + if frc_obj.Force == 0: + FreeCAD.Console.PrintMessage(" Warning --> Force = 0\n") + if femobj["RefShapeType"] == "Vertex": # point load on vertices + femobj["NodeLoadTable"] = meshtools.get_force_obj_vertex_nodeload_table( + self.femmesh, + frc_obj + ) + elif femobj["RefShapeType"] == "Edge": # line load on edges + femobj["NodeLoadTable"] = meshtools.get_force_obj_edge_nodeload_table( + self.femmesh, + self.femelement_table, + self.femnodes_mesh, frc_obj + ) + elif femobj["RefShapeType"] == "Face": # area load on faces + femobj["NodeLoadTable"] = meshtools.get_force_obj_face_nodeload_table( + self.femmesh, + self.femelement_table, + self.femnodes_mesh, frc_obj + ) + + # ******************************************************************************************** + # ******************************************************************************************** + # faces sets + def get_constraints_pressure_faces(self): + if not self.member.cons_pressure: + return + # TODO see comments in get_constraints_force_nodeloads() + # it applies here too. Mhh it applies to all constraints ... + + """ + # deprecated version + # get the faces and face numbers + for femobj in self.member.cons_pressure: + # femobj --> dict, FreeCAD document object is femobj["Object"] + femobj["PressureFaces"] = meshtools.get_pressure_obj_faces_depreciated( + self.femmesh, + femobj + ) + # print(femobj["PressureFaces"]) + """ + + if not self.femnodes_mesh: + self.femnodes_mesh = self.femmesh.Nodes + if not self.femelement_table: + self.femelement_table = meshtools.get_femelement_table(self.femmesh) + if not self.femnodes_ele_table: + self.femnodes_ele_table = meshtools.get_femnodes_ele_table( + self.femnodes_mesh, + self.femelement_table + ) + + for femobj in self.member.cons_pressure: + # femobj --> dict, FreeCAD document object is femobj["Object"] + print_obj_info(femobj["Object"]) + pressure_faces = meshtools.get_pressure_obj_faces( + self.femmesh, + self.femelement_table, + self.femnodes_ele_table, femobj + ) + # the data model is for compatibility reason with deprecated version + # get_pressure_obj_faces_depreciated returns the face ids in a tuple per ref_shape + # some_string was the reference_shape_element_string in deprecated method + # [(some_string, [ele_id, ele_face_id], [ele_id, ele_face_id], ...])] + some_string = "{}: face load".format(femobj["Object"].Name) + femobj["PressureFaces"] = [(some_string, pressure_faces)] + FreeCAD.Console.PrintLog("{}\n".format(femobj["PressureFaces"])) + + def get_constraints_contact_faces(self): + if not self.member.cons_contact: + return + if not self.femnodes_mesh: + self.femnodes_mesh = self.femmesh.Nodes + if not self.femelement_table: + self.femelement_table = meshtools.get_femelement_table(self.femmesh) + if not self.femnodes_ele_table: + self.femnodes_ele_table = meshtools.get_femnodes_ele_table( + self.femnodes_mesh, + self.femelement_table + ) + + for femobj in self.member.cons_contact: + # femobj --> dict, FreeCAD document object is femobj["Object"] + print_obj_info(femobj["Object"]) + contact_slave_faces, contact_master_faces = meshtools.get_contact_obj_faces( + self.femmesh, + self.femelement_table, + self.femnodes_ele_table, femobj + ) + # [ele_id, ele_face_id], [ele_id, ele_face_id], ...] + # whereas the ele_face_id might be ccx specific + femobj["ContactSlaveFaces"] = contact_slave_faces + femobj["ContactMasterFaces"] = contact_master_faces + # FreeCAD.Console.PrintLog("{}\n".format(femobj["ContactSlaveFaces"])) + # FreeCAD.Console.PrintLog("{}\n".format(femobj["ContactMasterFaces"])) + + # information in the regard of element faces constraints + # forum post: https://forum.freecadweb.org/viewtopic.php?f=18&t=42783&p=370286#p366723 + # contact: master and slave could be the same face: rubber of a damper + # tie: master and slave have to be separate faces AFA UR_ K + # section print: only the element faces of solid elements + # from one side of the geometric face are needed + + def get_constraints_tie_faces(self): + if not self.member.cons_tie: + return + if not self.femnodes_mesh: + self.femnodes_mesh = self.femmesh.Nodes + if not self.femelement_table: + self.femelement_table = meshtools.get_femelement_table(self.femmesh) + if not self.femnodes_ele_table: + self.femnodes_ele_table = meshtools.get_femnodes_ele_table( + self.femnodes_mesh, + self.femelement_table + ) + + for femobj in self.member.cons_tie: + # femobj --> dict, FreeCAD document object is femobj["Object"] + print_obj_info(femobj["Object"]) + slave_faces, master_faces = meshtools.get_tie_obj_faces( + self.femmesh, + self.femelement_table, + self.femnodes_ele_table, femobj + ) + # [ele_id, ele_face_id], [ele_id, ele_face_id], ...] + # whereas the ele_face_id might be ccx specific + femobj["TieSlaveFaces"] = slave_faces + femobj["TieMasterFaces"] = master_faces + # FreeCAD.Console.PrintLog("{}\n".format(femobj["ContactSlaveFaces"])) + # FreeCAD.Console.PrintLog("{}\n".format(femobj["ContactMasterFaces"])) + + def get_constraints_sectionprint_faces(self): + if not self.member.cons_sectionprint: + return + # TODO: use meshtools to get the surfaces + # see constraint contact or constraint tie + for femobj in self.member.cons_sectionprint: + # femobj --> dict, FreeCAD document object is femobj["Object"] + sectionprint_obj = femobj["Object"] + if len(sectionprint_obj.References) > 1: + FreeCAD.Console.PrintError( + "Only one reference shape allowed for a section print " + "but {} found: {}\n" + .format(len(sectionprint_obj.References), sectionprint_obj.References) + ) + for o, elem_tup in sectionprint_obj.References: + for elem in elem_tup: + # there should only be one reference for each section print object + # in the gui this is checked + ref_shape = o.Shape.getElement(elem) + if ref_shape.ShapeType == "Face": + v = self.mesh_object.FemMesh.getccxVolumesByFace(ref_shape) + if len(v) > 0: + femobj["SectionPrintFaces"] = v + # volume elements found + FreeCAD.Console.PrintLog( + "{}, surface {}, {} touching volume elements found\n" + .format(sectionprint_obj.Label, sectionprint_obj.Name, len(v)) + ) + else: + # no volume elements found, shell elements not allowed + FreeCAD.Console.PrintError( + "{}, surface {}, Error: " + "No volume elements found!\n" + .format(sectionprint_obj.Label, sectionprint_obj.Name) + ) + else: + # in Gui only Faces can be added + FreeCAD.Console.PrintError( + "Wrong reference shape type for {} " + "Only Faces are allowed, but a {} was found.\n" + .format(sectionprint_obj.Name, ref_shape.ShapeType) + ) + + def get_constraints_heatflux_faces(self): + if not self.member.cons_heatflux: + return + # TODO: use meshtools to get the surfaces (or move to mesh tools) + # see constraint contact or constraint tie and constraint force + # heatflux_obj_face_table: see force_obj_node_load_table + # [ + # ("refshape_name:elemname", face_table), + # ..., + # ("refshape_name:elemname", face_table) + # ] + for femobj in self.member.cons_heatflux: + # femobj --> dict, FreeCAD document object is femobj["Object"] + heatflux_obj = femobj["Object"] + femobj["HeatFluxFaceTable"] = [] + for o, elem_tup in heatflux_obj.References: + for elem in elem_tup: + ho = o.Shape.getElement(elem) + if ho.ShapeType == "Face": + elem_info = "{}:{}".format(o.Name, elem) + face_table = self.mesh_object.FemMesh.getccxVolumesByFace(ho) + femobj["HeatFluxFaceTable"].append((elem_info, face_table)) + + # ******************************************************************************************** + # ******************************************************************************************** + # element sets constraints + def get_constraints_centrif_elements(self): + # get element ids and write them into the femobj + if not self.member.cons_centrif: + return + if ( + len(self.member.cons_centrif) == 1 + and not self.member.cons_centrif[0]["Object"].References + ): + self.member.cons_centrif[0]["FEMElements"] = self.ccx_evolumes + else: + self.get_solid_element_sets(self.member.cons_centrif) + + # ******************************************************************************************** + # ******************************************************************************************** + # element sets material and element geometry + def get_solid_element_sets(self, femobjs): + # get element ids and write them into the femobj + all_found = False + if self.femmesh.GroupCount: + all_found = meshtools.get_femelement_sets_from_group_data( + self.femmesh, + femobjs + ) + FreeCAD.Console.PrintMessage(all_found) + FreeCAD.Console.PrintMessage("\n") + if all_found is False: + if not self.femelement_table: + self.femelement_table = meshtools.get_femelement_table(self.femmesh) + # we're going to use the binary search for get_femelements_by_femnodes() + # thus we need the parameter values self.femnodes_ele_table + if not self.femnodes_mesh: + self.femnodes_mesh = self.femmesh.Nodes + if not self.femnodes_ele_table: + self.femnodes_ele_table = meshtools.get_femnodes_ele_table( + self.femnodes_mesh, + self.femelement_table + ) + control = meshtools.get_femelement_sets( + self.femmesh, + self.femelement_table, + femobjs, + self.femnodes_ele_table + ) + # we only need to set it, if it is still True + if (self.femelement_count_test is True) and (control is False): + self.femelement_count_test = False + + def get_element_geometry2D_elements(self): + # get element ids and write them into the objects + FreeCAD.Console.PrintMessage("Shell thicknesses\n") + if not self.femelement_faces_table: + self.femelement_faces_table = meshtools.get_femelement_faces_table( + self.femmesh + ) + meshtools.get_femelement_sets( + self.femmesh, + self.femelement_faces_table, + self.member.geos_shellthickness + ) + + def get_element_geometry1D_elements(self): + # get element ids and write them into the objects + FreeCAD.Console.PrintMessage("Beam sections\n") + if not self.femelement_edges_table: + self.femelement_edges_table = meshtools.get_femelement_edges_table( + self.femmesh + ) + meshtools.get_femelement_sets( + self.femmesh, + self.femelement_edges_table, + self.member.geos_beamsection + ) + + def get_element_rotation1D_elements(self): + # get for each geometry edge direction the element ids and rotation norma + FreeCAD.Console.PrintMessage("Beam rotations\n") + if not self.femelement_edges_table: + self.femelement_edges_table = meshtools.get_femelement_edges_table( + self.femmesh + ) + meshtools.get_femelement_direction1D_set( + self.femmesh, + self.femelement_edges_table, + self.member.geos_beamrotation, + self.theshape + ) + + def get_element_fluid1D_elements(self): + # get element ids and write them into the objects + FreeCAD.Console.PrintMessage("Fluid sections\n") + if not self.femelement_edges_table: + self.femelement_edges_table = meshtools.get_femelement_edges_table( + self.femmesh + ) + meshtools.get_femelement_sets( + self.femmesh, + self.femelement_edges_table, + self.member.geos_fluidsection + ) + + def get_material_elements(self): + # it only works if either Volumes or Shellthicknesses or Beamsections + # are in the material objects, it means it does not work + # for mixed meshes and multiple materials, this is checked in check_prerequisites + # the femelement_table is only calculated for + # the highest dimension in get_femelement_table + FreeCAD.Console.PrintMessage("Materials\n") + if self.femmesh.Volumes: + # we only could do this for volumes + # if a mesh contains volumes we're going to use them in the analysis + # but a mesh could contain + # the element faces of the volumes as faces + # and the edges of the faces as edges + # there we have to check of some geometric objects + # get element ids and write them into the femobj + self.get_solid_element_sets(self.member.mats_linear) + if self.member.geos_shellthickness: + if not self.femelement_faces_table: + self.femelement_faces_table = meshtools.get_femelement_faces_table( + self.femmesh + ) + meshtools.get_femelement_sets( + self.femmesh, + self.femelement_faces_table, + self.member.mats_linear + ) + if self.member.geos_beamsection or self.member.geos_fluidsection: + if not self.femelement_edges_table: + self.femelement_edges_table = meshtools.get_femelement_edges_table( + self.femmesh + ) + meshtools.get_femelement_sets( + self.femmesh, + self.femelement_edges_table, + self.member.mats_linear + ) + + def get_element_sets_material_and_femelement_geometry(self): + if not self.member.mats_linear: + return + # in any case if we have beams, we're going to need the element ids for the rotation elsets + if self.member.geos_beamsection: + # we will need to split the beam even for one beamobj + # because no beam in z-direction can be used in ccx without a special adjustment + # thus they need an own ccx_elset + self.get_element_rotation1D_elements() + + # get the element ids for face and edge elements and write them into the objects + if len(self.member.geos_shellthickness) > 1: + self.get_element_geometry2D_elements() + if len(self.member.geos_beamsection) > 1: + self.get_element_geometry1D_elements() + if len(self.member.geos_fluidsection) > 1: + self.get_element_fluid1D_elements() + + # get the element ids for material objects and write them into the material object + if len(self.member.mats_linear) > 1: + self.get_material_elements() + + # create the ccx_elsets + if len(self.member.mats_linear) == 1: + if self.femmesh.Volumes: + print("here1") + # we only could do this for volumes, if a mesh contains volumes + # we're going to use them in the analysis + # but a mesh could contain the element faces of the volumes as faces + # and the edges of the faces as edges + # there we have to check for some geometric objects + self.get_ccx_elsets_single_mat_solid() + if len(self.member.geos_shellthickness) == 1: + self.get_ccx_elsets_single_mat_single_shell() + elif len(self.member.geos_shellthickness) > 1: + self.get_ccx_elsets_single_mat_multiple_shell() + if len(self.member.geos_beamsection) == 1: + self.get_ccx_elsets_single_mat_single_beam() + elif len(self.member.geos_beamsection) > 1: + self.get_ccx_elsets_single_mat_multiple_beam() + if len(self.member.geos_fluidsection) == 1: + self.get_ccx_elsets_single_mat_single_fluid() + elif len(self.member.geos_fluidsection) > 1: + self.get_ccx_elsets_single_mat_multiple_fluid() + elif len(self.member.mats_linear) > 1: + if self.femmesh.Volumes: + # we only could do this for volumes, if a mseh contains volumes + # we're going to use them in the analysis + # but a mesh could contain the element faces of the volumes as faces + # and the edges of the faces as edges + # there we have to check for some geometric objects + # volume is a bit special + # because retrieving ids from group mesh data is implemented + self.get_ccx_elsets_multiple_mat_solid() + if len(self.member.geos_shellthickness) == 1: + self.get_ccx_elsets_multiple_mat_single_shell() + elif len(self.member.geos_shellthickness) > 1: + self.get_ccx_elsets_multiple_mat_multiple_shell() + if len(self.member.geos_beamsection) == 1: + self.get_ccx_elsets_multiple_mat_single_beam() + elif len(self.member.geos_beamsection) > 1: + self.get_ccx_elsets_multiple_mat_multiple_beam() + if len(self.member.geos_fluidsection) == 1: + self.get_ccx_elsets_multiple_mat_single_fluid() + elif len(self.member.geos_fluidsection) > 1: + self.get_ccx_elsets_multiple_mat_multiple_fluid() + + # self.mat_geo_sets = [ { + # "ccx_elset" : [e1, e2, e3, ... , en] or elements set name strings + # "ccx_elset_name" : "ccx_identifier_elset" + # "mat_obj_name" : "mat_obj.Name" + # "ccx_mat_name" : "mat_obj.Material["Name"]" !!! not unique !!! + # "beamsection_obj" : "beamsection_obj" if exists + # "fluidsection_obj" : "fluidsection_obj" if exists + # "shellthickness_obj" : shellthickness_obj" if exists + # "beam_normal" : normal vector for beams only + # }, + # {}, ... , {} ] + + # beam + # TODO support multiple beamrotations + # we do not need any more any data from the rotation document object, + # thus we do not need to save the rotation document object name in the else + def get_ccx_elsets_single_mat_single_beam(self): + mat_obj = self.member.mats_linear[0]["Object"] + beamsec_obj = self.member.geos_beamsection[0]["Object"] + beamrot_data = self.member.geos_beamrotation[0] + for i, beamdirection in enumerate(beamrot_data["FEMRotations1D"]): + # ID's for this direction + elset_data = beamdirection["ids"] + names = [ + {"short": "M0"}, + {"short": "B0"}, + {"short": beamrot_data["ShortName"]}, + {"short": "D" + str(i)} + ] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["beamsection_obj"] = beamsec_obj + # normal for this direction + ccx_elset["beam_normal"] = beamdirection["normal"] + self.mat_geo_sets.append(ccx_elset) + + def get_ccx_elsets_single_mat_multiple_beam(self): + mat_obj = self.member.mats_linear[0]["Object"] + beamrot_data = self.member.geos_beamrotation[0] + for beamsec_data in self.member.geos_beamsection: + beamsec_obj = beamsec_data["Object"] + beamsec_ids = set(beamsec_data["FEMElements"]) + for i, beamdirection in enumerate(beamrot_data["FEMRotations1D"]): + beamdir_ids = set(beamdirection["ids"]) + # empty intersection sets possible + elset_data = list(sorted(beamsec_ids.intersection(beamdir_ids))) + if elset_data: + names = [ + {"short": "M0"}, + {"short": beamsec_data["ShortName"]}, + {"short": beamrot_data["ShortName"]}, + {"short": "D" + str(i)} + ] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["beamsection_obj"] = beamsec_obj + # normal for this direction + ccx_elset["beam_normal"] = beamdirection["normal"] + self.mat_geo_sets.append(ccx_elset) + + def get_ccx_elsets_multiple_mat_single_beam(self): + beamsec_obj = self.member.geos_beamsection[0]["Object"] + beamrot_data = self.member.geos_beamrotation[0] + for mat_data in self.member.mats_linear: + mat_obj = mat_data["Object"] + mat_ids = set(mat_data["FEMElements"]) + for i, beamdirection in enumerate(beamrot_data["FEMRotations1D"]): + beamdir_ids = set(beamdirection["ids"]) + elset_data = list(sorted(mat_ids.intersection(beamdir_ids))) + if elset_data: + names = [ + {"short": mat_data["ShortName"]}, + {"short": "B0"}, + {"short": beamrot_data["ShortName"]}, + {"short": "D" + str(i)} + ] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["beamsection_obj"] = beamsec_obj + # normal for this direction + ccx_elset["beam_normal"] = beamdirection["normal"] + self.mat_geo_sets.append(ccx_elset) + + def get_ccx_elsets_multiple_mat_multiple_beam(self): + beamrot_data = self.member.geos_beamrotation[0] + for beamsec_data in self.member.geos_beamsection: + beamsec_obj = beamsec_data["Object"] + beamsec_ids = set(beamsec_data["FEMElements"]) + for mat_data in self.member.mats_linear: + mat_obj = mat_data["Object"] + mat_ids = set(mat_data["FEMElements"]) + for i, beamdirection in enumerate(beamrot_data["FEMRotations1D"]): + beamdir_ids = set(beamdirection["ids"]) + # empty intersection sets possible + elset_data = list(sorted( + beamsec_ids.intersection(mat_ids).intersection(beamdir_ids) + )) + if elset_data: + names = [ + {"short": mat_data["ShortName"]}, + {"short": beamsec_data["ShortName"]}, + {"short": beamrot_data["ShortName"]}, + {"short": "D" + str(i)} + ] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["beamsection_obj"] = beamsec_obj + # normal for this direction + ccx_elset["beam_normal"] = beamdirection["normal"] + self.mat_geo_sets.append(ccx_elset) + + # fluid + def get_ccx_elsets_single_mat_single_fluid(self): + mat_obj = self.member.mats_linear[0]["Object"] + fluidsec_obj = self.member.geos_fluidsection[0]["Object"] + elset_data = self.ccx_eedges + names = [{"short": "M0"}, {"short": "F0"}] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["fluidsection_obj"] = fluidsec_obj + self.mat_geo_sets.append(ccx_elset) + + def get_ccx_elsets_single_mat_multiple_fluid(self): + mat_obj = self.member.mats_linear[0]["Object"] + for fluidsec_data in self.member.geos_fluidsection: + fluidsec_obj = fluidsec_data["Object"] + elset_data = fluidsec_data["FEMElements"] + names = [{"short": "M0"}, {"short": fluidsec_data["ShortName"]}] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["fluidsection_obj"] = fluidsec_obj + self.mat_geo_sets.append(ccx_elset) + + def get_ccx_elsets_multiple_mat_single_fluid(self): + fluidsec_obj = self.member.geos_fluidsection[0]["Object"] + for mat_data in self.member.mats_linear: + mat_obj = mat_data["Object"] + elset_data = mat_data["FEMElements"] + names = [{"short": mat_data["ShortName"]}, {"short": "F0"}] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["fluidsection_obj"] = fluidsec_obj + self.mat_geo_sets.append(ccx_elset) + + def get_ccx_elsets_multiple_mat_multiple_fluid(self): + for fluidsec_data in self.member.geos_fluidsection: + fluidsec_obj = fluidsec_data["Object"] + for mat_data in self.member.mats_linear: + mat_obj = mat_data["Object"] + fluidsec_ids = set(fluidsec_data["FEMElements"]) + mat_ids = set(mat_data["FEMElements"]) + # empty intersection sets possible + elset_data = list(sorted(fluidsec_ids.intersection(mat_ids))) + if elset_data: + names = [ + {"short": mat_data["ShortName"]}, + {"short": fluidsec_data["ShortName"]} + ] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["fluidsection_obj"] = fluidsec_obj + self.mat_geo_sets.append(ccx_elset) + + # shell + def get_ccx_elsets_single_mat_single_shell(self): + mat_obj = self.member.mats_linear[0]["Object"] + shellth_obj = self.member.geos_shellthickness[0]["Object"] + elset_data = self.ccx_efaces + names = [ + {"long": mat_obj.Name, "short": "M0"}, + {"long": shellth_obj.Name, "short": "S0"} + ] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["shellthickness_obj"] = shellth_obj + self.mat_geo_sets.append(ccx_elset) + + def get_ccx_elsets_single_mat_multiple_shell(self): + mat_obj = self.member.mats_linear[0]["Object"] + for shellth_data in self.member.geos_shellthickness: + shellth_obj = shellth_data["Object"] + elset_data = shellth_data["FEMElements"] + names = [ + {"long": mat_obj.Name, "short": "M0"}, + {"long": shellth_obj.Name, "short": shellth_data["ShortName"]} + ] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["shellthickness_obj"] = shellth_obj + self.mat_geo_sets.append(ccx_elset) + + def get_ccx_elsets_multiple_mat_single_shell(self): + shellth_obj = self.member.geos_shellthickness[0]["Object"] + for mat_data in self.member.mats_linear: + mat_obj = mat_data["Object"] + elset_data = mat_data["FEMElements"] + names = [ + {"long": mat_obj.Name, "short": mat_data["ShortName"]}, + {"long": shellth_obj.Name, "short": "S0"} + ] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["shellthickness_obj"] = shellth_obj + self.mat_geo_sets.append(ccx_elset) + + def get_ccx_elsets_multiple_mat_multiple_shell(self): + for shellth_data in self.member.geos_shellthickness: + shellth_obj = shellth_data["Object"] + for mat_data in self.member.mats_linear: + mat_obj = mat_data["Object"] + shellth_ids = set(shellth_data["FEMElements"]) + mat_ids = set(mat_data["FEMElements"]) + # empty intersection sets possible + elset_data = list(sorted(shellth_ids.intersection(mat_ids))) + if elset_data: + names = [ + {"long": mat_obj.Name, "short": mat_data["ShortName"]}, + {"long": shellth_obj.Name, "short": shellth_data["ShortName"]} + ] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + ccx_elset["shellthickness_obj"] = shellth_obj + self.mat_geo_sets.append(ccx_elset) + + # solid + def get_ccx_elsets_single_mat_solid(self): + print("here2") + mat_obj = self.member.mats_linear[0]["Object"] + elset_data = self.ccx_evolumes + names = [ + {"long": mat_obj.Name, "short": "M0"}, + {"long": "Solid", "short": "Solid"} + ] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + self.mat_geo_sets.append(ccx_elset) + print(self.mat_geo_sets) + + def get_ccx_elsets_multiple_mat_solid(self): + for mat_data in self.member.mats_linear: + mat_obj = mat_data["Object"] + elset_data = mat_data["FEMElements"] + names = [ + {"long": mat_obj.Name, "short": mat_data["ShortName"]}, + {"long": "Solid", "short": "Solid"} + ] + ccx_elset = {} + ccx_elset["ccx_elset"] = elset_data + ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) + ccx_elset["mat_obj_name"] = mat_obj.Name + ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] + self.mat_geo_sets.append(ccx_elset) + + +# ************************************************************************************************ +# Helpers + + +# ccx elset names: +# M .. Material +# B .. Beam +# R .. BeamRotation +# D .. Direction +# F .. Fluid +# S .. Shell, +# TODO write comment into input file to elset ids and elset attributes + + +def get_ccx_elset_name_standard(names): + # standard max length = 80 + ccx_elset_name = "" + for name in names: + ccx_elset_name += name["long"] + if len(ccx_elset_name) < 81: + return ccx_elset_name + else: + ccx_elset_name = "" + for name in names: + ccx_elset_name += name["short"] + if len(ccx_elset_name) < 81: + return ccx_elset_name + else: + error = ( + "FEM: Trouble in ccx input file, because an " + "elset name is longer than 80 character! {}\n" + .format(ccx_elset_name) + ) + raise Exception(error) + + +def get_ccx_elset_name_short(names): + # restricted max length = 20 (beam elsets) + ccx_elset_name = "" + for name in names: + ccx_elset_name += name["short"] + if len(ccx_elset_name) < 21: + return ccx_elset_name + else: + error = ( + "FEM: Trouble in ccx input file, because an" + "beam elset name is longer than 20 characters! {}\n" + .format(ccx_elset_name) + ) + raise Exception(error) + + +def print_obj_info(obj, log=False): + if log is False: + FreeCAD.Console.PrintMessage("{}:\n".format(obj.Label)) + FreeCAD.Console.PrintMessage( + " Type: {}, Name: {}\n".format(type_of_obj(obj), obj.Name) + ) + else: + FreeCAD.Console.PrintLog("{}:\n".format(obj.Label)) + FreeCAD.Console.PrintLog( + " Type: {}, Name: {}\n".format(type_of_obj(obj), obj.Name) + ) + +## @} diff --git a/src/Mod/Fem/femsolver/calculix/writer.py b/src/Mod/Fem/femsolver/calculix/writer.py index b81572d0c9..2dccd59542 100644 --- a/src/Mod/Fem/femsolver/calculix/writer.py +++ b/src/Mod/Fem/femsolver/calculix/writer.py @@ -110,7 +110,8 @@ class FemInputWriterCcx(writerbase.FemInputWriter): solver_obj, mesh_obj, member, - dir_name=None + dir_name=None, + mat_geo_sets=None ): writerbase.FemInputWriter.__init__( self, @@ -118,7 +119,8 @@ class FemInputWriterCcx(writerbase.FemInputWriter): solver_obj, mesh_obj, member, - dir_name + dir_name, + mat_geo_sets ) self.mesh_name = self.mesh_object.Name self.file_name = join(self.dir_name, self.mesh_name + ".inp") @@ -132,7 +134,8 @@ class FemInputWriterCcx(writerbase.FemInputWriter): FreeCAD.Console.PrintMessage("Get mesh sets.\n") time_start = time.process_time() - self.get_mesh_sets() + if not self.ccx_elsets: + self.get_mesh_sets() time_getsets = time.process_time() FreeCAD.Console.PrintMessage( @@ -156,44 +159,44 @@ class FemInputWriterCcx(writerbase.FemInputWriter): inpfile = con_fluidsection.handle_fluidsection_liquid_inlet_outlet(inpfile, self) # element sets constraints - self.write_constraints_meshsets(inpfile, self.centrif_objects, con_centrif) + self.write_constraints_meshsets(inpfile, self.member.cons_centrif, con_centrif) # node sets - self.write_constraints_meshsets(inpfile, self.fixed_objects, con_fixed) - self.write_constraints_meshsets(inpfile, self.displacement_objects, con_displacement) - self.write_constraints_meshsets(inpfile, self.planerotation_objects, con_planerotation) - self.write_constraints_meshsets(inpfile, self.transform_objects, con_transform) - self.write_constraints_meshsets(inpfile, self.temperature_objects, con_temperature) + self.write_constraints_meshsets(inpfile, self.member.cons_fixed, con_fixed) + self.write_constraints_meshsets(inpfile, self.member.cons_displacement, con_displacement) + self.write_constraints_meshsets(inpfile, self.member.cons_planerotation, con_planerotation) + self.write_constraints_meshsets(inpfile, self.member.cons_transform, con_transform) + self.write_constraints_meshsets(inpfile, self.member.cons_temperature, con_temperature) # surface sets - self.write_constraints_meshsets(inpfile, self.contact_objects, con_contact) - self.write_constraints_meshsets(inpfile, self.tie_objects, con_tie) - self.write_constraints_meshsets(inpfile, self.sectionprint_objects, con_sectionprint) + self.write_constraints_meshsets(inpfile, self.member.cons_contact, con_contact) + self.write_constraints_meshsets(inpfile, self.member.cons_tie, con_tie) + self.write_constraints_meshsets(inpfile, self.member.cons_sectionprint, con_sectionprint) # materials and fem element types write_femelement_material.write_femelement_material(inpfile, self) - self.write_constraints_propdata(inpfile, self.initialtemperature_objects, con_initialtemp) + self.write_constraints_propdata(inpfile, self.member.cons_initialtemperature, con_initialtemp) write_femelement_geometry.write_femelement_geometry(inpfile, self) # constraints independent from steps - self.write_constraints_propdata(inpfile, self.planerotation_objects, con_planerotation) - self.write_constraints_propdata(inpfile, self.contact_objects, con_contact) - self.write_constraints_propdata(inpfile, self.tie_objects, con_tie) - self.write_constraints_propdata(inpfile, self.transform_objects, con_transform) + self.write_constraints_propdata(inpfile, self.member.cons_planerotation, con_planerotation) + self.write_constraints_propdata(inpfile, self.member.cons_contact, con_contact) + self.write_constraints_propdata(inpfile, self.member.cons_tie, con_tie) + self.write_constraints_propdata(inpfile, self.member.cons_transform, con_transform) # step equation write_step_equation.write_step_equation(inpfile, self) # constraints dependent from steps - self.write_constraints_propdata(inpfile, self.fixed_objects, con_fixed) - self.write_constraints_propdata(inpfile, self.displacement_objects, con_displacement) - self.write_constraints_propdata(inpfile, self.sectionprint_objects, con_sectionprint) - self.write_constraints_propdata(inpfile, self.selfweight_objects, con_selfweight) - self.write_constraints_propdata(inpfile, self.centrif_objects, con_centrif) - self.write_constraints_meshsets(inpfile, self.force_objects, con_force) - self.write_constraints_meshsets(inpfile, self.pressure_objects, con_pressure) - self.write_constraints_propdata(inpfile, self.temperature_objects, con_temperature) - self.write_constraints_meshsets(inpfile, self.heatflux_objects, con_heatflux) + self.write_constraints_propdata(inpfile, self.member.cons_fixed, con_fixed) + self.write_constraints_propdata(inpfile, self.member.cons_displacement, con_displacement) + self.write_constraints_propdata(inpfile, self.member.cons_sectionprint, con_sectionprint) + self.write_constraints_propdata(inpfile, self.member.cons_selfweight, con_selfweight) + self.write_constraints_propdata(inpfile, self.member.cons_centrif, con_centrif) + self.write_constraints_meshsets(inpfile, self.member.cons_force, con_force) + self.write_constraints_meshsets(inpfile, self.member.cons_pressure, con_pressure) + self.write_constraints_propdata(inpfile, self.member.cons_temperature, con_temperature) + self.write_constraints_meshsets(inpfile, self.member.cons_heatflux, con_heatflux) con_fluidsection.write_constraints_fluidsection(inpfile, self) # output and step end diff --git a/src/Mod/Fem/femsolver/writerbase.py b/src/Mod/Fem/femsolver/writerbase.py index 74e1d7104d..e18f48ed46 100644 --- a/src/Mod/Fem/femsolver/writerbase.py +++ b/src/Mod/Fem/femsolver/writerbase.py @@ -33,8 +33,7 @@ from os.path import join import FreeCAD -from femmesh import meshtools -from femtools.femutils import type_of_obj +from femmesh import meshsetsgetter class FemInputWriter(): @@ -44,14 +43,76 @@ class FemInputWriter(): solver_obj, mesh_obj, member, - dir_name=None + dir_name=None, + mat_geo_sets=None ): # class attributes from parameter values self.analysis = analysis_obj self.solver_obj = solver_obj - self.analysis_type = self.solver_obj.AnalysisType self.mesh_object = mesh_obj + self.member = member + # mor attributes + self.analysis_type = self.solver_obj.AnalysisType self.document = self.analysis.Document + # working dir + self.dir_name = dir_name + # if dir_name was not given or if it exists but isn't empty: create a temporary dir + # Purpose: makes sure the analysis can be run even on wired situation + if not dir_name: + FreeCAD.Console.PrintWarning( + "Error: FemInputWriter has no working_dir --> " + "we are going to make a temporary one!\n" + ) + self.dir_name = self.document.TransientDir.replace( + "\\", "/" + ) + "/FemAnl_" + analysis_obj.Uid[-4:] + if not os.path.isdir(self.dir_name): + os.mkdir(self.dir_name) + + # new class attributes + self.fc_ver = FreeCAD.Version() + self.ccx_nall = "Nall" + self.ccx_eall = "Eall" + self.ccx_evolumes = "Evolumes" + self.ccx_efaces = "Efaces" + self.ccx_eedges = "Eedges" + if mat_geo_sets is not None: + self.ccx_elsets = mat_geo_sets + else: + self.ccx_elsets = [] + if self.mesh_object: + if hasattr(self.mesh_object, "Shape"): + self.theshape = self.mesh_object.Shape + elif hasattr(self.mesh_object, "Part"): + self.theshape = self.mesh_object.Part + else: + FreeCAD.Console.PrintWarning( + "A finite mesh without a link to a Shape was given. " + "Happen on pure mesh objects. " + "Not all methods do work without this link.\n" + ) + # ATM only used in meshtools.get_femelement_direction1D_set + # TODO somehow this is not smart, rare meshes might be used often + self.femmesh = self.mesh_object.FemMesh + else: + FreeCAD.Console.PrintWarning( + "No finite element mesh object was given to the writer class. " + "In rare cases this might not be an error. " + ) + + # deprecated, leave for compatibility reasons + # if these are calculated here they are calculated twice :-( + self.femnodes_mesh = {} + self.femelement_table = {} + self.constraint_conflict_nodes = [] + self.femnodes_ele_table = {} + self.femelements_edges_only = [] + self.femelements_faces_only = [] + self.femelement_volumes_table = {} + self.femelement_faces_table = {} + self.femelement_edges_table = {} + self.femelement_count_test = True + # materials self.material_objects = member.mats_linear self.material_nonlinear_objects = member.mats_nonlinear @@ -75,58 +136,14 @@ class FemInputWriter(): self.temperature_objects = member.cons_temperature self.tie_objects = member.cons_tie self.transform_objects = member.cons_transform - # working dir - self.dir_name = dir_name - # if dir_name was not given or if it exists but isn't empty: create a temporary dir - # Purpose: makes sure the analysis can be run even on wired situation - if not dir_name: - FreeCAD.Console.PrintWarning( - "Error: FemInputWriter has no working_dir --> " - "we are going to make a temporary one!\n" - ) - self.dir_name = self.document.TransientDir.replace( - "\\", "/" - ) + "/FemAnl_" + analysis_obj.Uid[-4:] - if not os.path.isdir(self.dir_name): - os.mkdir(self.dir_name) - # new class attributes - self.fc_ver = FreeCAD.Version() - self.ccx_nall = "Nall" - self.ccx_eall = "Eall" - self.ccx_evolumes = "Evolumes" - self.ccx_efaces = "Efaces" - self.ccx_eedges = "Eedges" - self.ccx_elsets = [] - if self.mesh_object: - if hasattr(self.mesh_object, "Shape"): - self.theshape = self.mesh_object.Shape - elif hasattr(self.mesh_object, "Part"): - self.theshape = self.mesh_object.Part - else: - FreeCAD.Console.PrintWarning( - "A finite mesh without a link to a Shape was given. " - "Happen on pure mesh objects. " - "Not all methods do work without this link.\n" - ) - # ATM only used in meshtools.get_femelement_direction1D_set - # TODO somehow this is not smart, rare meshes might be used often - self.femmesh = self.mesh_object.FemMesh - else: - FreeCAD.Console.PrintWarning( - "No finite element mesh object was given to the writer class. " - "In rare cases this might not be an error. " - ) - self.femnodes_mesh = {} - self.femelement_table = {} - self.constraint_conflict_nodes = [] - self.femnodes_ele_table = {} - self.femelements_edges_only = [] - self.femelements_faces_only = [] - self.femelement_volumes_table = {} - self.femelement_faces_table = {} - self.femelement_edges_table = {} - self.femelement_count_test = True + # meshdatagetter, for compatibility, same with all getter methods + self.meshdatagetter = meshsetsgetter.MeshSetsGetter( + self.analysis, + self.solver_obj, + self.mesh_object, + self.member, + ) # ******************************************************************************************** # ******************************************************************************************** @@ -213,7 +230,6 @@ class FemInputWriter(): # - done in return value of meshtools.get_femnodes_by_femobj_with_references # TODO FIXME might be appropriate for element sets and surfaceface sets too - # ******************************************************************************************** # ******************************************************************************************** # get all known sets def get_mesh_sets(self): @@ -246,907 +262,49 @@ class FemInputWriter(): self.get_constraints_heatflux_faces() # ******************************************************************************************** - # ******************************************************************************************** - # node sets + # sets def get_constraints_fixed_nodes(self): - if not self.fixed_objects: - return - # get nodes - for femobj in self.fixed_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - print_obj_info(femobj["Object"]) - femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( - self.femmesh, - femobj - ) - # add nodes to constraint_conflict_nodes, needed by constraint plane rotation - for node in femobj["Nodes"]: - self.constraint_conflict_nodes.append(node) - # if mixed mesh with solids the node set needs to be split - # because solid nodes do not have rotational degree of freedom - if self.femmesh.Volumes \ - and (len(self.shellthickness_objects) > 0 or len(self.beamsection_objects) > 0): - FreeCAD.Console.PrintMessage("We need to find the solid nodes.\n") - if not self.femelement_volumes_table: - self.femelement_volumes_table = meshtools.get_femelement_volumes_table( - self.femmesh - ) - for femobj in self.fixed_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - nds_solid = [] - nds_faceedge = [] - for n in femobj["Nodes"]: - solid_node = False - for ve in self.femelement_volumes_table: - if n in self.femelement_volumes_table[ve]: - solid_node = True - nds_solid.append(n) - break - if not solid_node: - nds_faceedge.append(n) - femobj["NodesSolid"] = set(nds_solid) - femobj["NodesFaceEdge"] = set(nds_faceedge) + self.meshdatagetter.get_constraints_fixed_nodes() def get_constraints_displacement_nodes(self): - if not self.displacement_objects: - return - # get nodes - for femobj in self.displacement_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - print_obj_info(femobj["Object"]) - femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( - self.femmesh, - femobj - ) - # add nodes to constraint_conflict_nodes, needed by constraint plane rotation - for node in femobj["Nodes"]: - self.constraint_conflict_nodes.append(node) + self.meshdatagetter.get_constraints_displacement_nodes() def get_constraints_planerotation_nodes(self): - if not self.planerotation_objects: - return - # get nodes - for femobj in self.planerotation_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - print_obj_info(femobj["Object"]) - femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( - self.femmesh, - femobj - ) + self.meshdatagetter.get_constraints_planerotation_nodes() def get_constraints_transform_nodes(self): - if not self.transform_objects: - return - # get nodes - for femobj in self.transform_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - print_obj_info(femobj["Object"]) - femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( - self.femmesh, - femobj - ) + self.meshdatagetter.get_constraints_transform_nodes() def get_constraints_temperature_nodes(self): - if not self.temperature_objects: - return - # get nodes - for femobj in self.temperature_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - print_obj_info(femobj["Object"]) - femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( - self.femmesh, - femobj - ) + self.meshdatagetter.get_constraints_temperature_nodes() def get_constraints_fluidsection_nodes(self): - if not self.fluidsection_objects: - return - # get nodes - for femobj in self.fluidsection_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - print_obj_info(femobj["Object"]) - femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( - self.femmesh, - femobj - ) + self.meshdatagetter.get_constraints_fluidsection_nodes() def get_constraints_force_nodeloads(self): - if not self.force_objects: - return - # check shape type of reference shape - for femobj in self.force_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - print_obj_info(femobj["Object"], log=True) - if femobj["RefShapeType"] == "Vertex": - FreeCAD.Console.PrintLog( - " load on vertices --> The femelement_table " - "and femnodes_mesh are not needed for node load calculation.\n" - ) - elif femobj["RefShapeType"] == "Face" \ - and meshtools.is_solid_femmesh(self.femmesh) \ - and not meshtools.has_no_face_data(self.femmesh): - FreeCAD.Console.PrintLog( - " solid_mesh with face data --> The femelement_table is not " - "needed but the femnodes_mesh is needed for node load calculation.\n" - ) - if not self.femnodes_mesh: - self.femnodes_mesh = self.femmesh.Nodes - else: - FreeCAD.Console.PrintLog( - " mesh without needed data --> The femelement_table " - "and femnodes_mesh are not needed for node load calculation.\n" - ) - if not self.femnodes_mesh: - self.femnodes_mesh = self.femmesh.Nodes - if not self.femelement_table: - self.femelement_table = meshtools.get_femelement_table( - self.femmesh - ) - # get node loads - FreeCAD.Console.PrintLog( - " Finite element mesh nodes will be retrieved by searching " - "the appropriate nodes in the finite element mesh.\n" - ) - FreeCAD.Console.PrintLog( - " The appropriate finite element mesh node load values will " - "be calculated according to the finite element definition.\n" - ) - for femobj in self.force_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - frc_obj = femobj["Object"] - print_obj_info(frc_obj) - if frc_obj.Force == 0: - FreeCAD.Console.PrintMessage(" Warning --> Force = 0\n") - if femobj["RefShapeType"] == "Vertex": # point load on vertices - femobj["NodeLoadTable"] = meshtools.get_force_obj_vertex_nodeload_table( - self.femmesh, - frc_obj - ) - elif femobj["RefShapeType"] == "Edge": # line load on edges - femobj["NodeLoadTable"] = meshtools.get_force_obj_edge_nodeload_table( - self.femmesh, - self.femelement_table, - self.femnodes_mesh, frc_obj - ) - elif femobj["RefShapeType"] == "Face": # area load on faces - femobj["NodeLoadTable"] = meshtools.get_force_obj_face_nodeload_table( - self.femmesh, - self.femelement_table, - self.femnodes_mesh, frc_obj - ) + self.meshdatagetter.get_constraints_force_nodeloads() - # ******************************************************************************************** - # ******************************************************************************************** - # faces sets def get_constraints_pressure_faces(self): - if not self.pressure_objects: - return - # TODO see comments in get_constraints_force_nodeloads() - # it applies here too. Mhh it applies to all constraints ... - - """ - # deprecated version - # get the faces and face numbers - for femobj in self.pressure_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - femobj["PressureFaces"] = meshtools.get_pressure_obj_faces_depreciated( - self.femmesh, - femobj - ) - # print(femobj["PressureFaces"]) - """ - - if not self.femnodes_mesh: - self.femnodes_mesh = self.femmesh.Nodes - if not self.femelement_table: - self.femelement_table = meshtools.get_femelement_table(self.femmesh) - if not self.femnodes_ele_table: - self.femnodes_ele_table = meshtools.get_femnodes_ele_table( - self.femnodes_mesh, - self.femelement_table - ) - - for femobj in self.pressure_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - print_obj_info(femobj["Object"]) - pressure_faces = meshtools.get_pressure_obj_faces( - self.femmesh, - self.femelement_table, - self.femnodes_ele_table, femobj - ) - # the data model is for compatibility reason with deprecated version - # get_pressure_obj_faces_depreciated returns the face ids in a tuple per ref_shape - # some_string was the reference_shape_element_string in deprecated method - # [(some_string, [ele_id, ele_face_id], [ele_id, ele_face_id], ...])] - some_string = "{}: face load".format(femobj["Object"].Name) - femobj["PressureFaces"] = [(some_string, pressure_faces)] - FreeCAD.Console.PrintLog("{}\n".format(femobj["PressureFaces"])) + self.meshdatagetter.get_constraints_pressure_faces() def get_constraints_contact_faces(self): - if not self.contact_objects: - return - if not self.femnodes_mesh: - self.femnodes_mesh = self.femmesh.Nodes - if not self.femelement_table: - self.femelement_table = meshtools.get_femelement_table(self.femmesh) - if not self.femnodes_ele_table: - self.femnodes_ele_table = meshtools.get_femnodes_ele_table( - self.femnodes_mesh, - self.femelement_table - ) - - for femobj in self.contact_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - print_obj_info(femobj["Object"]) - contact_slave_faces, contact_master_faces = meshtools.get_contact_obj_faces( - self.femmesh, - self.femelement_table, - self.femnodes_ele_table, femobj - ) - # [ele_id, ele_face_id], [ele_id, ele_face_id], ...] - # whereas the ele_face_id might be ccx specific - femobj["ContactSlaveFaces"] = contact_slave_faces - femobj["ContactMasterFaces"] = contact_master_faces - # FreeCAD.Console.PrintLog("{}\n".format(femobj["ContactSlaveFaces"])) - # FreeCAD.Console.PrintLog("{}\n".format(femobj["ContactMasterFaces"])) - - # information in the regard of element faces constraints - # forum post: https://forum.freecadweb.org/viewtopic.php?f=18&t=42783&p=370286#p366723 - # contact: master and slave could be the same face: rubber of a damper - # tie: master and slave have to be separate faces AFA UR_ K - # section print: only the element faces of solid elements - # from one side of the geometric face are needed + self.meshdatagetter.get_constraints_contact_faces() def get_constraints_tie_faces(self): - if not self.tie_objects: - return - if not self.femnodes_mesh: - self.femnodes_mesh = self.femmesh.Nodes - if not self.femelement_table: - self.femelement_table = meshtools.get_femelement_table(self.femmesh) - if not self.femnodes_ele_table: - self.femnodes_ele_table = meshtools.get_femnodes_ele_table( - self.femnodes_mesh, - self.femelement_table - ) - - for femobj in self.tie_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - print_obj_info(femobj["Object"]) - slave_faces, master_faces = meshtools.get_tie_obj_faces( - self.femmesh, - self.femelement_table, - self.femnodes_ele_table, femobj - ) - # [ele_id, ele_face_id], [ele_id, ele_face_id], ...] - # whereas the ele_face_id might be ccx specific - femobj["TieSlaveFaces"] = slave_faces - femobj["TieMasterFaces"] = master_faces - # FreeCAD.Console.PrintLog("{}\n".format(femobj["ContactSlaveFaces"])) - # FreeCAD.Console.PrintLog("{}\n".format(femobj["ContactMasterFaces"])) + self.meshdatagetter.get_constraints_tie_faces() def get_constraints_sectionprint_faces(self): - if not self.sectionprint_objects: - return - # TODO: use meshtools to get the surfaces - # see constraint contact or constraint tie - for femobj in self.sectionprint_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - sectionprint_obj = femobj["Object"] - if len(sectionprint_obj.References) > 1: - FreeCAD.Console.PrintError( - "Only one reference shape allowed for a section print " - "but {} found: {}\n" - .format(len(sectionprint_obj.References), sectionprint_obj.References) - ) - for o, elem_tup in sectionprint_obj.References: - for elem in elem_tup: - # there should only be one reference for each section print object - # in the gui this is checked - ref_shape = o.Shape.getElement(elem) - if ref_shape.ShapeType == "Face": - v = self.mesh_object.FemMesh.getccxVolumesByFace(ref_shape) - if len(v) > 0: - femobj["SectionPrintFaces"] = v - # volume elements found - FreeCAD.Console.PrintLog( - "{}, surface {}, {} touching volume elements found\n" - .format(sectionprint_obj.Label, sectionprint_obj.Name, len(v)) - ) - else: - # no volume elements found, shell elements not allowed - FreeCAD.Console.PrintError( - "{}, surface {}, Error: " - "No volume elements found!\n" - .format(sectionprint_obj.Label, sectionprint_obj.Name) - ) - else: - # in Gui only Faces can be added - FreeCAD.Console.PrintError( - "Wrong reference shape type for {} " - "Only Faces are allowed, but a {} was found.\n" - .format(sectionprint_obj.Name, ref_shape.ShapeType) - ) + self.meshdatagetter.get_constraints_sectionprint_faces() def get_constraints_heatflux_faces(self): - if not self.heatflux_objects: - return - # TODO: use meshtools to get the surfaces (or move to mesh tools) - # see constraint contact or constraint tie and constraint force - # heatflux_obj_face_table: see force_obj_node_load_table - # [ - # ("refshape_name:elemname", face_table), - # ..., - # ("refshape_name:elemname", face_table) - # ] - for femobj in self.heatflux_objects: - # femobj --> dict, FreeCAD document object is femobj["Object"] - heatflux_obj = femobj["Object"] - femobj["HeatFluxFaceTable"] = [] - for o, elem_tup in heatflux_obj.References: - for elem in elem_tup: - ho = o.Shape.getElement(elem) - if ho.ShapeType == "Face": - elem_info = "{}:{}".format(o.Name, elem) - face_table = self.mesh_object.FemMesh.getccxVolumesByFace(ho) - femobj["HeatFluxFaceTable"].append((elem_info, face_table)) + self.meshdatagetter.get_constraints_heatflux_faces() - # ******************************************************************************************** - # ******************************************************************************************** - # element sets constraints def get_constraints_centrif_elements(self): - # get element ids and write them into the femobj - if not self.centrif_objects: - return - if len(self.centrif_objects) == 1 and not self.centrif_objects[0]["Object"].References: - self.centrif_objects[0]["FEMElements"] = self.ccx_evolumes - else: - self.get_solid_element_sets(self.centrif_objects) - - # ******************************************************************************************** - # ******************************************************************************************** - # element sets material and element geometry - def get_solid_element_sets(self, femobjs): - # get element ids and write them into the femobj - all_found = False - if self.femmesh.GroupCount: - all_found = meshtools.get_femelement_sets_from_group_data( - self.femmesh, - femobjs - ) - FreeCAD.Console.PrintMessage(all_found) - FreeCAD.Console.PrintMessage("\n") - if all_found is False: - if not self.femelement_table: - self.femelement_table = meshtools.get_femelement_table(self.femmesh) - # we're going to use the binary search for get_femelements_by_femnodes() - # thus we need the parameter values self.femnodes_ele_table - if not self.femnodes_mesh: - self.femnodes_mesh = self.femmesh.Nodes - if not self.femnodes_ele_table: - self.femnodes_ele_table = meshtools.get_femnodes_ele_table( - self.femnodes_mesh, - self.femelement_table - ) - control = meshtools.get_femelement_sets( - self.femmesh, - self.femelement_table, - femobjs, - self.femnodes_ele_table - ) - # we only need to set it, if it is still True - if (self.femelement_count_test is True) and (control is False): - self.femelement_count_test = False - - def get_element_geometry2D_elements(self): - # get element ids and write them into the objects - FreeCAD.Console.PrintMessage("Shell thicknesses\n") - if not self.femelement_faces_table: - self.femelement_faces_table = meshtools.get_femelement_faces_table( - self.femmesh - ) - meshtools.get_femelement_sets( - self.femmesh, - self.femelement_faces_table, - self.shellthickness_objects - ) - - def get_element_geometry1D_elements(self): - # get element ids and write them into the objects - FreeCAD.Console.PrintMessage("Beam sections\n") - if not self.femelement_edges_table: - self.femelement_edges_table = meshtools.get_femelement_edges_table( - self.femmesh - ) - meshtools.get_femelement_sets( - self.femmesh, - self.femelement_edges_table, - self.beamsection_objects - ) - - def get_element_rotation1D_elements(self): - # get for each geometry edge direction the element ids and rotation norma - FreeCAD.Console.PrintMessage("Beam rotations\n") - if not self.femelement_edges_table: - self.femelement_edges_table = meshtools.get_femelement_edges_table( - self.femmesh - ) - meshtools.get_femelement_direction1D_set( - self.femmesh, - self.femelement_edges_table, - self.beamrotation_objects, - self.theshape - ) - - def get_element_fluid1D_elements(self): - # get element ids and write them into the objects - FreeCAD.Console.PrintMessage("Fluid sections\n") - if not self.femelement_edges_table: - self.femelement_edges_table = meshtools.get_femelement_edges_table( - self.femmesh - ) - meshtools.get_femelement_sets( - self.femmesh, - self.femelement_edges_table, - self.fluidsection_objects - ) - - def get_material_elements(self): - # it only works if either Volumes or Shellthicknesses or Beamsections - # are in the material objects, it means it does not work - # for mixed meshes and multiple materials, this is checked in check_prerequisites - # the femelement_table is only calculated for - # the highest dimension in get_femelement_table - FreeCAD.Console.PrintMessage("Materials\n") - if self.femmesh.Volumes: - # we only could do this for volumes - # if a mesh contains volumes we're going to use them in the analysis - # but a mesh could contain - # the element faces of the volumes as faces - # and the edges of the faces as edges - # there we have to check of some geometric objects - # get element ids and write them into the femobj - self.get_solid_element_sets(self.material_objects) - if self.shellthickness_objects: - if not self.femelement_faces_table: - self.femelement_faces_table = meshtools.get_femelement_faces_table( - self.femmesh - ) - meshtools.get_femelement_sets( - self.femmesh, - self.femelement_faces_table, - self.material_objects - ) - if self.beamsection_objects or self.fluidsection_objects: - if not self.femelement_edges_table: - self.femelement_edges_table = meshtools.get_femelement_edges_table( - self.femmesh - ) - meshtools.get_femelement_sets( - self.femmesh, - self.femelement_edges_table, - self.material_objects - ) + self.meshdatagetter.get_constraints_centrif_elements() def get_element_sets_material_and_femelement_geometry(self): - if not self.material_objects: - return - # in any case if we have beams, we're going to need the element ids for the rotation elsets - if self.beamsection_objects: - # we will need to split the beam even for one beamobj - # because no beam in z-direction can be used in ccx without a special adjustment - # thus they need an own ccx_elset - self.get_element_rotation1D_elements() + self.meshdatagetter.get_element_sets_material_and_femelement_geometry() + self.ccx_elsets = self.meshdatagetter.mat_geo_sets - # get the element ids for face and edge elements and write them into the objects - if len(self.shellthickness_objects) > 1: - self.get_element_geometry2D_elements() - if len(self.beamsection_objects) > 1: - self.get_element_geometry1D_elements() - if len(self.fluidsection_objects) > 1: - self.get_element_fluid1D_elements() - - # get the element ids for material objects and write them into the material object - if len(self.material_objects) > 1: - self.get_material_elements() - - # create the ccx_elsets - if len(self.material_objects) == 1: - if self.femmesh.Volumes: - # we only could do this for volumes, if a mesh contains volumes - # we're going to use them in the analysis - # but a mesh could contain the element faces of the volumes as faces - # and the edges of the faces as edges - # there we have to check for some geometric objects - self.get_ccx_elsets_single_mat_solid() - if len(self.shellthickness_objects) == 1: - self.get_ccx_elsets_single_mat_single_shell() - elif len(self.shellthickness_objects) > 1: - self.get_ccx_elsets_single_mat_multiple_shell() - if len(self.beamsection_objects) == 1: - self.get_ccx_elsets_single_mat_single_beam() - elif len(self.beamsection_objects) > 1: - self.get_ccx_elsets_single_mat_multiple_beam() - if len(self.fluidsection_objects) == 1: - self.get_ccx_elsets_single_mat_single_fluid() - elif len(self.fluidsection_objects) > 1: - self.get_ccx_elsets_single_mat_multiple_fluid() - elif len(self.material_objects) > 1: - if self.femmesh.Volumes: - # we only could do this for volumes, if a mseh contains volumes - # we're going to use them in the analysis - # but a mesh could contain the element faces of the volumes as faces - # and the edges of the faces as edges - # there we have to check for some geometric objects - # volume is a bit special - # because retrieving ids from group mesh data is implemented - self.get_ccx_elsets_multiple_mat_solid() - if len(self.shellthickness_objects) == 1: - self.get_ccx_elsets_multiple_mat_single_shell() - elif len(self.shellthickness_objects) > 1: - self.get_ccx_elsets_multiple_mat_multiple_shell() - if len(self.beamsection_objects) == 1: - self.get_ccx_elsets_multiple_mat_single_beam() - elif len(self.beamsection_objects) > 1: - self.get_ccx_elsets_multiple_mat_multiple_beam() - if len(self.fluidsection_objects) == 1: - self.get_ccx_elsets_multiple_mat_single_fluid() - elif len(self.fluidsection_objects) > 1: - self.get_ccx_elsets_multiple_mat_multiple_fluid() - - # self.ccx_elsets = [ { - # "ccx_elset" : [e1, e2, e3, ... , en] or elements set name strings - # "ccx_elset_name" : "ccx_identifier_elset" - # "mat_obj_name" : "mat_obj.Name" - # "ccx_mat_name" : "mat_obj.Material["Name"]" !!! not unique !!! - # "beamsection_obj" : "beamsection_obj" if exists - # "fluidsection_obj" : "fluidsection_obj" if exists - # "shellthickness_obj" : shellthickness_obj" if exists - # "beam_normal" : normal vector for beams only - # }, - # {}, ... , {} ] - - # beam - # TODO support multiple beamrotations - # we do not need any more any data from the rotation document object, - # thus we do not need to save the rotation document object name in the else - def get_ccx_elsets_single_mat_single_beam(self): - mat_obj = self.material_objects[0]["Object"] - beamsec_obj = self.beamsection_objects[0]["Object"] - beamrot_data = self.beamrotation_objects[0] - for i, beamdirection in enumerate(beamrot_data["FEMRotations1D"]): - # ID's for this direction - elset_data = beamdirection["ids"] - names = [ - {"short": "M0"}, - {"short": "B0"}, - {"short": beamrot_data["ShortName"]}, - {"short": "D" + str(i)} - ] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["beamsection_obj"] = beamsec_obj - # normal for this direction - ccx_elset["beam_normal"] = beamdirection["normal"] - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_single_mat_multiple_beam(self): - mat_obj = self.material_objects[0]["Object"] - beamrot_data = self.beamrotation_objects[0] - for beamsec_data in self.beamsection_objects: - beamsec_obj = beamsec_data["Object"] - beamsec_ids = set(beamsec_data["FEMElements"]) - for i, beamdirection in enumerate(beamrot_data["FEMRotations1D"]): - beamdir_ids = set(beamdirection["ids"]) - # empty intersection sets possible - elset_data = list(sorted(beamsec_ids.intersection(beamdir_ids))) - if elset_data: - names = [ - {"short": "M0"}, - {"short": beamsec_data["ShortName"]}, - {"short": beamrot_data["ShortName"]}, - {"short": "D" + str(i)} - ] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["beamsection_obj"] = beamsec_obj - # normal for this direction - ccx_elset["beam_normal"] = beamdirection["normal"] - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_multiple_mat_single_beam(self): - beamsec_obj = self.beamsection_objects[0]["Object"] - beamrot_data = self.beamrotation_objects[0] - for mat_data in self.material_objects: - mat_obj = mat_data["Object"] - mat_ids = set(mat_data["FEMElements"]) - for i, beamdirection in enumerate(beamrot_data["FEMRotations1D"]): - beamdir_ids = set(beamdirection["ids"]) - elset_data = list(sorted(mat_ids.intersection(beamdir_ids))) - if elset_data: - names = [ - {"short": mat_data["ShortName"]}, - {"short": "B0"}, - {"short": beamrot_data["ShortName"]}, - {"short": "D" + str(i)} - ] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["beamsection_obj"] = beamsec_obj - # normal for this direction - ccx_elset["beam_normal"] = beamdirection["normal"] - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_multiple_mat_multiple_beam(self): - beamrot_data = self.beamrotation_objects[0] - for beamsec_data in self.beamsection_objects: - beamsec_obj = beamsec_data["Object"] - beamsec_ids = set(beamsec_data["FEMElements"]) - for mat_data in self.material_objects: - mat_obj = mat_data["Object"] - mat_ids = set(mat_data["FEMElements"]) - for i, beamdirection in enumerate(beamrot_data["FEMRotations1D"]): - beamdir_ids = set(beamdirection["ids"]) - # empty intersection sets possible - elset_data = list(sorted( - beamsec_ids.intersection(mat_ids).intersection(beamdir_ids) - )) - if elset_data: - names = [ - {"short": mat_data["ShortName"]}, - {"short": beamsec_data["ShortName"]}, - {"short": beamrot_data["ShortName"]}, - {"short": "D" + str(i)} - ] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["beamsection_obj"] = beamsec_obj - # normal for this direction - ccx_elset["beam_normal"] = beamdirection["normal"] - self.ccx_elsets.append(ccx_elset) - - # fluid - def get_ccx_elsets_single_mat_single_fluid(self): - mat_obj = self.material_objects[0]["Object"] - fluidsec_obj = self.fluidsection_objects[0]["Object"] - elset_data = self.ccx_eedges - names = [{"short": "M0"}, {"short": "F0"}] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["fluidsection_obj"] = fluidsec_obj - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_single_mat_multiple_fluid(self): - mat_obj = self.material_objects[0]["Object"] - for fluidsec_data in self.fluidsection_objects: - fluidsec_obj = fluidsec_data["Object"] - elset_data = fluidsec_data["FEMElements"] - names = [{"short": "M0"}, {"short": fluidsec_data["ShortName"]}] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["fluidsection_obj"] = fluidsec_obj - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_multiple_mat_single_fluid(self): - fluidsec_obj = self.fluidsection_objects[0]["Object"] - for mat_data in self.material_objects: - mat_obj = mat_data["Object"] - elset_data = mat_data["FEMElements"] - names = [{"short": mat_data["ShortName"]}, {"short": "F0"}] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["fluidsection_obj"] = fluidsec_obj - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_multiple_mat_multiple_fluid(self): - for fluidsec_data in self.fluidsection_objects: - fluidsec_obj = fluidsec_data["Object"] - for mat_data in self.material_objects: - mat_obj = mat_data["Object"] - fluidsec_ids = set(fluidsec_data["FEMElements"]) - mat_ids = set(mat_data["FEMElements"]) - # empty intersection sets possible - elset_data = list(sorted(fluidsec_ids.intersection(mat_ids))) - if elset_data: - names = [ - {"short": mat_data["ShortName"]}, - {"short": fluidsec_data["ShortName"]} - ] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_short(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["fluidsection_obj"] = fluidsec_obj - self.ccx_elsets.append(ccx_elset) - - # shell - def get_ccx_elsets_single_mat_single_shell(self): - mat_obj = self.material_objects[0]["Object"] - shellth_obj = self.shellthickness_objects[0]["Object"] - elset_data = self.ccx_efaces - names = [ - {"long": mat_obj.Name, "short": "M0"}, - {"long": shellth_obj.Name, "short": "S0"} - ] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["shellthickness_obj"] = shellth_obj - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_single_mat_multiple_shell(self): - mat_obj = self.material_objects[0]["Object"] - for shellth_data in self.shellthickness_objects: - shellth_obj = shellth_data["Object"] - elset_data = shellth_data["FEMElements"] - names = [ - {"long": mat_obj.Name, "short": "M0"}, - {"long": shellth_obj.Name, "short": shellth_data["ShortName"]} - ] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["shellthickness_obj"] = shellth_obj - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_multiple_mat_single_shell(self): - shellth_obj = self.shellthickness_objects[0]["Object"] - for mat_data in self.material_objects: - mat_obj = mat_data["Object"] - elset_data = mat_data["FEMElements"] - names = [ - {"long": mat_obj.Name, "short": mat_data["ShortName"]}, - {"long": shellth_obj.Name, "short": "S0"} - ] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["shellthickness_obj"] = shellth_obj - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_multiple_mat_multiple_shell(self): - for shellth_data in self.shellthickness_objects: - shellth_obj = shellth_data["Object"] - for mat_data in self.material_objects: - mat_obj = mat_data["Object"] - shellth_ids = set(shellth_data["FEMElements"]) - mat_ids = set(mat_data["FEMElements"]) - # empty intersection sets possible - elset_data = list(sorted(shellth_ids.intersection(mat_ids))) - if elset_data: - names = [ - {"long": mat_obj.Name, "short": mat_data["ShortName"]}, - {"long": shellth_obj.Name, "short": shellth_data["ShortName"]} - ] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - ccx_elset["shellthickness_obj"] = shellth_obj - self.ccx_elsets.append(ccx_elset) - - # solid - def get_ccx_elsets_single_mat_solid(self): - mat_obj = self.material_objects[0]["Object"] - elset_data = self.ccx_evolumes - names = [ - {"long": mat_obj.Name, "short": "M0"}, - {"long": "Solid", "short": "Solid"} - ] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_multiple_mat_solid(self): - for mat_data in self.material_objects: - mat_obj = mat_data["Object"] - elset_data = mat_data["FEMElements"] - names = [ - {"long": mat_obj.Name, "short": mat_data["ShortName"]}, - {"long": "Solid", "short": "Solid"} - ] - ccx_elset = {} - ccx_elset["ccx_elset"] = elset_data - ccx_elset["ccx_elset_name"] = get_ccx_elset_name_standard(names) - ccx_elset["mat_obj_name"] = mat_obj.Name - ccx_elset["ccx_mat_name"] = mat_obj.Material["Name"] - self.ccx_elsets.append(ccx_elset) - - -# ************************************************************************************************ -# Helpers - - -# ccx elset names: -# M .. Material -# B .. Beam -# R .. BeamRotation -# D .. Direction -# F .. Fluid -# S .. Shell, -# TODO write comment into input file to elset ids and elset attributes - - -def get_ccx_elset_name_standard(names): - # standard max length = 80 - ccx_elset_name = "" - for name in names: - ccx_elset_name += name["long"] - if len(ccx_elset_name) < 81: - return ccx_elset_name - else: - ccx_elset_name = "" - for name in names: - ccx_elset_name += name["short"] - if len(ccx_elset_name) < 81: - return ccx_elset_name - else: - error = ( - "FEM: Trouble in ccx input file, because an " - "elset name is longer than 80 character! {}\n" - .format(ccx_elset_name) - ) - raise Exception(error) - - -def get_ccx_elset_name_short(names): - # restricted max length = 20 (beam elsets) - ccx_elset_name = "" - for name in names: - ccx_elset_name += name["short"] - if len(ccx_elset_name) < 21: - return ccx_elset_name - else: - error = ( - "FEM: Trouble in ccx input file, because an" - "beam elset name is longer than 20 characters! {}\n" - .format(ccx_elset_name) - ) - raise Exception(error) - - -def print_obj_info(obj, log=False): - if log is False: - FreeCAD.Console.PrintMessage("{}:\n".format(obj.Label)) - FreeCAD.Console.PrintMessage( - " Type: {}, Name: {}\n".format(type_of_obj(obj), obj.Name) - ) - else: - FreeCAD.Console.PrintLog("{}:\n".format(obj.Label)) - FreeCAD.Console.PrintLog( - " Type: {}, Name: {}\n".format(type_of_obj(obj), obj.Name) - ) ## @}