def get_refshape_type(fem_doc_object): """ Return shape type the constraints references. Determine single shape type of references of *fem_doc_object* which must be a constraint (=have a *References* property). All references must be of the same type which is than returned as a string. A type can be "Vertex", "Edge", "Face" or "Solid". :param fem_doc_object: A constraint object with a *References* property. :returns: A string representing the shape type ("Vertex", "Edge", "Face" or "Solid"). If *fem_doc_object* isn't a constraint ``""`` is returned. :note: Undefined behaviour if the type of the references of one object are not all the same. :note: Undefined behaviour if constraint contains no references (empty list). """ from femtools.geomtools import get_element if hasattr(fem_doc_object, "References") and fem_doc_object.References: first_ref_obj = fem_doc_object.References[0] first_ref_shape = get_element(first_ref_obj[0], first_ref_obj[1][0]) st = first_ref_shape.ShapeType FreeCAD.Console.PrintMessage("References: {} in {}, {}\n".format( st, fem_doc_object.Name, fem_doc_object.Label)) return st else: FreeCAD.Console.PrintMessage("References: empty in {}, {}\n".format( fem_doc_object.Name, fem_doc_object.Label)) return ""
def select_clicked_reference_shape(self): self.setback_listobj_visibility() if self.sel_server: FreeCADGui.Selection.removeObserver(self.sel_server) self.sel_server = None if not self.sel_server: if not self.references: return currentItemName = str(self.list_References.currentItem().text()) for ref in self.references: if self.get_item_text(ref) == currentItemName: # print("found: shape: " + ref[0].Name + " element: " + ref[1]) if not ref[0].ViewObject.Visibility: self.obj_notvisible.append(ref[0]) ref[0].ViewObject.Visibility = True FreeCADGui.Selection.clearSelection() ref_sh_type = ref[0].Shape.ShapeType if ref[1].startswith("Solid") and ( ref_sh_type == "Compound" or ref_sh_type == "CompSolid"): # selection of Solids of Compounds or CompSolids is not possible # because a Solid is no Subelement # since only Subelements can be selected # we're going to select all Faces of said Solids # the method getElement(element)doesn't return Solid elements solid = geomtools.get_element(ref[0], ref[1]) if not solid: return faces = [] for fs in solid.Faces: # find these faces in ref[0] for i, fref in enumerate(ref[0].Shape.Faces): if fs.isSame(fref): fref_elstring = "Face" + str(i + 1) if fref_elstring not in faces: faces.append(fref_elstring) for f in faces: FreeCADGui.Selection.addSelection(ref[0], f) else: # Selection of all other element types is supported FreeCADGui.Selection.addSelection(ref[0], ref[1])
def has_equal_references_shape_types(self, ref_shty=""): for ref in self.references: # the method getElement(element) does not return Solid elements r = geomtools.get_element(ref[0], ref[1]) if not r: FreeCAD.Console.PrintError( "Problem in retrieving element: {} \n".format(ref[1])) continue FreeCAD.Console.PrintLog( " ReferenceShape : {}, {}, {} --> {}\n".format( r.ShapeType, ref[0].Name, ref[0].Label, ref[1])) if not ref_shty: ref_shty = r.ShapeType if r.ShapeType != ref_shty: message = "Multiple shape types are not allowed in the reference list.\n" FreeCAD.Console.PrintMessage(message) QtGui.QMessageBox.critical(None, "Multiple ShapeTypes not allowed", message) return False return True
def get_boundary_layer_data(self): # mesh boundary layer # currently only one boundary layer setting object is allowed # but multiple boundary can be selected # Mesh.CharacteristicLengthMin, must be zero # or a value less than first inflation layer height if not self.mesh_obj.MeshBoundaryLayerList: # print(" No mesh boundary layer setting document object.") pass else: Console.PrintMessage(" Mesh boundary layers, we need to get the elements.\n") if self.part_obj.Shape.ShapeType == "Compound": # see http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&start=40#p149467 and # http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&p=149520#p149520 err = ( "Gmsh could return unexpected meshes for a boolean split tools Compound. " "It is strongly recommended to extract the shape to mesh " "from the Compound and use this one." ) Console.PrintError(err + "\n") for mr_obj in self.mesh_obj.MeshBoundaryLayerList: if mr_obj.MinimumThickness and Units.Quantity(mr_obj.MinimumThickness).Value > 0: if mr_obj.References: belem_list = [] for sub in mr_obj.References: # print(sub[0]) # Part the elements belongs to # check if the shape of the mesh boundary_layer is an # element of the Part to mesh # if not try to find the element in the shape to mesh search_ele_in_shape_to_mesh = False if not self.part_obj.Shape.isSame(sub[0].Shape): Console.PrintLog( " One element of the mesh boundary layer {} is " "not an element of the Part to mesh.\n" "But we are going to try to find it in " "the Shape to mesh :-)\n" .format(mr_obj.Name) ) search_ele_in_shape_to_mesh = True for elems in sub[1]: # print(elems) # elems --> element if search_ele_in_shape_to_mesh: # we try to find the element it in the Shape to mesh # and use the found element as elems # the method getElement(element) does not return Solid elements ele_shape = geomtools.get_element(sub[0], elems) found_element = geomtools.find_element_in_shape( self.part_obj.Shape, ele_shape ) if found_element: # also elems = found_element else: Console.PrintError( "One element of the mesh boundary layer {} could " "not be found in the Part to mesh. " "It will be ignored.\n" .format(mr_obj.Name) ) # print(elems) # element if elems not in self.bl_boundary_list: # fetch settings in DocumentObject # fan setting is not implemented belem_list.append(elems) self.bl_boundary_list.append(elems) else: Console.PrintError( "The element {} of the mesh boundary " "layer {} has been added " "to another mesh boundary layer.\n" .format(elems, mr_obj.Name) ) setting = {} setting["hwall_n"] = Units.Quantity(mr_obj.MinimumThickness).Value setting["ratio"] = mr_obj.GrowthRate setting["thickness"] = sum([ setting["hwall_n"] * setting["ratio"] ** i for i in range( mr_obj.NumberOfLayers ) ]) # setting["hwall_n"] * 5 # tangential cell dimension setting["hwall_t"] = setting["thickness"] # hfar: cell dimension outside boundary # should be set later if some character length is set if self.clmax > setting["thickness"] * 0.8 \ and self.clmax < setting["thickness"] * 1.6: setting["hfar"] = self.clmax else: # set a value for safety, it may works as background mesh cell size setting["hfar"] = setting["thickness"] # from face name -> face id is done in geo file write up # TODO: fan angle setup is not implemented yet if self.dimension == "2": setting["EdgesList"] = belem_list elif self.dimension == "3": setting["FacesList"] = belem_list else: Console.PrintError( "boundary layer is only supported for 2D and 3D mesh.\n" ) self.bl_setting_list.append(setting) else: Console.PrintError( "The mesh boundary layer: {} is not used to create " "the mesh because the reference list is empty.\n" .format(mr_obj.Name) ) else: Console.PrintError( "The mesh boundary layer: {} is not used to create " "the mesh because the min thickness is 0.0 mm.\n" .format(mr_obj.Name) ) Console.PrintMessage(" {}\n".format(self.bl_setting_list))
def get_region_data(self): # mesh regions if not self.mesh_obj.MeshRegionList: # print(" No mesh regions.") pass else: Console.PrintMessage(' Mesh regions, we need to get the elements.\n') # by the use of MeshRegion object and a BooleanSplitCompound # there could be problems with node numbers see # http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&start=40#p149467 # http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&p=149520#p149520 part = self.part_obj if ( self.mesh_obj.MeshRegionList and part.Shape.ShapeType == "Compound" and ( femutils.is_of_type(part, "FeatureBooleanFragments") or femutils.is_of_type(part, "FeatureSlice") or femutils.is_of_type(part, "FeatureXOR") ) ): error_message = ( " The mesh to shape is a boolean split tools Compound " "and the mesh has mesh region list. " "Gmsh could return unexpected meshes in such circumstances. " "It is strongly recommended to extract the shape to mesh " "from the Compound and use this one." ) Console.PrintError(error_message + "\n") # TODO: no gui popup because FreeCAD will be in a endless output loop # as long as the pop up is on --> maybe find a better solution for # either of both --> thus the pop up is in task panel for mr_obj in self.mesh_obj.MeshRegionList: # print(mr_obj.Name) # print(mr_obj.CharacteristicLength) # print(Units.Quantity(mr_obj.CharacteristicLength).Value) if mr_obj.CharacteristicLength: if mr_obj.References: for sub in mr_obj.References: # print(sub[0]) # Part the elements belongs to # check if the shape of the mesh region # is an element of the Part to mesh # if not try to find the element in the shape to mesh search_ele_in_shape_to_mesh = False if not self.part_obj.Shape.isSame(sub[0].Shape): Console.PrintLog( " One element of the meshregion {} is " "not an element of the Part to mesh.\n" "But we are going to try to find it in " "the Shape to mesh :-)\n" .format(mr_obj.Name) ) search_ele_in_shape_to_mesh = True for elems in sub[1]: # print(elems) # elems --> element if search_ele_in_shape_to_mesh: # we're going to try to find the element in the # Shape to mesh and use the found element as elems # the method getElement(element) # does not return Solid elements ele_shape = geomtools.get_element(sub[0], elems) found_element = geomtools.find_element_in_shape( self.part_obj.Shape, ele_shape ) if found_element: elems = found_element else: Console.PrintError( "One element of the meshregion {} could not be found " "in the Part to mesh. It will be ignored.\n" .format(mr_obj.Name) ) # print(elems) # element if elems not in self.ele_length_map: self.ele_length_map[elems] = Units.Quantity( mr_obj.CharacteristicLength ).Value else: Console.PrintError( "The element {} of the meshregion {} has " "been added to another mesh region.\n" .format(elems, mr_obj.Name) ) else: Console.PrintError( "The meshregion: {} is not used to create the mesh " "because the reference list is empty.\n" .format(mr_obj.Name) ) else: Console.PrintError( "The meshregion: {} is not used to create the " "mesh because the CharacteristicLength is 0.0 mm.\n" .format(mr_obj.Name) ) for eleml in self.ele_length_map: # the method getElement(element) does not return Solid elements ele_shape = geomtools.get_element(self.part_obj, eleml) ele_vertexes = geomtools.get_vertexes_by_element(self.part_obj.Shape, ele_shape) self.ele_node_map[eleml] = ele_vertexes Console.PrintMessage(" {}\n".format(self.ele_length_map)) Console.PrintMessage(" {}\n".format(self.ele_node_map))
def processRefinements(self): """ Process mesh refinements """ mr_objs = CfdTools.getMeshRefinementObjs(self.mesh_obj) if self.mesh_obj.MeshUtility == "gmsh": # mesh regions self.ele_length_map = {} # { 'ElementString' : element length } self.ele_node_map = {} # { 'ElementString' : [element nodes] } if not mr_objs: print(' No mesh refinements') else: print(' Mesh refinements found - getting elements') if self.part_obj.Shape.ShapeType == 'Compound': # see http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&start=40#p149467 and http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&p=149520#p149520 err = "GMSH could return unexpected meshes for a boolean split tools Compound. It is strongly recommended to extract the shape to mesh from the Compound and use this one." FreeCAD.Console.PrintError(err + "\n") for mr_obj in mr_objs: if mr_obj.RelativeLength: if mr_obj.References: for sub in mr_obj.References: # Check if the shape of the mesh region is an element of the Part to mesh; # if not try to find the element in the shape to mesh search_ele_in_shape_to_mesh = False ref = FreeCAD.ActiveDocument.getObject(sub[0]) if not self.part_obj.Shape.isSame(ref.Shape): search_ele_in_shape_to_mesh = True elems = sub[1] if search_ele_in_shape_to_mesh: # Try to find the element in the Shape to mesh ele_shape = FemGeomTools.get_element( ref, elems ) # the method getElement(element) does not return Solid elements found_element = CfdTools.findElementInShape( self.part_obj.Shape, ele_shape) if found_element: elems = found_element else: FreeCAD.Console.PrintError( "One element of the meshregion " + mr_obj.Name + " could not be found in the Part to mesh. It will be ignored.\n" ) elems = None if elems: if elems not in self.ele_length_map: # self.ele_length_map[elems] = Units.Quantity(mr_obj.CharacteristicLength).Value mr_rellen = mr_obj.RelativeLength if mr_rellen > 1.0: mr_rellen = 1.0 FreeCAD.Console.PrintError( "The meshregion: " + mr_obj.Name + " should not use a relative length greater than unity.\n" ) elif mr_rellen < 0.01: mr_rellen = 0.01 # Relative length should not be less than 1/100 of base length FreeCAD.Console.PrintError( "The meshregion: " + mr_obj.Name + " should not use a relative length smaller than 0.01.\n" ) self.ele_length_map[ elems] = mr_rellen * self.clmax else: FreeCAD.Console.PrintError( "The element " + elems + " of the mesh refinement " + mr_obj.Name + " has been added to another mesh refinement.\n" ) else: FreeCAD.Console.PrintError( "The meshregion: " + mr_obj.Name + " is not used to create the mesh because the reference list is empty.\n" ) else: FreeCAD.Console.PrintError( "The meshregion: " + mr_obj.Name + " is not used to create the mesh because the CharacteristicLength is 0.0 mm.\n" ) for eleml in self.ele_length_map: ele_shape = FemGeomTools.get_element( self.part_obj, eleml ) # the method getElement(element) does not return Solid elements ele_vertexes = FemGeomTools.get_vertexes_by_element( self.part_obj.Shape, ele_shape) self.ele_node_map[eleml] = ele_vertexes else: cf_settings = self.cf_settings cf_settings['MeshRegions'] = {} cf_settings['BoundaryLayers'] = {} cf_settings['InternalRegions'] = {} snappy_settings = self.snappy_settings snappy_settings['MeshRegions'] = {} snappy_settings['InternalRegions'] = {} from collections import defaultdict ele_meshpatch_map = defaultdict(list) if not mr_objs: print(' No mesh refinement') else: print(' Mesh refinements - getting the elements') if "Boolean" in self.part_obj.Name: err = "Cartesian meshes should not be generated for boolean split compounds." FreeCAD.Console.PrintError(err + "\n") # Make list of list of all references for their corresponding mesh object bl_matched_faces = [] if self.mesh_obj.MeshUtility == 'cfMesh': CfdTools.cfdMessage("Matching refinement regions") region_face_list = [] for mr_id, mr_obj in enumerate(mr_objs): if mr_obj.NumberLayers > 1 and not mr_obj.Internal: refs = mr_obj.References for r in refs: obj = FreeCAD.ActiveDocument.getObject(r[0]) if not obj: raise RuntimeError( "Referenced object '{}' not found - object may " "have been deleted".format(r[0])) try: f = obj.Shape.getElement(r[1]) except Part.OCCError: raise RuntimeError( "Referenced face '{}:{}' not found - face may " "have been deleted".format(r[0], r[1])) region_face_list.append((f, mr_id)) # Make list of all faces in meshed shape with original index mesh_face_list = \ list(zip(self.mesh_obj.Part.Shape.Faces, range(len(self.mesh_obj.Part.Shape.Faces)))) # Match them up bl_matched_faces = CfdTools.matchFaces( region_face_list, mesh_face_list) for mr_id, mr_obj in enumerate(mr_objs): Internal = mr_obj.Internal if mr_obj.RelativeLength: # Store parameters per region mr_rellen = mr_obj.RelativeLength if mr_rellen > 1.0: mr_rellen = 1.0 FreeCAD.Console.PrintError( "The meshregion: {} should not use a relative length greater " "than unity.\n".format(mr_obj.Name)) elif mr_rellen < 0.001: mr_rellen = 0.001 # Relative length should not be less than 0.1% of base length FreeCAD.Console.PrintError( "The meshregion: {} should not use a relative length smaller " "than 0.001.\n".format(mr_obj.Name)) if not (self.mesh_obj.MeshUtility == 'snappyHexMesh' and mr_obj.Baffle): fid = open( os.path.join(self.triSurfaceDir, mr_obj.Name + '.stl'), 'w') snappy_mesh_region_list = [] patch_list = [] for (si, sub) in enumerate(mr_obj.References): shape = FreeCAD.ActiveDocument.getObject( sub[0]).Shape elem = sub[1] if self.mesh_obj.MeshUtility == 'snappyHexMesh' and mr_obj.Baffle: # Save baffle references or faces individually baffle = "{}{}{}".format( mr_obj.Name, sub[0], elem) fid = open( os.path.join(self.triSurfaceDir, baffle + ".stl"), 'w') snappy_mesh_region_list.append(baffle) if elem.startswith( 'Solid' ): # getElement doesn't work with solids for some reason elt = shape.Solids[int(elem.lstrip('Solid')) - 1] else: elt = shape.getElement(elem) if elt.ShapeType == 'Face' or elt.ShapeType == 'Solid': CfdTools.cfdMessage( "Triangulating part: {}:{} ...".format( FreeCAD.ActiveDocument.getObject( sub[0]).Label, sub[1])) facemesh = MeshPart.meshFromShape( elt, LinearDeflection=self.mesh_obj. STLLinearDeflection) CfdTools.cfdMessage(" writing to file\n") fid.write("solid {}{}{}\n".format( mr_obj.Name, sub[0], elem)) for face in facemesh.Facets: fid.write(" facet normal 0 0 0\n") fid.write(" outer loop\n") for i in range(3): p = [ i * self.scale for i in face.Points[i] ] fid.write( " vertex {} {} {}\n".format( p[0], p[1], p[2])) fid.write(" endloop\n") fid.write(" endfacet\n") fid.write("endsolid {}{}{}\n".format( mr_obj.Name, sub[0], elem)) if self.mesh_obj.MeshUtility == 'snappyHexMesh' and mr_obj.Baffle: fid.close() if not (self.mesh_obj.MeshUtility == 'snappyHexMesh' and mr_obj.Baffle): fid.close() if self.mesh_obj.MeshUtility == 'cfMesh' and mr_obj.NumberLayers > 1 and not Internal: for mf in bl_matched_faces: if mr_id == mf[0]: sfN = self.mesh_obj.ShapeFaceNames[mf[1]] ele_meshpatch_map[mr_obj.Name].append(sfN) patch_list.append(sfN) # Limit expansion ratio to greater than 1.0 and less than 1.2 expratio = mr_obj.ExpansionRatio expratio = min(1.2, max(1.0, expratio)) cf_settings['BoundaryLayers'][ self.mesh_obj. ShapeFaceNames[mf[1]]] = { 'NumberLayers': mr_obj.NumberLayers, 'ExpansionRatio': expratio, 'FirstLayerHeight': self.scale * Units.Quantity( mr_obj.FirstLayerHeight).Value } if self.mesh_obj.MeshUtility == 'cfMesh': if not Internal: cf_settings['MeshRegions'][mr_obj.Name] = { 'RelativeLength': mr_rellen * self.clmax * self.scale, 'RefinementThickness': self.scale * Units.Quantity( mr_obj.RefinementThickness).Value, } else: cf_settings['InternalRegions'][mr_obj.Name] = { 'RelativeLength': mr_rellen * self.clmax * self.scale } elif self.mesh_obj.MeshUtility == 'snappyHexMesh': refinement_level = CfdTools.relLenToRefinementLevel( mr_obj.RelativeLength) if not Internal: if not mr_obj.Baffle: snappy_mesh_region_list.append(mr_obj.Name) edge_level = CfdTools.relLenToRefinementLevel( mr_obj.RegionEdgeRefinement) for rL in range(len(snappy_mesh_region_list)): mrName = mr_obj.Name + snappy_mesh_region_list[ rL] snappy_settings['MeshRegions'][mrName] = { 'RegionName': snappy_mesh_region_list[rL], 'RefinementLevel': refinement_level, 'EdgeRefinementLevel': edge_level, 'MaxRefinementLevel': max(refinement_level, edge_level), 'Baffle': mr_obj.Baffle } else: snappy_settings['InternalRegions'][ mr_obj.Name] = { 'RefinementLevel': refinement_level } else: FreeCAD.Console.PrintError( "The meshregion: " + mr_obj.Name + " is not used to create the mesh because the " "CharacteristicLength is 0.0 mm or the reference list is empty.\n" )
def processRefinements(self): """ Process mesh refinements """ mr_objs = CfdTools.getMeshRefinementObjs(self.mesh_obj) if self.mesh_obj.MeshUtility == "gmsh": # mesh regions self.ele_length_map = {} # { 'ElementString' : element length } self.ele_node_map = {} # { 'ElementString' : [element nodes] } if not mr_objs: print(' No mesh refinements') else: print(' Mesh refinements found - getting elements') if self.part_obj.Shape.ShapeType == 'Compound': # see http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&start=40#p149467 and http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&p=149520#p149520 err = "GMSH could return unexpected meshes for a boolean split tools Compound. It is strongly recommended to extract the shape to mesh from the Compound and use this one." FreeCAD.Console.PrintError(err + "\n") for mr_obj in mr_objs: if mr_obj.RelativeLength: if mr_obj.References: for sub in mr_obj.References: # Check if the shape of the mesh region is an element of the Part to mesh; # if not try to find the element in the shape to mesh search_ele_in_shape_to_mesh = False ref = FreeCAD.ActiveDocument.getObject(sub[0]) if not self.part_obj.Shape.isSame(ref.Shape): search_ele_in_shape_to_mesh = True elems = sub[1] if search_ele_in_shape_to_mesh: # Try to find the element in the Shape to mesh ele_shape = FemGeomTools.get_element( ref, elems ) # the method getElement(element) does not return Solid elements found_element = CfdTools.findElementInShape( self.part_obj.Shape, ele_shape) if found_element: elems = found_element else: FreeCAD.Console.PrintError( "One element of the meshregion " + mr_obj.Name + " could not be found in the Part to mesh. It will be ignored.\n" ) elems = None if elems: if elems not in self.ele_length_map: # self.ele_length_map[elems] = Units.Quantity(mr_obj.CharacteristicLength).Value mr_rellen = mr_obj.RelativeLength if mr_rellen > 1.0: mr_rellen = 1.0 FreeCAD.Console.PrintError( "The meshregion: " + mr_obj.Name + " should not use a relative length greater than unity.\n" ) elif mr_rellen < 0.01: mr_rellen = 0.01 # Relative length should not be less than 1/100 of base length FreeCAD.Console.PrintError( "The meshregion: " + mr_obj.Name + " should not use a relative length smaller than 0.01.\n" ) self.ele_length_map[ elems] = mr_rellen * self.clmax else: FreeCAD.Console.PrintError( "The element " + elems + " of the mesh refinement " + mr_obj.Name + " has been added to another mesh refinement.\n" ) else: FreeCAD.Console.PrintError( "The meshregion: " + mr_obj.Name + " is not used to create the mesh because the reference list is empty.\n" ) else: FreeCAD.Console.PrintError( "The meshregion: " + mr_obj.Name + " is not used to create the mesh because the CharacteristicLength is 0.0 mm.\n" ) for eleml in self.ele_length_map: ele_shape = FemGeomTools.get_element( self.part_obj, eleml ) # the method getElement(element) does not return Solid elements ele_vertexes = FemGeomTools.get_vertexes_by_element( self.part_obj.Shape, ele_shape) self.ele_node_map[eleml] = ele_vertexes else: cf_settings = self.cf_settings cf_settings['MeshRegions'] = {} cf_settings['BoundaryLayers'] = {} cf_settings['InternalRegions'] = {} snappy_settings = self.snappy_settings snappy_settings['MeshRegions'] = {} snappy_settings['InternalRegions'] = {} # Make list of all faces in meshed shape with original index mesh_face_list = list( zip(self.mesh_obj.Part.Shape.Faces, range(len(self.mesh_obj.Part.Shape.Faces)))) # Make list of all boundary references CfdTools.cfdMessage("Matching boundary patches\n") bc_group = None analysis_obj = CfdTools.getParentAnalysisObject(self.mesh_obj) if not analysis_obj: analysis_obj = CfdTools.getActiveAnalysis() if analysis_obj: bc_group = CfdTools.getCfdBoundaryGroup(analysis_obj) boundary_face_list = [] for bc_id, bc_obj in enumerate(bc_group): for ri, ref in enumerate(bc_obj.References): try: bf = CfdTools.resolveReference(ref) except RuntimeError as re: raise RuntimeError( "Error processing boundary condition {}: {}". format(bc_obj.Label, str(re))) boundary_face_list.append((bf, (bc_id, ref, ri))) # Match them up to faces in the main geometry bc_matched_faces = CfdTools.matchFaces(boundary_face_list, mesh_face_list) # Make list of all boundary layer mesh regions for cfMesh bl_matched_faces = [] if self.mesh_obj.MeshUtility == 'cfMesh': CfdTools.cfdMessage("Matching boundary layer regions\n") bl_face_list = [] for mr_id, mr_obj in enumerate(mr_objs): if mr_obj.NumberLayers > 1 and not mr_obj.Internal: for ri, r in enumerate(mr_obj.References): try: f = CfdTools.resolveReference(r) except RuntimeError as re: raise RuntimeError( "Error processing mesh refinement {}: {}". format(mr_obj.Label, str(re))) bl_face_list.append((f, (mr_id, r, ri))) # Match them up bl_matched_faces = CfdTools.matchFaces(bl_face_list, mesh_face_list) # Check for and filter duplicates bc_match_per_shape_face = [-1] * len(mesh_face_list) for k in range(len(bc_matched_faces)): match = bc_matched_faces[k][1] prev_k = bc_match_per_shape_face[match] if prev_k >= 0: nb, bref, ri = bc_matched_faces[k][0] nb2, bref2, ri2 = bc_matched_faces[prev_k][0] CfdTools.cfdMessage( "Boundary '{}' reference {}:{} also assigned as " "boundary '{}' reference {}:{} - ignoring duplicate\n". format(bc_group[nb].Label, bref[0], bref[1], bc_group[nb2].Label, bref2[0], bref2[1])) else: bc_match_per_shape_face[match] = k bl_match_per_shape_face = [-1] * len(mesh_face_list) for k in range(len(bl_matched_faces)): match = bl_matched_faces[k][1] prev_k = bl_match_per_shape_face[match] if prev_k >= 0: nr, ref, ri = bl_matched_faces[k][0] nr2, ref2, ri2 = bl_matched_faces[prev_k][0] CfdTools.cfdMessage( "Mesh refinement '{}' reference {}:{} also assigned as " "mesh refinement '{}' reference {}:{} - ignoring duplicate\n" .format(mr_objs[nr].Label, ref[0], ref[1], mr_objs[nr2].Label, ref2[0], ref2[1])) else: bl_match_per_shape_face[match] = k self.patch_faces = [] self.patch_names = [] for k in range(len(bc_group) + 1): self.patch_faces.append([]) self.patch_names.append([]) for l in range(len(mr_objs) + 1): self.patch_faces[k].append([]) self.patch_names[k].append("patch_" + str(k) + "_" + str(l)) for i in range(len(mesh_face_list)): k = bc_match_per_shape_face[i] l = bl_match_per_shape_face[i] nb = -1 nr = -1 if k >= 0: nb, bref, bri = bc_matched_faces[k][0] if l >= 0: nr, ref, rri = bl_matched_faces[l][0] self.patch_faces[nb + 1][nr + 1].append(i) # Additionally for snappy, match baffles to any surface mesh refinements # as well as matching each surface mesh refinement region to boundary conditions mr_face_list = [] bc_mr_matched_faces = [] if self.mesh_obj.MeshUtility == 'snappyHexMesh': CfdTools.cfdMessage("Matching surface geometries\n") for mr_id, mr_obj in enumerate(mr_objs): if not mr_obj.Internal: for ri, r in enumerate(mr_obj.References): try: f = CfdTools.resolveReference(r) except RuntimeError as re: raise RuntimeError( "Error processing mesh refinement {}: {}". format(mr_obj.Label, str(re))) mr_face_list.append((f, (mr_id, r, ri))) # Match mesh regions to the boundary conditions, to identify boundary conditions on supplementary # geometry (including on baffles) bc_mr_matched_faces = CfdTools.matchFaces( boundary_face_list, mr_face_list) for bc_id, bc_obj in enumerate(bc_group): if bc_obj.BoundaryType == 'baffle': baffle_matches = [ m for m in bc_mr_matched_faces if m[0][0] == bc_id ] mr_match_per_baffle_ref = [-1] * len(bc_obj.References) for m in baffle_matches: mr_match_per_baffle_ref[m[0][2]] = m[1][0] # For each mesh region, the refs that are part of this baffle baffle_patch_refs = [[] for ri in range(len(mr_objs) + 1)] for ri, mri in enumerate(mr_match_per_baffle_ref): baffle_patch_refs[mri + 1].append( bc_obj.References[ri]) # Write these geometries for ri, refs in enumerate(baffle_patch_refs): try: shape = CfdTools.makeShapeFromReferences(refs) except RuntimeError as re: raise RuntimeError( "Error processing baffle {}: {}".format( bc_obj.Label, str(re))) solid_name = bc_obj.Name + "_" + str(ri) if shape: CfdTools.cfdMessage( "Triangulating baffle {}, section {} ...". format(bc_obj.Label, ri)) facemesh = MeshPart.meshFromShape( shape, LinearDeflection=self.mesh_obj. STLLinearDeflection) CfdTools.cfdMessage(" writing to file\n") with open( os.path.join(self.triSurfaceDir, solid_name + '.stl'), 'w') as fid: CfdTools.writePatchToStl( solid_name, facemesh, fid, self.scale) if ri > 0: # The parts of the baffle corresponding to a surface mesh region obj mr_obj = mr_objs[ri - 1] refinement_level = CfdTools.relLenToRefinementLevel( mr_obj.RelativeLength) edge_level = CfdTools.relLenToRefinementLevel( mr_obj.RegionEdgeRefinement) else: # The parts of the baffle with no refinement obj refinement_level = 0 edge_level = 0 snappy_settings['MeshRegions'][solid_name] = { 'RefinementLevel': refinement_level, 'EdgeRefinementLevel': edge_level, 'MaxRefinementLevel': max(refinement_level, edge_level), 'Baffle': True } mr_matched_faces = [] if self.mesh_obj.MeshUtility == 'snappyHexMesh': # Match mesh regions to the primary geometry mr_matched_faces = CfdTools.matchFaces(mr_face_list, mesh_face_list) for mr_id, mr_obj in enumerate(mr_objs): Internal = mr_obj.Internal mr_rellen = mr_obj.RelativeLength if mr_rellen > 1.0: mr_rellen = 1.0 FreeCAD.Console.PrintError( "The mesh refinement region '{}' should not use a relative length greater " "than unity.\n".format(mr_obj.Name)) elif mr_rellen < 0.001: mr_rellen = 0.001 # Relative length should not be less than 0.1% of base length FreeCAD.Console.PrintError( "The mesh refinement region '{}' should not use a relative length smaller " "than 0.001.\n".format(mr_obj.Name)) # Find any matches with boundary conditions; mark those matching baffles for removal bc_matches = [ m for m in bc_mr_matched_faces if m[1][0] == mr_id ] bc_match_per_mr_ref = [-1] * len(mr_obj.References) for m in bc_matches: bc_match_per_mr_ref[m[1][2]] = -2 if bc_group[ m[0][0]].BoundaryType == 'baffle' else m[0][0] # Unmatch those in primary geometry main_geom_matches = [ m for m in mr_matched_faces if m[0][0] == mr_id ] for m in main_geom_matches: bc_match_per_mr_ref[m[0][2]] = -1 # For each boundary, the refs that are part of this mesh region mr_patch_refs = [[] for ri in range(len(bc_group) + 1)] for ri, bci in enumerate(bc_match_per_mr_ref): if bci > -2: mr_patch_refs[bci + 1].append(mr_obj.References[ri]) # Loop over and write the sub-sections of this mesh object for bi in range(len(mr_patch_refs)): if len(mr_patch_refs[bi]): if bi == 0: mr_patch_name = mr_obj.Name else: mr_patch_name = self.patch_names[bi][mr_id + 1] CfdTools.cfdMessage( "Triangulating mesh refinement region {}, section {} ..." .format(mr_obj.Label, bi)) try: shape = CfdTools.makeShapeFromReferences( mr_patch_refs[bi]) except RuntimeError as re: raise RuntimeError( "Error processing mesh refinement region {}: {}" .format(mr_obj.Label, str(re))) if shape: facemesh = MeshPart.meshFromShape( shape, LinearDeflection=self.mesh_obj. STLLinearDeflection) CfdTools.cfdMessage(" writing to file\n") with open( os.path.join(self.triSurfaceDir, mr_patch_name + '.stl'), 'w') as fid: CfdTools.writePatchToStl( mr_patch_name, facemesh, fid, self.scale) if self.mesh_obj.MeshUtility == 'cfMesh': if not Internal: cf_settings['MeshRegions'][mr_patch_name] = { 'RelativeLength': mr_rellen * self.clmax * self.scale, 'RefinementThickness': self.scale * Units.Quantity( mr_obj.RefinementThickness).Value, } else: cf_settings['InternalRegions'][mr_obj.Name] = { 'RelativeLength': mr_rellen * self.clmax * self.scale } elif self.mesh_obj.MeshUtility == 'snappyHexMesh': refinement_level = CfdTools.relLenToRefinementLevel( mr_obj.RelativeLength) if not Internal: edge_level = CfdTools.relLenToRefinementLevel( mr_obj.RegionEdgeRefinement) snappy_settings['MeshRegions'][ mr_patch_name] = { 'RefinementLevel': refinement_level, 'EdgeRefinementLevel': edge_level, 'MaxRefinementLevel': max(refinement_level, edge_level), 'Baffle': False } else: snappy_settings['InternalRegions'][ mr_patch_name] = { 'RefinementLevel': refinement_level } # In addition, for cfMesh, record matched boundary layer patches if self.mesh_obj.MeshUtility == 'cfMesh' and mr_obj.NumberLayers > 1 and not Internal: for k in range(len(self.patch_faces)): # Limit expansion ratio to greater than 1.0 and less than 1.2 expratio = mr_obj.ExpansionRatio expratio = min(1.2, max(1.0, expratio)) cf_settings['BoundaryLayers'][ self.patch_names[k][mr_id]] = { 'NumberLayers': mr_obj.NumberLayers, 'ExpansionRatio': expratio, 'FirstLayerHeight': self.scale * Units.Quantity(mr_obj.FirstLayerHeight).Value }