1""" 2 FreeCADBatchFEMTools - A library for using FreeCAD for FEM preprocessing in batch mode 3 4 Copyright 1st May 2018 - , Trafotek Oy, Finland 5 6 This library is free software; you can redistribute it and/or 7 modify it under the terms of the GNU Lesser General Public 8 License as published by the Free Software Foundation; either 9 version 2.1 of the License, or (at your option) any later version. 10 11 This library is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 Lesser General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with this library (in file ../LGPL-2.1); if not, write 18 to the Free Software Foundation, Inc., 51 Franklin Street, 19 Fifth Floor, Boston, MA 02110-1301 USA 20 21 Authors: Eelis Takala, Janne Keranen, Sami Rannikko 22 Emails: eelis.takala@gmail.com, janne.sami.keranen@vtt.fi 23 Address: Trafotek Oy 24 Kaarinantie 700 25 20540 Turku 26 Finland 27 28 Original Date: May 2018 29""" 30from __future__ import print_function 31import os 32import Fem 33import FreeCAD 34import Part 35import BOPTools.SplitFeatures 36import ObjectsFem 37import femmesh.gmshtools 38import math 39import itertools 40import subprocess 41 42import meshutils 43 44 45def fit_view(): 46 """ 47 If GUI is available, fit the view so that the geometry can be seen 48 """ 49 if FreeCAD.GuiUp: 50 import FreeCADGui 51 FreeCADGui.ActiveDocument.activeView().viewAxonometric() 52 FreeCADGui.SendMsgToActiveView("ViewFit") 53 54def isclose(a, b, rel_tol=1e-4, abs_tol=1e-4): 55 """ 56 Returns True if a and b are close to each other (within absolute or relative tolerance). 57 58 :param a: float 59 :param b: float 60 :param rel_tol: float 61 :param abs_tol: float 62 :return: bool 63 """ 64 return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) 65 66def vectors_are_same(vec1,vec2,tol=1e-4): 67 """ 68 Compares vectors vec1 and vec2. If they are same within a tolerance returns 69 True if not returns false. 70 71 :param vec1: Vector 1 72 :param vec2: Vector 2 to be compared with Vector 1 73 :return: Boolean 74 """ 75 vec3 = vec1.sub(vec2) 76 return isclose(vec3.Length, 0., abs_tol=tol) 77 78def faces_with_vertices_in_symmetry_plane(face_object_list, plane=None, abs_tol=1e-4): 79 """ 80 Returns faces from a list of FreeCAD face objects. The returned faces have to 81 be in a defined symmetry plane. The face is in symmetry plane if all of its points 82 and the center of mass are in the plane. 83 84 :param face_object_list: list of FreeCAD face objects 85 :param plane: symmetry plane. 86 87 :return: list of FreeCAD face objects that are in the given symmetry plane 88 """ 89 if plane is None: return None 90 face_object_list_out = [] 91 for face_object in face_object_list: 92 vertices = face_object.Vertexes 93 center_of_mass = face_object.CenterOfMass 94 if plane in ['zx', 'xz']: center_compare_value = center_of_mass.y 95 elif plane in ['xy', 'yx']: center_compare_value = center_of_mass.z 96 elif plane in ['yz', 'zy']: center_compare_value = center_of_mass.x 97 else: raise ValueError("Wrong keyword for plane variable, should be: zx, xy, yz, xz, yx or zy!") 98 for i, vertex in enumerate(vertices): 99 if plane in ['zx', 'xz']: compare_value = vertex.Y 100 elif plane in ['xy', 'yx']: compare_value = vertex.Z 101 elif plane in ['yz', 'zy']: compare_value = vertex.X 102 else: raise ValueError("Wrong keyword for plane variable, should be: zx, xy, yz, xz, yx or zy!") 103 if not isclose(compare_value, 0., abs_tol=abs_tol): break 104 if i==len(vertices)-1 and isclose(center_compare_value, 0., abs_tol=abs_tol): face_object_list_out.append(face_object) 105 return face_object_list_out 106 107def reduce_half_symmetry(solid, name, App, doc, planes=None, reversed_direction = False): 108 doc.recompute() 109 if planes==None: return solid 110 plane = planes.pop() 111 doc.recompute() 112 reduced_name = name + '_' + plane 113 tool_box = doc.addObject("Part::Box","CutBox"+reduced_name) 114 x = 10. * solid.Shape.BoundBox.XLength 115 y = 10. * solid.Shape.BoundBox.YLength 116 z = 10. * solid.Shape.BoundBox.ZLength 117 if isinstance(solid, Part.Feature): 118 center=solid.Shape.Solids[0].CenterOfMass 119 else: 120 center=solid.Shape.CenterOfMass 121 122 tool_box.Length = x 123 tool_box.Width = y 124 tool_box.Height = z 125 if plane == 'zx': 126 tool_box.Placement = App.Placement(App.Vector(center.x-x/2.,0,center.z-z/2.),App.Rotation(App.Vector(0,0,1),0)) 127 elif plane == 'xy': 128 tool_box.Placement = App.Placement(App.Vector(center.x-x/2.,center.y-y/2.,0),App.Rotation(App.Vector(0,0,1),0)) 129 elif plane == 'yz': 130 tool_box.Placement = App.Placement(App.Vector(0,center.y-y/2.,center.z-z/2.),App.Rotation(App.Vector(0,0,1),0)) 131 else: 132 raise ValueError("Wrong keyword for plane variable, should be: zx, xy or yz!") 133 134 if reversed_direction: 135 half_symmetry = doc.addObject("Part::MultiCommon",reduced_name) 136 half_symmetry.Shapes = [solid, tool_box] 137 else: 138 half_symmetry = doc.addObject("Part::Cut", reduced_name) 139 half_symmetry.Base = solid 140 half_symmetry.Tool = tool_box 141 142 if len(planes) > 0: 143 return reduce_half_symmetry(half_symmetry, reduced_name, App, doc, planes, reversed_direction) 144 145 return half_symmetry 146 147def faces_same_center_of_masses(face1, face2, tolerance=0.0001): 148 """ 149 Compare two faces by comparing if they have same centers of mass with the tolerance. 150 151 :param face1: FreeCAD face object 152 :param face2: FreeCAD face object 153 :param tolerance: float 154 155 """ 156 return vectors_are_same(face1.CenterOfMass, face2.CenterOfMass, tolerance) 157 158def faces_are_same(face1, face2, tolerance=1e-4): 159 """ 160 Return true if face1 is same as face2. The faces are same if they have 161 the same center of masses and same vertices. 162 163 :param face1: FreeCAD face object 164 :param face2: FreeCAD face object 165 166 :return: bool 167 """ 168 return faces_same_center_of_masses(face1, face2, tolerance) and faces_have_same_vertices(face1, face2, tolerance) 169 170def is_face_in_list(search_face, face_object_list, tolerance=1e-4): 171 """ 172 Returns true if search_face is in the face_object_list. Compares faces with 173 face_compare method. 174 175 :param search_face: FreeCAD face object 176 :param face_object_list: list of FreeCAD face objects 177 """ 178 for face_object in face_object_list: 179 if faces_are_same(search_face, face_object): return True 180 return False 181 182def remove_compare_face_from_list(cface, face_object_list, tolerance=1e-4): 183 """ 184 Removes the first FreeCAD face object that matches in the FreeCAD face object list. 185 Uses face_compare to determine if the face is to be removed. 186 187 :param cface: a FreeCAD face object to be compared 188 :param face_object_list: the list of FreeCAD face objects where the face 189 is to be removed in case of a match 190 191 :return: list of FreeCAD face objects that are removed from the original list of 192 FreeCAD face objects. 193 """ 194 195 for i, face_object in enumerate(face_object_list): 196 if faces_are_same(cface, face_object): 197 return face_object_list.pop(i) 198 return None 199 200def remove_compare_faces_from_list(compare_face_object_list, face_object_list): 201 """ 202 Removes all the face objects in compare_face_object_list that match to the face objects in 203 the face_object_list. Uses face_compare to determine if the face is to be removed. 204 205 :param compare_face_object_list: list of FreeCAD face objects to be compared 206 :param face_object_list: original list of FreeCAD face objects 207 208 :return: list of FreeCAD face objects that are removed from the original list of 209 FreeCAD face objects. 210 """ 211 removed = [] 212 for face_object in compare_face_object_list: 213 removed.append(remove_compare_face_from_list(face_object, face_object_list)) 214 return removed 215 216def faces_have_same_vertices(face1, face2, tolerance=0.0001): 217 """ 218 Compare two faces by comparing that they have same number of vertices and 219 the vertices are in identical coordinates with the tolerance. Return 220 truth value to the faces are the same in this regard. 221 222 :param face1: FreeCAD face object 223 :param face2: FreeCAD face object 224 :return: bool 225 """ 226 face_vertices_found = [] 227 for vertex in face2.Vertexes: 228 for cvertex in face1.Vertexes: 229 if vectors_are_same(vertex.Point,cvertex.Point, tolerance): 230 face_vertices_found.append(1) 231 return len(face_vertices_found) == len(face2.Vertexes) and len(face_vertices_found) == len(face1.Vertexes) 232 233def is_point_inside_face(face, vector, tolerance=0.0001): 234 """ 235 Returns True if point is inside face. 236 237 WARNING: This function calls function face.isInside which does NOT respect tolerance 238 https://forum.freecadweb.org/viewtopic.php?t=31524 239 240 :param face: FreeCAD face object 241 :param vector: Vector 242 :param tolerance: float 243 244 :return: bool 245 """ 246 return face.isInside(vector, tolerance, True) 247 248def is_point_inside_solid(solid, vector, tolerance=0.0001, include_faces=True): 249 """ 250 Returns True if point is inside solid. 251 252 :param solid: FreeCAD solid object 253 :param vector: Vector 254 :param tolerance: float 255 :param include_faces: bool 256 257 :return: bool 258 """ 259 return solid.isInside(vector, tolerance, include_faces) 260 261def is_point_inside_solid_with_round(solid, vector, tolerance=0.0001, round_digits=6): 262 """ 263 Returns True if point is inside solid (faces included) with 264 additional tolerance (8 points checked). 265 Tries upper and lower rounding of coordinates with precision round_digits. 266 267 :param solid: FreeCAD solid object 268 :param vector: Vector 269 :param tolerance: float 270 :param round_digits: integer 271 272 :return: bool 273 """ 274 rounding = 10**round_digits 275 x_floor, x_ceil = math.floor(rounding*vector.x)/rounding, math.ceil(rounding*vector.x)/rounding 276 y_floor, y_ceil = math.floor(rounding*vector.y)/rounding, math.ceil(rounding*vector.y)/rounding 277 z_floor, z_ceil = math.floor(rounding*vector.z)/rounding, math.ceil(rounding*vector.z)/rounding 278 for coordinates in itertools.product([x_floor, x_ceil], [y_floor, y_ceil], [z_floor, z_ceil]): 279 vector.x = coordinates[0] 280 vector.y = coordinates[1] 281 vector.z = coordinates[2] 282 if is_point_inside_solid(solid, vector, tolerance): 283 return True 284 return False 285 286def is_same_vertices(vertex1, vertex2, tolerance=0.0001): 287 """ 288 Checks if given vertices are same. 289 290 :param vertex1: FreeCAD vertex 291 :param vertex2: FreeCAD vertex 292 :param tolerance: float 293 294 :return: bool 295 """ 296 if abs(vertex1.X - vertex2.X) < tolerance: 297 if abs(vertex1.Y - vertex2.Y) < tolerance: 298 if abs(vertex1.Z - vertex2.Z) < tolerance: 299 return True 300 return False 301 302def is_same_edge(edge1, edge2, tolerance=0.0001): 303 """ 304 Checks if given edges are in same place by comparing end points. 305 306 :param edge1: FreeCAD edge 307 :param edge2: FreeCAD edge 308 :param tolerance: float 309 310 :return: bool 311 """ 312 if is_same_vertices(edge1.Vertexes[0], edge2.Vertexes[0], tolerance): 313 if is_same_vertices(edge1.Vertexes[1], edge2.Vertexes[1], tolerance): 314 return True 315 elif is_same_vertices(edge1.Vertexes[0], edge2.Vertexes[1], tolerance): 316 if is_same_vertices(edge1.Vertexes[1], edge2.Vertexes[0], tolerance): 317 return True 318 return False 319 320def is_edge_in_solid(solid, edge, tolerance=0.0001): 321 """ 322 Returns True if edge inside solid by comparing is edge vertices inside solid. 323 324 :param solid: FreeCAD solid object 325 :param edge: FreeCAD edge object 326 :param tolerance: float 327 328 :return: bool 329 """ 330 for vertex in edge.Vertexes: 331 if not is_point_inside_solid(solid, vertex.Point, tolerance): 332 return False 333 return True 334 335def get_point_from_solid(solid, tolerance=0.0001): 336 """ 337 Returns point from given solid. 338 339 :param solid: FreeCAD solid object 340 :param tolerance: float 341 342 :return: None or FreeCAD vector object 343 """ 344 x_min, y_min, z_min = solid.BoundBox.XMin, solid.BoundBox.YMin, solid.BoundBox.ZMin 345 x_len, y_len, z_len = solid.BoundBox.XLength, solid.BoundBox.YLength, solid.BoundBox.ZLength 346 for split_count in [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 347 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 348 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 349 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 350 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541]: 351 x_split_len, y_split_len, z_split_len = x_len/split_count, y_len/split_count, z_len/split_count 352 for i in range(1, split_count): 353 x_test = x_min + i*x_split_len 354 for j in range(1, split_count): 355 y_test = y_min + j*y_split_len 356 for k in range(1, split_count): 357 test_point = FreeCAD.Vector(x_test, y_test, z_min + k*z_split_len) 358 if is_point_inside_solid(solid, test_point, tolerance, include_faces=False): 359 return test_point 360 return None 361 362def is_point_on_face_edges(face, p2, tol=0.0001): 363 """ 364 Checks if given point is on same edge of given face. 365 366 :param face: FreeCAD face object 367 :param p2: FreeCAD Vector object 368 :param tol: float 369 370 :return: bool 371 """ 372 vertex = Part.Vertex(p2) 373 for edge in face.Edges: 374 if vertex.distToShape(edge)[0] < tol: 375 return True 376 return False 377 378def get_point_from_face_close_to_edge(face): 379 """ 380 Increases parameter range minimum values of face by one until at least 381 two of the x, y and z coordinates of the corresponding point has moved at least 1 unit. 382 If point is not found None is returned. 383 384 :param face: FreeCAD face object. 385 386 :return: None or FreeCAD vector object. 387 """ 388 u_min, u_max, v_min, v_max = face.ParameterRange 389 p1 = face.valueAt(u_min, v_min) 390 u_test, v_test = u_min+1, v_min+1 391 while u_test < u_max and v_test < v_max: 392 p2 = face.valueAt(u_test, v_test) 393 # Check at least two coordinates moved 1 unit 394 if (abs(p1.x - p2.x) >= 1) + (abs(p1.y - p2.y) >= 1) + (abs(p1.z - p2.z) >= 1) > 1: 395 if is_point_on_face_edges(face, p2): 396 v_test += 0.5 397 continue # go back at the beginning of while 398 if face.isPartOfDomain(u_test, v_test): 399 return p2 400 return None 401 u_test, v_test = u_test+1, v_test+1 402 return None 403 404def get_point_from_face(face): 405 """ 406 Returns point from given face. 407 408 :param face: FreeCAD face object. 409 410 :return: None or FreeCAD vector object 411 """ 412 point = get_point_from_face_close_to_edge(face) 413 if point is not None: 414 return point 415 u_min, u_max, v_min, v_max = face.ParameterRange 416 u_len, v_len = u_max-u_min, v_max-v_min 417 # use primes so same points are not checked multiple times 418 for split_count in [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 419 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 420 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 421 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 422 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541]: 423 u_split_len, v_split_len = u_len/float(split_count), v_len/float(split_count) 424 for i in range(1, split_count): 425 u_test = u_min + i*u_split_len 426 for j in range(1, split_count): 427 v_test = v_min + j*v_split_len 428 if face.isPartOfDomain(u_test, v_test): 429 return face.valueAt(u_test, v_test) 430 return None 431 432def is_face_in_face(face1, face2, tolerance=0.0001): 433 """ 434 Returns True if all vertices and point in face1 also belongs to face2 435 436 :param face1: FreeCAD face object 437 :param face2: FreeCAD face object 438 :param tolerance: float 439 440 :return: bool 441 """ 442 for vertex in face1.Vertexes: 443 if not is_point_inside_face(face2, vertex.Point, tolerance): 444 return False 445 point_in_face1 = get_point_from_face(face1) 446 if point_in_face1 is not None: 447 if not is_point_inside_face(face2, point_in_face1, tolerance): 448 return False 449 else: 450 raise ValueError('Face point not found') 451 return True 452 453def is_face_in_solid(solid, face, tolerance=0.0001, use_round=True): 454 """ 455 Checks if all face vertices and one additional point from face are inside solid. 456 If use_round is True calls function meth:`is_point_inside_solid_with_round` to check 457 if point inside solid. 458 459 :param solid: FreeCAD solid object 460 :param face: FreeCAD face object 461 :param tolerance: float 462 :param use_round: bool 463 464 :return: bool 465 """ 466 if use_round: 467 is_point_in_solid_func = is_point_inside_solid_with_round 468 else: 469 is_point_in_solid_func = is_point_inside_solid 470 for vertex in face.Vertexes: 471 if not is_point_in_solid_func(solid, vertex.Point, tolerance): 472 return False 473 point_in_face = get_point_from_face(face) 474 if point_in_face is not None: 475 if not is_point_in_solid_func(solid, point_in_face, tolerance): 476 return False 477 else: 478 raise ValueError('Face point not found') 479 return True 480 481def is_compound_filter_solid_in_solid(compound_filter_solid, solid, tolerance=0.0001, point_search=True): 482 """ 483 If point_search is True: 484 returns True if all faces of compound_filter_solid are inside solid 485 else: 486 returns compound_filter_solid.common(solid).Volume > 0 487 488 :param compound_filter_solid: FreeCAD solid object 489 :param solid: FreeCAD solid object 490 :param tolerance: float (only used with point_search) 491 :param point_search: bool 492 493 :return: bool 494 """ 495 if point_search: 496 point_in_solid = get_point_from_solid(compound_filter_solid, tolerance) 497 if point_in_solid is None: 498 raise ValueError('Solid point not found') 499 return is_point_inside_solid(solid, point_in_solid, tolerance) 500 return compound_filter_solid.common(solid).Volume > 0 501 502def solids_are_the_same(solid1, solid2): 503 """ 504 Compare two solids by comparing have they same number of faces and the faces are identical. 505 Return truth value to the solids are the same in this regard. 506 507 :param solid1: FreeCAD solid object 508 :param solid2: FreeCAD solid object 509 :return: bool 510 """ 511 solid_faces_found = [] 512 for face in solid2.Faces: 513 for cface in solid1.Faces: 514 if faces_same_center_of_masses(cface, face) and faces_have_same_vertices(cface, face): 515 solid_faces_found.append(1) 516 return len(solid_faces_found) == len(solid2.Faces) and len(solid_faces_found) == len(solid1.Faces) 517 518def create_boolean_compound(solid_objects, doc): 519 """ 520 Creates a FreeCAD boolean compound for the list of FreeCAD solid objects. 521 This is needed when mesh is computed for the whole geometry. Note that 522 there is also a create_mesh_object_and_compound_filter for meshing purpose. 523 524 :param solid_objects: list of FreeCAD solid geometry objects. 525 :param doc: FreeCAD document. 526 :return: FreeCAD compound object. 527 """ 528 doc.recompute() 529 comp_obj = BOPTools.SplitFeatures.makeBooleanFragments(name='Compsolid') 530 comp_obj.Objects = solid_objects 531 comp_obj.Mode = "CompSolid" 532 comp_obj.Proxy.execute(comp_obj) 533 comp_obj.purgeTouched() 534 return comp_obj 535 536def create_compound(solid_objects, doc, name='Compsolid'): 537 """ 538 Creates a FreeCAD compound for the list of FreeCAD solid objects. 539 540 :param solid_objects: list of FreeCAD solid geometry objects. 541 :param doc: FreeCAD document. 542 :param name: String. 543 :return: FreeCAD compound object. 544 """ 545 compound = doc.addObject('Part::Compound', name) 546 compound.Links = solid_objects 547 doc.recompute() 548 return compound 549 550def create_xor_object(solid_objects, doc): 551 """ 552 Creates a FreeCAD xor object for the list of FreeCAD solid objects. 553 554 :param solid_objects: list of FreeCAD solid geometry objects. 555 :param doc: FreeCAD document. 556 :return: FreeCAD xor object 557 """ 558 doc.recompute() 559 xor_object = BOPTools.SplitFeatures.makeXOR(name='XOR') 560 xor_object.Objects = solid_objects 561 xor_object.Proxy.execute(xor_object) 562 xor_object.purgeTouched() 563 return xor_object 564 565def create_compound_filter(compsolid): 566 """ 567 Create a compound filter. This is needed for meshing. Note that 568 there is also a create_mesh_object_and_compound_filter for meshing purpose. 569 570 :param compsolid: FreeCAD compound object for example from create_boolean_compound 571 :return: FreeCAD compound filter object 572 """ 573 import CompoundTools.CompoundFilter 574 compound_filter = CompoundTools.CompoundFilter.makeCompoundFilter(name='CompoundFilter') 575 compound_filter.Base = compsolid 576 compound_filter.FilterType = 'window-volume' #??? 577 compound_filter.Proxy.execute(compound_filter) #??? 578 return compound_filter 579 580def create_mesh_object(compound_filter, CharacteristicLength, doc, algorithm2d='Automatic', algorithm3d='New Delaunay'): 581 """ 582 Creates a mesh object that controls the mesh definitions. 583 584 :param compound_filter: FreeCAD compound filter object 585 :param CharacteristicLength: Default mesh size characteristic length 586 :param doc: FreeCAD document. 587 :param algorithm2d: String 'MeshAdapt', 'Automatic', 'Delaunay', 'Frontal', 'BAMG', 'DelQuad'. 588 :param algorithm3d: String 'Delaunay', 'New Delaunay', 'Frontal', 'Frontal Delaunay', 'Frontal Hex', 589 'MMG3D', 'R-tree'. 590 :return: FreeCAD mesh object 591 """ 592 # Make a FEM mesh and mesh groups for material bodies and boundary conditions 593 mesh_object = ObjectsFem.makeMeshGmsh(doc, 'GMSHMeshObject') 594 mesh_object.Part = compound_filter 595 mesh_object.CharacteristicLengthMax = CharacteristicLength 596 mesh_object.Algorithm2D = algorithm2d 597 mesh_object.Algorithm3D = algorithm3d 598 mesh_object.ElementOrder = u"1st" 599 return mesh_object 600 601def set_mesh_group_elements(gmsh_mesh): 602 """ 603 Updates group_elements dictionary in gmsh_mesh. 604 Rewritten gmsh_mesh.get_group_data without finding mesh group elements again 605 606 :param gmsh_mesh: Instance of gmshtools.GmshTools. 607 """ 608 for mg in gmsh_mesh.mesh_obj.MeshGroupList: 609 gmsh_mesh.group_elements[mg.Label] = list(mg.References[0][1]) # tuple to list 610 if gmsh_mesh.group_elements: 611 FreeCAD.Console.PrintMessage(' {}\n'.format(gmsh_mesh.group_elements)) 612 613def _remove_ansi_color_escape_codes(message): 614 """ 615 Replace color code escape codes from message with empty string. 616 617 :param message: A string. 618 619 :return: A string. 620 """ 621 return message.replace('\x1b[1m', '').replace('\x1b[31m', '').replace('\x1b[35m', '').replace('\x1b[0m', '') 622 623def run_gmsh(gmsh_mesh, gmsh_log_file=None): 624 """ 625 Runs gmsh. Writes gmsh output to gmsh_log_file if given. 626 627 :param gmsh_mesh: Instance of gmshtools.GmshTools. 628 :param gmsh_log_file: None or path to gmsh_log. 629 630 :return: Gmsh stderr, None or error string. 631 """ 632 if gmsh_log_file is None: 633 error = gmsh_mesh.run_gmsh_with_geo() 634 else: 635 try: 636 with open(gmsh_log_file, 'w') as f: 637 p = subprocess.Popen([gmsh_mesh.gmsh_bin, '-', gmsh_mesh.temp_file_geo], 638 stdout=f, stderr=subprocess.PIPE, universal_newlines=True) 639 no_value, error = p.communicate() 640 if error: 641 error = _remove_ansi_color_escape_codes(error) 642 f.write(error) 643 f.flush() 644 except Exception: 645 error = 'Error executing gmsh' 646 return error 647 648def create_mesh(mesh_object, directory=False, gmsh_log_file=None, transfinite_param_list=None): 649 """ 650 Create mesh mesh with Gmsh. 651 Value of directory determines location gmsh temporary files:: 652 653 - False: Use current working directory 654 - None: Let GmshTools decide (temp directory) 655 - something else: try to use given value 656 657 :param mesh_object: FreeCAD mesh object 658 :param directory: Gmsh temp file location. 659 :param gmsh_log_file: None or path to gmsh_log. 660 :param transfinite_param_list: None or a list containing dictionaries {'volume': 'name', 661 'surface_list': [s_name, sname2]}. 662 663 :return: None or gmsh error text. 664 """ 665 if directory is False: 666 directory = os.getcwd() 667 gmsh_mesh = femmesh.gmshtools.GmshTools(mesh_object) 668 # error = gmsh_mesh.create_mesh() 669 # update mesh data 670 gmsh_mesh.start_logs() 671 gmsh_mesh.get_dimension() 672 set_mesh_group_elements(gmsh_mesh) # gmsh_mesh.get_group_data 673 gmsh_mesh.get_region_data() 674 gmsh_mesh.get_boundary_layer_data() 675 # create mesh 676 gmsh_mesh.get_tmp_file_paths(param_working_dir=directory) 677 gmsh_mesh.get_gmsh_command() 678 gmsh_mesh.write_gmsh_input_files() 679 if transfinite_param_list: 680 meshutils.add_transfinite_lines_to_geo_file(directory, transfinite_param_list) 681 error = run_gmsh(gmsh_mesh, gmsh_log_file) 682 if error: 683 FreeCAD.Console.PrintError('{}\n'.format(error)) 684 return error 685 gmsh_mesh.read_and_set_new_mesh() 686 687def create_mesh_object_and_compound_filter(solid_objects, CharacteristicLength, doc, separate_boundaries=False, 688 algorithm2d='Automatic', algorithm3d='New Delaunay'): 689 """ 690 Creates FreeCAD mesh and compound filter objects. Uses create_boolean_compound/create_compound and 691 create_compound_filter, create_mesh_object methods. 692 693 :param solid_objects: list of FreeCAD solid geometry objects 694 :param CharacteristicLength: Default mesh size characteristic length 695 :param doc: FreeCAD document. 696 :param separate_boundaries: Boolean (create compound instead of boolean fragment). 697 :param algorithm2d: String 'MeshAdapt', 'Automatic', 'Delaunay', 'Frontal', 'BAMG', 'DelQuad'. 698 :param algorithm3d: String 'Delaunay', 'New Delaunay', 'Frontal', 'Frontal Delaunay', 'Frontal Hex', 699 'MMG3D', 'R-tree'. 700 """ 701 if len(solid_objects) == 1 or separate_boundaries: # boolean compound can not be created with only one solid 702 boolean_compound = create_compound(solid_objects, doc) 703 else: 704 boolean_compound = create_boolean_compound(solid_objects, doc) 705 compound_filter = create_compound_filter(boolean_compound) 706 mesh_object = create_mesh_object(compound_filter, CharacteristicLength, doc, algorithm2d, algorithm3d) 707 return mesh_object, compound_filter 708 709def run_elmergrid(export_path, mesh_object, out_dir=None, log_file=None): 710 """ 711 Run ElmerGrid as an external process if it found in the operating system. 712 713 :param export_path: path where the result is written 714 :param mesh_object: FreeCAD mesh object that is to be exported 715 :param out_dir: directory where to write mesh files (if not given unv file name is used) 716 :param log_file: None or a string. 717 """ 718 # Export to UNV file for Elmer 719 export_objects = [mesh_object] 720 Fem.export(export_objects, export_path) 721 elmerGrid_command = 'ElmerGrid 8 2 ' + export_path + ' -autoclean -names' 722 if out_dir is not None: 723 elmerGrid_command += ' -out ' + out_dir 724 725 FreeCAD.Console.PrintMessage('Running ' + elmerGrid_command + '\n') 726 if log_file is not None: 727 with open(log_file, 'w') as f: 728 p = subprocess.Popen(elmerGrid_command.split(), stdout=f, stderr=subprocess.STDOUT) 729 p.communicate() 730 else: 731 from PySide import QtCore, QtGui 732 try: 733 process = QtCore.QProcess() 734 process.startDetached(elmerGrid_command) 735 except: 736 FreeCAD.Console.PrintError('Error') 737 QtGui.QMessageBox.critical(None, 'Error', 'Error!!', QtGui.QMessageBox.Abort) 738 FreeCAD.Console.PrintMessage('Finished ElmerGrid\n') 739 740def export_unv(export_path, mesh_object): 741 """ 742 Exports UNV file for Elmer. 743 744 :param export_path: string 745 :param mesh_object: Mesh object 746 """ 747 Fem.export([mesh_object], export_path) 748 749def find_compound_filter_edge(compound_filter, edge): 750 """ 751 Find which edge in the compound filter object is the edge as the one given in second argument. 752 Returns the name of the edge in compound filter. 753 754 :param compound_filter: FreeCAD compound filter. 755 :param edge: FreeCAD edge. 756 757 :return: A string. 758 """ 759 for num, c_edge in enumerate(compound_filter.Shape.Edges): 760 if is_same_edge(c_edge, edge): 761 return str(num+1) 762 raise ValueError('Edge not found') 763 764def find_compound_filter_boundary(compound_filter, face): 765 """ 766 Find which face in the compound filter object is the face as the one given in second argument. 767 Returns the name of the face in compound filter. 768 769 :param compound_filter: FreeCAD compound filter 770 :param face: FreeCAD face object 771 :return: string 772 """ 773 faces = compound_filter.Shape.Faces 774 face_found = None 775 for num, cface in enumerate(faces): 776 if faces_have_same_vertices(cface, face): 777 face_found = num 778 if face_found is None: return None 779 string = "Face" + str(face_found+1) 780 return string 781 782def find_compound_filter_solid(compound_filter, solid): 783 """ 784 Find which solid in the compound filter object is the solid as the one given in second argument. 785 Returns the name of the solid in compound filter. 786 787 :param compound_filter: FreeCAD compound filter 788 :param solid: FreeCAD solid object 789 :return: string 790 """ 791 solids = compound_filter.Shape.Solids 792 solid_found = None 793 for num, csolid in enumerate(solids): 794 if solids_are_the_same(csolid, solid): 795 solid_found = num 796 if solid_found is None: return None 797 string = "Solid" + str(solid_found+1) 798 return string 799 800def find_compound_filter_boundaries(compound_filter, face, used_compound_face_names=None): 801 """ 802 Finds all faces in the compound filter object which are inside given face. 803 Returns a tuple containing all names of the faces in compound filter. 804 If list used_compound_face_names is given checks that found face is not already used and 805 face is not already found here (relates to argument 'separate_boundaries' in other functions). 806 807 :param compound_filter: FreeCAD compound filter 808 :param face: FreeCAD face object 809 :param used_compound_face_names: None or a list. 810 :return: tuple 811 """ 812 face_name_list, already_found_cfaces = [], [] 813 for num, cface in enumerate(compound_filter.Shape.Faces): 814 if is_face_in_face(cface, face): 815 f_name = "Face" + str(num+1) 816 if used_compound_face_names is not None: 817 if f_name in used_compound_face_names: 818 continue 819 face_already_found = False 820 for found_cface in already_found_cfaces: 821 if is_face_in_face(cface, found_cface): 822 face_already_found = True 823 break 824 if face_already_found: 825 continue 826 already_found_cfaces.append(cface) 827 face_name_list.append(f_name) 828 if len(face_name_list) == 0: 829 raise ValueError("Faces not found") 830 return tuple(face_name_list) 831 832def find_compound_filter_solids(compound_filter, solid, point_search=True): 833 """ 834 Finds all solids in the compound filter object which are inside given solid. 835 Returns a tuple containing all names of the solids in compound filter. 836 837 :param compound_filter: FreeCAD compound filter 838 :param solid: FreeCAD solid object 839 :param point_search: bool 840 :return: tuple 841 """ 842 solid_name_list = [] 843 for num, csolid in enumerate(compound_filter.Shape.Solids): 844 if is_compound_filter_solid_in_solid(csolid, solid, point_search=point_search): 845 solid_name_list.append("Solid" + str(num+1)) 846 if len(solid_name_list) == 0: 847 raise ValueError("Solids not found") 848 return tuple(solid_name_list) 849 850""" 851There is no topological naming in FreeCAD. Further, the face numbers are changed in 852making boolean compound. Hence, then making the original solids, the important solids 853and faces are named with generating the following entities dictionary: 854 855Entities dict definition 856 example_entities = { 857 'name' : 'This is the name of the example', 858 'faces' : [ 859 {'name':'face1', 860 'geometric object':face1_geom_object, 861 'mesh size':mesh_size}, 862 ..., 863 {'name':'facen', 864 'geometric object':facen_geom_object, 865 'mesh size':mesh_size} 866 ] 867 'solids' : [ 868 {'name':'solid_1', 869 'geometric object':solid1_geom_object, 870 'mesh size':mesh_size_1}, 871 ..., 872 {'name':'solid_n', 873 'geometric object':solidn_geom_object, 874 'mesh size':mesh_size_n} 875 ] 876 'main object': main_geom_object 877 } 878In 'faces' and 'solids' have lists that are so called entity lists. 879Entity is defined as a dict containing the name, geometric object and 880the mesh size. In principle one could dynamically add more keys in this 881entity property dict: 882 883 {'name':'solid_n', 884 'geometric object':solidn_geom_object, 885 'mesh size':mesh_size_n} 886 887main_geom_object is for storing a main geometry. For example usually 888when a single solid is created, it contains many face and one solid. 889it is handy to store the solid under the 'main object' key. 890""" 891def add_entity_in_list(entity_list, name, geom_object, mesh_sizes=None): 892 """ 893 Add entity in list of entities. The mesh sizes can be defined by 894 providing the following dictionary: 895 896 mesh_sizes={ 897 entity_name_1:mesh_size_for_entity_name_1, 898 ... 899 entity_name_n:mesh_size_for_entity_name_n, 900 mesh size:mesh size if name is not in the dict 901 } 902 903 :param entity_list: [entity_1, ..., entity_n] 904 :param name: string (name of the entity to be added) 905 :param geom_object: geometric object of the entity 906 :param mesh_sizes: dict 907 """ 908 if mesh_sizes is not None: 909 if name in mesh_sizes: mesh_size = mesh_sizes[name] 910 elif 'mesh size' in mesh_sizes: mesh_size = mesh_sizes['mesh size'] 911 else: mesh_size = None 912 else: 913 mesh_size = None 914 entity_list.append({'name': name, 915 'geometric object': geom_object, 916 'mesh size': mesh_size}) 917 918def add_geom_obj_list_in_entitylist(entity_list, name, geom_obj_list, mesh_sizes=None): 919 """ 920 Adds a list of geometry objects in entitylist using add_entity_in_list(entity_list, name, geom_object, mesh_sizes=None) 921 """ 922 for geom_object in geom_obj_list: 923 add_entity_in_list(entity_list, name, geom_object, mesh_sizes) 924 925def add_symmetry_plane_faces_in_entity_list(entity_list, geom_object, plane, mesh_sizes=None): 926 """ 927 Adds symmetry plane faces using add_geom_obj_list_in_entitylist(entity_list, name, geom_obj_list, mesh_sizes=None) 928 """ 929 faces_in_symmetry_plane = faces_with_vertices_in_symmetry_plane(geom_object.Shape.Faces, plane) 930 add_geom_obj_list_in_entitylist(entity_list, plane, faces_in_symmetry_plane) 931 932def get_entitylist_faces(entity_list): 933 """ 934 Collects geometric objects from dictionaries in entity_list. 935 936 :param entity_list: list 937 938 :return: list 939 """ 940 faces = [] 941 for entity in entity_list: 942 faces.append(entity['geometric object']) 943 return faces 944 945def create_entities_dict(name, face_entity_list, solid_entity_list, main_object=None, params=None): 946 """ 947 Helper method for creating an entities dictionary. 948 949 :param name: name of the collection of entities 950 :param face_entity_list: [face_entity_1, ..., face_entity_n] 951 :param solid_entity_list: [solid_entity_1, ..., solid_entity_n] 952 :param main_object: main object (usually a solid when the entity only has one) 953 :param params: None or a dictionary added to entities_dict: 954 955 :return: entities_dict 956 """ 957 entities_dict = { 958 'name': name, 959 'faces': face_entity_list, 960 'solids': solid_entity_list, 961 'main object': main_object 962 } 963 if params: 964 entities_dict.update(params) 965 return entities_dict 966 967def pick_faces_from_geometry(geom_object, face_picks, mesh_sizes=None): 968 """ 969 Helper function for picking faces from a geometry object. 970 The mesh sizes can be defined by providing the following dictionary:: 971 972 mesh_sizes={ 973 entity_name_1:mesh_size_for_entity_name_1, 974 ... 975 entity_name_n:mesh_size_for_entity_name_n, 976 mesh size:mesh size if name is not in the dict 977 } 978 979 :param geom_object: FreeCAD geometric object where the faces are picked 980 :param face_picks: tuple('name of the face', int(face_number)) 981 :param mesh_sizes: dict 982 983 :return: list 984 """ 985 faces = [] 986 face_objects = geom_object.Shape.Faces 987 for face_pick in face_picks: 988 face_name = face_pick[0] 989 face_number = face_pick[1] 990 add_entity_in_list(faces, face_name, face_objects[face_number], mesh_sizes) 991 return faces 992 993def create_transfinite_mesh_param_dict(volume_name, surface_name_list, direction_dict=None, line_params=None): 994 """ 995 Creates transfinite mesh parameter dictionary e.g.:: 996 997 {'transfinite_mesh_params': {'volume': 'A1', 998 'surface_list': ['A1_alpha0', 'A1_alpha1']} 999 } 1000 1001 :param volume_name: List containing volume names. 1002 :param surface_name_list: List containing surface names. 1003 :param direction_dict: None or a dictionary e.g. {'A1_alpha0': 'Left'} (added to geo file). 1004 :param line_params: None or a list containing dictionaries (see function create_transfinite_line_param_dict). 1005 1006 :return: Dictionary. 1007 """ 1008 mesh_params = {'volume': volume_name, 1009 'surface_list': surface_name_list} 1010 if direction_dict: 1011 mesh_params.update(direction_dict) 1012 if line_params: 1013 mesh_params['line_params'] = line_params 1014 return {'transfinite_mesh_params': mesh_params} 1015 1016def create_transfinite_line_param_dict(edge_list, nof_points, progression=1, comment=''): 1017 """ 1018 Creates dictionary containing transfinite line parameters. 1019 1020 :param edge_list: List containing FreeCAD edge objects. 1021 :param nof_points: Integer. 1022 :param progression: Number. 1023 :param comment: String (commented in geo file). 1024 1025 :return: Dictionary. 1026 """ 1027 return {'edges': edge_list, 'points': str(nof_points), 'progression': str(progression), 'comment': comment} 1028 1029def merge_entities_dicts(entities_dicts, name, default_mesh_size=None, add_prefixes=None): 1030 """ 1031 This method merges all the entities_dicts and optionally prefixes the entity names with the 1032 name of the entity. As default the solids are not prefixed but the faces are. 1033 1034 :param entities_dicts: [entities_dict_1, ..., entities_dict_n] 1035 :param name: string 1036 :param default_mesh_size: float 1037 :param add_prefixes: {'solids':bool, 'faces':bool} 1038 """ 1039 if add_prefixes is None: 1040 add_prefixes = {'solids': False, 'faces': True} 1041 entities_out = {'name': name} 1042 faces = [] 1043 solids = [] 1044 transfinite_mesh_params = [] 1045 for d in entities_dicts: 1046 for face in d['faces']: 1047 if face['mesh size'] is None: face['mesh size'] = default_mesh_size 1048 if add_prefixes['faces']: face_name = d['name'] + '_' + face['name'] 1049 else: face_name = face['name'] 1050 add_entity_in_list(faces, face_name, face['geometric object'], {'mesh size':face['mesh size']}) 1051 for solid in d['solids']: 1052 if add_prefixes['solids']: solid_name = d['name'] + '_' + solid['name'] 1053 else: solid_name = solid['name'] 1054 if solid['mesh size'] is None: solid['mesh size'] = default_mesh_size 1055 add_entity_in_list(solids, solid_name, solid['geometric object'], {'mesh size':solid['mesh size']}) 1056 if d.get('transfinite_mesh_params', {}): 1057 transfinite_mesh_params.append(d['transfinite_mesh_params']) 1058 entities_out['faces'] = faces 1059 entities_out['solids'] = solids 1060 entities_out['transfinite_mesh_params'] = transfinite_mesh_params 1061 return entities_out 1062 1063def get_solids_from_entities_dict(entities_dict): 1064 """ 1065 Return a list of solids from entities dictionary. 1066 1067 :param entities_dict: entities dictionary 1068 :return: [solid object 1, ..., solid object n] 1069 """ 1070 solid_objects = [solid_dict_list['geometric object'] for solid_dict_list in entities_dict['solids']] 1071 return solid_objects 1072 1073def create_mesh_group_and_set_mesh_size(mesh_object, doc, name, mesh_size): 1074 """ 1075 Creates mesh group with function ObjectsFem.makeMeshGroup. 1076 Adds property 'mesh_size' to created group and returns object. 1077 1078 :param mesh_object: FreeCAD mesh object 1079 :param doc: FreeCAD document. 1080 :param name: string 1081 :param mesh_size: float 1082 :return: MeshGroup object 1083 """ 1084 # The third argument of makeMeshGroup is True, as we want to use labels, 1085 # not the names which cannot be changed. 1086 # 1087 # WARNING: No other object should have same label than this Mesh Group, 1088 # otherwise FreeCAD adds numbers to the end of the label to make it unique 1089 obj = ObjectsFem.makeMeshGroup(doc, mesh_object, True, name+'_group') 1090 obj.Label = name 1091 obj.addProperty('App::PropertyFloat', 'mesh_size') 1092 obj.mesh_size = mesh_size 1093 return obj 1094 1095def find_lines_to_transfinite_mesh_params(compound_filter, entities_dict): 1096 """ 1097 Find edge names from compound_filter and adds them to transfinite mesh parameters. 1098 1099 Example of list entities_dict['transfinite_mesh_params'] after this function:: 1100 1101 [{'volume': 'A1', 1102 'surface_list': ['A1_alpha0', 'A1_alpha1'] 1103 'line_params': [{'edges': [edgeObject1, edgeObject2], 1104 'lines': ['1', '2'], # this function adds these 1105 'points': '11', 1106 'progression': '1', 1107 'comment': ''}] 1108 }] 1109 1110 :param compound_filter: FreeCAD compound filter. 1111 :param entities_dict: A dictionary containing transfinite_mesh_params. 1112 """ 1113 for mesh_param_dict in entities_dict['transfinite_mesh_params']: 1114 for line_param_dict in mesh_param_dict.get('line_params', []): 1115 line_ids = [] 1116 for edge in line_param_dict['edges']: 1117 line_ids.append(find_compound_filter_edge(compound_filter, edge)) 1118 line_param_dict['lines'] = line_ids 1119 1120def merge_boundaries(mesh_object, compound_filter, doc, face_entity_dict, compound_face_names, face_name_list, 1121 surface_objs, surface_objs_by_compound_face_names, surface_object=None): 1122 """ 1123 If face in compound_faces is already added to surface: 1124 - renames surface if there was only one face in existing 1125 - removes face from existing surface and creates a new surface for merged face 1126 Creates new surface object (MeshGroup) for compound_faces if needed and surface_object is not given. 1127 1128 :param mesh_object: FreeCAD mesh object 1129 :param compound_filter: FreeCAD compound filter 1130 :param doc: FreeCAD document. 1131 :param face_entity_dict: dictionary 1132 :param compound_face_names: tuple containing compound face names in face 1133 :param face_name_list: list containing already handled face names 1134 :param surface_objs: list containing created surface objects same order as in face_name_list 1135 :param surface_objs_by_compound_face_names: dictionary (for checking if face needs to be merged) 1136 :param surface_object: None or already created surface object 1137 :return: tuple containing surface object and tuple containing filtered compound names 1138 """ 1139 filtered_compound_faces = [] 1140 for cface_name in compound_face_names: 1141 if cface_name in surface_objs_by_compound_face_names: 1142 surf_obj = surface_objs_by_compound_face_names[cface_name] 1143 old_face_name = surf_obj.Label 1144 new_face_name = '{}_{}'.format(old_face_name, face_entity_dict['name']) 1145 1146 old_found_cface_names = surf_obj.References[0][1] 1147 filtered_old_found_cface_names = [cfname_i for cfname_i in old_found_cface_names if cfname_i != cface_name] 1148 if len(filtered_old_found_cface_names) == 0: 1149 # existing mesh object with new label 1150 surf_obj.Label = new_face_name 1151 # update face name in face_name_list 1152 index_found = face_name_list.index(old_face_name) 1153 face_name_list[index_found] = new_face_name 1154 else: 1155 # update references for existing mesh group 1156 surf_obj.References = [(compound_filter, tuple(filtered_old_found_cface_names))] 1157 # handle merged boundary 1158 if new_face_name in face_name_list: 1159 # add merged boundary to existing mesh group 1160 surface_index = face_name_list.index(new_face_name) 1161 found_cface_names = surface_objs[surface_index].References[0][1] 1162 surface_objs[surface_index].References = [(compound_filter, found_cface_names+tuple([cface_name]))] 1163 else: 1164 # create new mesh group for merged boundary 1165 surface_objs.append(create_mesh_group_and_set_mesh_size(mesh_object, doc, new_face_name, 1166 face_entity_dict['mesh size'])) 1167 surface_objs[-1].References = [(compound_filter, tuple([cface_name]))] 1168 face_name_list.append(new_face_name) 1169 surface_objs_by_compound_face_names[cface_name] = surface_objs[-1] 1170 else: 1171 filtered_compound_faces.append(cface_name) 1172 # create new mesh group only once if needed 1173 if surface_object is None: 1174 surface_object = create_mesh_group_and_set_mesh_size(mesh_object, doc, face_entity_dict['name'], 1175 face_entity_dict['mesh size']) 1176 surface_objs_by_compound_face_names[cface_name] = surface_object 1177 1178 return surface_object, tuple(filtered_compound_faces) 1179 1180def find_boundaries_with_entities_dict(mesh_object, compound_filter, entities_dict, doc, separate_boundaries=False): 1181 """ 1182 For all faces in entities_dict, the same face in compound filter is added to a Mesh Group. 1183 All faces with same name in entities_dict are merged into one Mesh Group with the original name. 1184 If separate_boundaries is True calls function :meth:`find_compound_filter_boundaries` 1185 with used_compound_face_names list. 1186 1187 :param mesh_object: FreeCAD mesh object 1188 :param compound_filter: FreeCAD compound filter 1189 :param entities_dict: entities dictionary 1190 :param doc: FreeCAD document. 1191 :param separate_boundaries: Boolean. 1192 :return: list containing MeshGroup objects with mesh size. 1193 """ 1194 surface_objs = [] 1195 face_name_list = [] 1196 all_found_cface_names = [] # needed only for separate boundaries 1197 surface_objs_by_cface_names = {} 1198 for num, face in enumerate(entities_dict['faces']): 1199 if face['name'] in face_name_list: 1200 # Old name, do not create new MeshGroup 1201 index_found = face_name_list.index(face['name']) 1202 found_cface_names = surface_objs[index_found].References[0][1] 1203 if separate_boundaries: 1204 cface_names = find_compound_filter_boundaries(compound_filter, face['geometric object'], 1205 used_compound_face_names=all_found_cface_names) 1206 all_found_cface_names.extend(cface_names) 1207 else: 1208 cface_names = find_compound_filter_boundaries(compound_filter, face['geometric object']) 1209 surface_obj, filtered_cface_names = merge_boundaries(mesh_object, compound_filter, doc, face, 1210 cface_names, face_name_list, surface_objs, 1211 surface_objs_by_cface_names, 1212 surface_object=surface_objs[index_found]) 1213 if len(filtered_cface_names) > 0: 1214 surface_obj.References = [(compound_filter, found_cface_names+filtered_cface_names)] 1215 else: 1216 # New name, create new MeshGroup 1217 if separate_boundaries: 1218 cface_names = find_compound_filter_boundaries(compound_filter, face['geometric object'], 1219 used_compound_face_names=all_found_cface_names) 1220 all_found_cface_names.extend(cface_names) 1221 else: 1222 cface_names = find_compound_filter_boundaries(compound_filter, face['geometric object']) 1223 if all_found_cface_names is not None: 1224 all_found_cface_names.extend(cface_names) 1225 surface_obj, filtered_cface_names = merge_boundaries(mesh_object, compound_filter, doc, face, 1226 cface_names, face_name_list, surface_objs, 1227 surface_objs_by_cface_names, surface_object=None) 1228 if len(filtered_cface_names) > 0: # new surface_obj is already created 1229 surface_obj.References = [(compound_filter, filtered_cface_names)] 1230 surface_objs.append(surface_obj) 1231 face_name_list.append(face['name']) 1232 return surface_objs 1233 1234def find_bodies_with_entities_dict(mesh_object, compound_filter, entities_dict, doc, point_search=True): 1235 """ 1236 For all solids in entities_dict, the same solid in compound filter is added to a Mesh Group. 1237 All solids with same name in entities_dict are merged into one Mesh Group with the original name. 1238 1239 :param mesh_object: FreeCAD mesh object 1240 :param compound_filter: FreeCAD compound filter 1241 :param entities_dict: entities dictionary 1242 :param doc: FreeCAD document. 1243 :param point_search: bool 1244 :return: list containing MeshGroup objects with mesh size. 1245 """ 1246 solid_objs = [] 1247 solid_name_list = [] 1248 for num, solid in enumerate(entities_dict['solids']): 1249 if solid['name'] in solid_name_list: 1250 # Old name, do not create new MeshGroup 1251 index_found = solid_name_list.index(solid['name']) 1252 found_csolid_names = solid_objs[index_found].References[0][1] 1253 csolid_names = find_compound_filter_solids(compound_filter, solid['geometric object'].Shape, point_search) 1254 found_csolid_names = found_csolid_names + csolid_names 1255 solid_objs[index_found].References = [(compound_filter, found_csolid_names)] 1256 else: 1257 # New name, create new MeshGroup 1258 solid_objs.append(create_mesh_group_and_set_mesh_size(mesh_object, doc, solid['name'], solid['mesh size'])) 1259 csolid_names = find_compound_filter_solids(compound_filter, solid['geometric object'].Shape, point_search) 1260 solid_objs[-1].References = [(compound_filter, csolid_names)] 1261 solid_name_list.append(solid['name']) 1262 return solid_objs 1263 1264def define_mesh_sizes_with_mesh_groups(mesh_object, mesh_group_list, doc, ignore_list=None): 1265 """ 1266 Meshregions are needed to have regionwise mesh density parameters. 1267 The mesh element length is the third parameter given in makeMeshRegion. 1268 Each mesh group in mesh_group_list needs to know its 1269 mesh size (created with function :meth:`create_mesh_group_and_set_mesh_size`). 1270 1271 :param mesh_object: FreeCAD mesh object 1272 :param mesh_group_list: list containing MeshGroups 1273 :param doc: FreeCAD document. 1274 :param ignore_list: None or list containing solid names which mesh size is not defined. 1275 """ 1276 if ignore_list is None: 1277 ignore_list = [] 1278 for mesh_group in mesh_group_list: 1279 if mesh_group.Label not in ignore_list: 1280 mesh_region = ObjectsFem.makeMeshRegion(doc, mesh_object, mesh_group.mesh_size, mesh_group.Name+'_region') 1281 mesh_region.References = [(mesh_group.References[0][0], mesh_group.References[0][1])] 1282 1283def define_mesh_sizes(mesh_object, compound_filter, entities_dict, doc, point_search=True, ignore_list=None): 1284 """ 1285 Meshregions are needed to have regionwise mesh density parameters. 1286 The mesh element length is the third parameter given in makeMeshRegion. 1287 1288 :param mesh_object: FreeCAD mesh object 1289 :param compound_filter: FreeCAD compound filter 1290 :param entities_dict: entities dictionary 1291 :param doc: FreeCAD document. 1292 :param point_search: bool 1293 :param ignore_list: None or list containing solid names which mesh size is not defined. 1294 """ 1295 if ignore_list is None: 1296 ignore_list = [] 1297 solid_objs = [] 1298 solid_name_list = [] 1299 for num, solid in enumerate(entities_dict['solids']): 1300 if solid['name'] in ignore_list: 1301 continue 1302 if solid['name'] in solid_name_list: 1303 # Old name, do not create new MeshGroup 1304 index_found = solid_name_list.index(solid['name']) 1305 found_csolid_names = solid_objs[index_found].References[0][1] 1306 csolid_names = find_compound_filter_solids(compound_filter, solid['geometric object'].Shape, point_search) 1307 solid_objs[index_found].References = [(compound_filter, found_csolid_names+csolid_names)] 1308 else: 1309 # New name, create new MeshGroup 1310 solid_objs.append(ObjectsFem.makeMeshRegion(doc, mesh_object, solid['mesh size'], solid['name']+'_region')) 1311 csolid_names = find_compound_filter_solids(compound_filter, solid['geometric object'].Shape, point_search) 1312 solid_objs[-1].References = [(compound_filter, csolid_names)] 1313 solid_name_list.append(solid['name']) 1314 1315 1316