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