Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ElmerCSC
GitHub Repository: ElmerCSC/elmerfem
Path: blob/devel/ElmerWorkflows/FreeCADBatchFEMTools/FreeCADBatchFEMTools.py
3196 views
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: [email protected], [email protected]
23
Address: Trafotek Oy
24
Kaarinantie 700
25
20540 Turku
26
Finland
27
28
Original Date: May 2018
29
"""
30
from __future__ import print_function
31
import os
32
import Fem
33
import FreeCAD
34
import Part
35
import BOPTools.SplitFeatures
36
import ObjectsFem
37
import femmesh.gmshtools
38
import math
39
import itertools
40
import subprocess
41
42
import meshutils
43
44
45
def 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
54
def 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
66
def 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
78
def 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
107
def 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
147
def 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
158
def 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
170
def 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
182
def 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
200
def 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
216
def 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
233
def 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
248
def 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
261
def 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
286
def 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
302
def 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
320
def 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
335
def 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
362
def 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
378
def 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
404
def 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
432
def 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
453
def 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
481
def 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
502
def 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
518
def 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
536
def 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
550
def 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
565
def 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
580
def 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
601
def 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
613
def _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
623
def 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
648
def 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
687
def 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
709
def 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
740
def 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
749
def find_compound_filter_point(compound_filter, point):
750
"""
751
Finds which point in the compound filter object is given point.
752
Returns the name of the point in compound filter.
753
754
:param compound_filter: FreeCAD compound filter.
755
:param point: FreeCAD vector.
756
757
:return: A string.
758
"""
759
for num, c_vertex in enumerate(compound_filter.Shape.Vertexes):
760
if vectors_are_same(c_vertex.Point, point):
761
return str(num+1)
762
raise ValueError('Point not found')
763
764
def find_compound_filter_edge(compound_filter, edge):
765
"""
766
Find which edge in the compound filter object is the edge as the one given in second argument.
767
Returns the name of the edge in compound filter.
768
769
:param compound_filter: FreeCAD compound filter.
770
:param edge: FreeCAD edge.
771
772
:return: A string.
773
"""
774
for num, c_edge in enumerate(compound_filter.Shape.Edges):
775
if is_same_edge(c_edge, edge):
776
return str(num+1)
777
raise ValueError('Edge not found')
778
779
def find_compound_filter_boundary(compound_filter, face):
780
"""
781
Find which face in the compound filter object is the face as the one given in second argument.
782
Returns the name of the face in compound filter.
783
784
:param compound_filter: FreeCAD compound filter
785
:param face: FreeCAD face object
786
:return: string
787
"""
788
faces = compound_filter.Shape.Faces
789
face_found = None
790
for num, cface in enumerate(faces):
791
if faces_have_same_vertices(cface, face):
792
face_found = num
793
if face_found is None: return None
794
string = "Face" + str(face_found+1)
795
return string
796
797
def find_compound_filter_solid(compound_filter, solid):
798
"""
799
Find which solid in the compound filter object is the solid as the one given in second argument.
800
Returns the name of the solid in compound filter.
801
802
:param compound_filter: FreeCAD compound filter
803
:param solid: FreeCAD solid object
804
:return: string
805
"""
806
solids = compound_filter.Shape.Solids
807
solid_found = None
808
for num, csolid in enumerate(solids):
809
if solids_are_the_same(csolid, solid):
810
solid_found = num
811
if solid_found is None: return None
812
string = "Solid" + str(solid_found+1)
813
return string
814
815
def find_compound_filter_boundaries(compound_filter, face, used_compound_face_names=None):
816
"""
817
Finds all faces in the compound filter object which are inside given face.
818
Returns a tuple containing all names of the faces in compound filter.
819
If list used_compound_face_names is given checks that found face is not already used and
820
face is not already found here (relates to argument 'separate_boundaries' in other functions).
821
822
:param compound_filter: FreeCAD compound filter
823
:param face: FreeCAD face object
824
:param used_compound_face_names: None or a list.
825
:return: tuple
826
"""
827
face_name_list, already_found_cfaces = [], []
828
for num, cface in enumerate(compound_filter.Shape.Faces):
829
if is_face_in_face(cface, face):
830
f_name = "Face" + str(num+1)
831
if used_compound_face_names is not None:
832
if f_name in used_compound_face_names:
833
continue
834
face_already_found = False
835
for found_cface in already_found_cfaces:
836
if is_face_in_face(cface, found_cface):
837
face_already_found = True
838
break
839
if face_already_found:
840
continue
841
already_found_cfaces.append(cface)
842
face_name_list.append(f_name)
843
if len(face_name_list) == 0:
844
raise ValueError("Faces not found")
845
return tuple(face_name_list)
846
847
def find_compound_filter_solids(compound_filter, solid, point_search=True):
848
"""
849
Finds all solids in the compound filter object which are inside given solid.
850
Returns a tuple containing all names of the solids in compound filter.
851
852
:param compound_filter: FreeCAD compound filter
853
:param solid: FreeCAD solid object
854
:param point_search: bool
855
:return: tuple
856
"""
857
solid_name_list = []
858
for num, csolid in enumerate(compound_filter.Shape.Solids):
859
if is_compound_filter_solid_in_solid(csolid, solid, point_search=point_search):
860
solid_name_list.append("Solid" + str(num+1))
861
if len(solid_name_list) == 0:
862
raise ValueError("Solids not found")
863
return tuple(solid_name_list)
864
865
"""
866
There is no topological naming in FreeCAD. Further, the face numbers are changed in
867
making boolean compound. Hence, then making the original solids, the important solids
868
and faces are named with generating the following entities dictionary:
869
870
Entities dict definition
871
example_entities = {
872
'name' : 'This is the name of the example',
873
'faces' : [
874
{'name':'face1',
875
'geometric object':face1_geom_object,
876
'mesh size':mesh_size},
877
...,
878
{'name':'facen',
879
'geometric object':facen_geom_object,
880
'mesh size':mesh_size}
881
]
882
'solids' : [
883
{'name':'solid_1',
884
'geometric object':solid1_geom_object,
885
'mesh size':mesh_size_1},
886
...,
887
{'name':'solid_n',
888
'geometric object':solidn_geom_object,
889
'mesh size':mesh_size_n}
890
]
891
'main object': main_geom_object
892
}
893
In 'faces' and 'solids' have lists that are so called entity lists.
894
Entity is defined as a dict containing the name, geometric object and
895
the mesh size. In principle one could dynamically add more keys in this
896
entity property dict:
897
898
{'name':'solid_n',
899
'geometric object':solidn_geom_object,
900
'mesh size':mesh_size_n}
901
902
main_geom_object is for storing a main geometry. For example usually
903
when a single solid is created, it contains many face and one solid.
904
it is handy to store the solid under the 'main object' key.
905
"""
906
def add_entity_in_list(entity_list, name, geom_object, mesh_sizes=None):
907
"""
908
Add entity in list of entities. The mesh sizes can be defined by
909
providing the following dictionary:
910
911
mesh_sizes={
912
entity_name_1:mesh_size_for_entity_name_1,
913
...
914
entity_name_n:mesh_size_for_entity_name_n,
915
mesh size:mesh size if name is not in the dict
916
}
917
918
:param entity_list: [entity_1, ..., entity_n]
919
:param name: string (name of the entity to be added)
920
:param geom_object: geometric object of the entity
921
:param mesh_sizes: dict
922
"""
923
if mesh_sizes is not None:
924
if name in mesh_sizes: mesh_size = mesh_sizes[name]
925
elif 'mesh size' in mesh_sizes: mesh_size = mesh_sizes['mesh size']
926
else: mesh_size = None
927
else:
928
mesh_size = None
929
entity_list.append({'name': name,
930
'geometric object': geom_object,
931
'mesh size': mesh_size})
932
933
def add_geom_obj_list_in_entitylist(entity_list, name, geom_obj_list, mesh_sizes=None):
934
"""
935
Adds a list of geometry objects in entitylist using add_entity_in_list(entity_list, name, geom_object, mesh_sizes=None)
936
"""
937
for geom_object in geom_obj_list:
938
add_entity_in_list(entity_list, name, geom_object, mesh_sizes)
939
940
def add_symmetry_plane_faces_in_entity_list(entity_list, geom_object, plane, mesh_sizes=None):
941
"""
942
Adds symmetry plane faces using add_geom_obj_list_in_entitylist(entity_list, name, geom_obj_list, mesh_sizes=None)
943
"""
944
faces_in_symmetry_plane = faces_with_vertices_in_symmetry_plane(geom_object.Shape.Faces, plane)
945
add_geom_obj_list_in_entitylist(entity_list, plane, faces_in_symmetry_plane)
946
947
def get_entitylist_faces(entity_list):
948
"""
949
Collects geometric objects from dictionaries in entity_list.
950
951
:param entity_list: list
952
953
:return: list
954
"""
955
faces = []
956
for entity in entity_list:
957
faces.append(entity['geometric object'])
958
return faces
959
960
def create_entities_dict(name, face_entity_list, solid_entity_list, main_object=None, params=None):
961
"""
962
Helper method for creating an entities dictionary.
963
964
:param name: name of the collection of entities
965
:param face_entity_list: [face_entity_1, ..., face_entity_n]
966
:param solid_entity_list: [solid_entity_1, ..., solid_entity_n]
967
:param main_object: main object (usually a solid when the entity only has one)
968
:param params: None or a dictionary added to entities_dict:
969
970
:return: entities_dict
971
"""
972
entities_dict = {
973
'name': name,
974
'faces': face_entity_list,
975
'solids': solid_entity_list,
976
'main object': main_object
977
}
978
if params:
979
entities_dict.update(params)
980
return entities_dict
981
982
def pick_faces_from_geometry(geom_object, face_picks, mesh_sizes=None):
983
"""
984
Helper function for picking faces from a geometry object.
985
The mesh sizes can be defined by providing the following dictionary::
986
987
mesh_sizes={
988
entity_name_1:mesh_size_for_entity_name_1,
989
...
990
entity_name_n:mesh_size_for_entity_name_n,
991
mesh size:mesh size if name is not in the dict
992
}
993
994
:param geom_object: FreeCAD geometric object where the faces are picked
995
:param face_picks: tuple('name of the face', int(face_number))
996
:param mesh_sizes: dict
997
998
:return: list
999
"""
1000
faces = []
1001
face_objects = geom_object.Shape.Faces
1002
for face_pick in face_picks:
1003
face_name = face_pick[0]
1004
face_number = face_pick[1]
1005
add_entity_in_list(faces, face_name, face_objects[face_number], mesh_sizes)
1006
return faces
1007
1008
def create_transfinite_mesh_param_dict(volume_name, surface_name_list, direction_dict=None, line_params=None,
1009
volume_corner_vectors=None):
1010
"""
1011
Creates transfinite mesh parameter dictionary e.g.::
1012
1013
{'transfinite_mesh_params': {'volume': 'A1',
1014
'surface_list': ['A1_alpha0', 'A1_alpha1']}
1015
}
1016
1017
:param volume_name: List containing volume names.
1018
:param surface_name_list: List containing surface names.
1019
:param direction_dict: None or a dictionary e.g. {'A1_alpha0_direction': 'Left'} (added to geo file).
1020
:param line_params: None or a list containing dictionaries (see function create_transfinite_line_param_dict).
1021
:param volume_corner_vectors: None or a list containing volume corner points (for transfinite volume definition)
1022
1023
:return: Dictionary.
1024
"""
1025
mesh_params = {'volume': volume_name,
1026
'surface_list': surface_name_list}
1027
if direction_dict:
1028
mesh_params.update(direction_dict)
1029
if line_params:
1030
mesh_params['line_params'] = line_params
1031
if volume_corner_vectors:
1032
mesh_params['volume_corner_vectors'] = volume_corner_vectors
1033
return {'transfinite_mesh_params': mesh_params}
1034
1035
def create_transfinite_line_param_dict(edge_list, nof_points, progression=1, comment='', mesh_type='Progression'):
1036
"""
1037
Creates dictionary containing transfinite line parameters.
1038
1039
:param edge_list: List containing FreeCAD edge objects.
1040
:param nof_points: Integer.
1041
:param progression: Number (mesh_type coefficient).
1042
:param comment: String (commented in geo file).
1043
:param mesh_type: String.
1044
1045
:return: Dictionary.
1046
"""
1047
return {'edges': edge_list, 'points': str(nof_points), 'progression': str(progression),
1048
'mesh_type': mesh_type, 'comment': comment}
1049
1050
def merge_entities_dicts(entities_dicts, name, default_mesh_size=None, add_prefixes=None):
1051
"""
1052
This method merges all the entities_dicts and optionally prefixes the entity names with the
1053
name of the entity. As default the solids are not prefixed but the faces are.
1054
1055
:param entities_dicts: [entities_dict_1, ..., entities_dict_n]
1056
:param name: string
1057
:param default_mesh_size: float
1058
:param add_prefixes: {'solids':bool, 'faces':bool}
1059
"""
1060
if add_prefixes is None:
1061
add_prefixes = {'solids': False, 'faces': True}
1062
entities_out = {'name': name}
1063
faces = []
1064
solids = []
1065
transfinite_mesh_params = []
1066
for d in entities_dicts:
1067
for face in d['faces']:
1068
if face['mesh size'] is None: face['mesh size'] = default_mesh_size
1069
if add_prefixes['faces']: face_name = d['name'] + '_' + face['name']
1070
else: face_name = face['name']
1071
add_entity_in_list(faces, face_name, face['geometric object'], {'mesh size':face['mesh size']})
1072
for solid in d['solids']:
1073
if add_prefixes['solids']: solid_name = d['name'] + '_' + solid['name']
1074
else: solid_name = solid['name']
1075
if solid['mesh size'] is None: solid['mesh size'] = default_mesh_size
1076
add_entity_in_list(solids, solid_name, solid['geometric object'], {'mesh size':solid['mesh size']})
1077
if d.get('transfinite_mesh_params', {}):
1078
transfinite_mesh_params.append(d['transfinite_mesh_params'])
1079
entities_out['faces'] = faces
1080
entities_out['solids'] = solids
1081
entities_out['transfinite_mesh_params'] = transfinite_mesh_params
1082
return entities_out
1083
1084
def get_solids_from_entities_dict(entities_dict):
1085
"""
1086
Return a list of solids from entities dictionary.
1087
1088
:param entities_dict: entities dictionary
1089
:return: [solid object 1, ..., solid object n]
1090
"""
1091
solid_objects = [solid_dict_list['geometric object'] for solid_dict_list in entities_dict['solids']]
1092
return solid_objects
1093
1094
def create_mesh_group_and_set_mesh_size(mesh_object, doc, name, mesh_size):
1095
"""
1096
Creates mesh group with function ObjectsFem.makeMeshGroup.
1097
Adds property 'mesh_size' to created group and returns object.
1098
1099
:param mesh_object: FreeCAD mesh object
1100
:param doc: FreeCAD document.
1101
:param name: string
1102
:param mesh_size: float
1103
:return: MeshGroup object
1104
"""
1105
# The third argument of makeMeshGroup is True, as we want to use labels,
1106
# not the names which cannot be changed.
1107
#
1108
# WARNING: No other object should have same label than this Mesh Group,
1109
# otherwise FreeCAD adds numbers to the end of the label to make it unique
1110
obj = ObjectsFem.makeMeshGroup(doc, mesh_object, True, name+'_group')
1111
obj.Label = name
1112
obj.addProperty('App::PropertyFloat', 'mesh_size')
1113
obj.mesh_size = mesh_size
1114
return obj
1115
1116
def find_lines_to_transfinite_mesh_params(compound_filter, entities_dict):
1117
"""
1118
Find edge names from compound_filter and adds them to transfinite mesh parameters.
1119
1120
Example of list entities_dict['transfinite_mesh_params'] after this function::
1121
1122
[{'volume': 'A1',
1123
'surface_list': ['A1_alpha0', 'A1_alpha1']
1124
'line_params': [{'edges': [edgeObject1, edgeObject2],
1125
'lines': ['1', '2'], # this function adds these
1126
'points': '11',
1127
'progression': '1',
1128
'comment': ''}]
1129
}]
1130
1131
:param compound_filter: FreeCAD compound filter.
1132
:param entities_dict: A dictionary containing transfinite_mesh_params.
1133
"""
1134
for mesh_param_dict in entities_dict['transfinite_mesh_params']:
1135
for line_param_dict in mesh_param_dict.get('line_params', []):
1136
line_ids = []
1137
for edge in line_param_dict['edges']:
1138
line_ids.append(find_compound_filter_edge(compound_filter, edge))
1139
line_param_dict['lines'] = line_ids
1140
if mesh_param_dict.get('volume_corner_vectors', []):
1141
volume_corner_points = []
1142
# if used corner points needs already be in correct order
1143
for corner_vector in mesh_param_dict['volume_corner_vectors']:
1144
volume_corner_points.append(find_compound_filter_point(compound_filter, corner_vector))
1145
mesh_param_dict['volume_corner_points'] = volume_corner_points
1146
1147
def merge_boundaries(mesh_object, compound_filter, doc, face_entity_dict, compound_face_names, face_name_list,
1148
surface_objs, surface_objs_by_compound_face_names, surface_object=None):
1149
"""
1150
If face in compound_faces is already added to surface:
1151
- renames surface if there was only one face in existing
1152
- removes face from existing surface and creates a new surface for merged face
1153
Creates new surface object (MeshGroup) for compound_faces if needed and surface_object is not given.
1154
1155
:param mesh_object: FreeCAD mesh object
1156
:param compound_filter: FreeCAD compound filter
1157
:param doc: FreeCAD document.
1158
:param face_entity_dict: dictionary
1159
:param compound_face_names: tuple containing compound face names in face
1160
:param face_name_list: list containing already handled face names
1161
:param surface_objs: list containing created surface objects same order as in face_name_list
1162
:param surface_objs_by_compound_face_names: dictionary (for checking if face needs to be merged)
1163
:param surface_object: None or already created surface object
1164
:return: tuple containing surface object and tuple containing filtered compound names
1165
"""
1166
filtered_compound_faces = []
1167
for cface_name in compound_face_names:
1168
if cface_name in surface_objs_by_compound_face_names:
1169
surf_obj = surface_objs_by_compound_face_names[cface_name]
1170
old_face_name = surf_obj.Label
1171
new_face_name = '{}_{}'.format(old_face_name, face_entity_dict['name'])
1172
1173
old_found_cface_names = surf_obj.References[0][1]
1174
filtered_old_found_cface_names = [cfname_i for cfname_i in old_found_cface_names if cfname_i != cface_name]
1175
if len(filtered_old_found_cface_names) == 0:
1176
# existing mesh object with new label
1177
surf_obj.Label = new_face_name
1178
# update face name in face_name_list
1179
index_found = face_name_list.index(old_face_name)
1180
face_name_list[index_found] = new_face_name
1181
else:
1182
# update references for existing mesh group
1183
surf_obj.References = [(compound_filter, tuple(filtered_old_found_cface_names))]
1184
# handle merged boundary
1185
if new_face_name in face_name_list:
1186
# add merged boundary to existing mesh group
1187
surface_index = face_name_list.index(new_face_name)
1188
found_cface_names = surface_objs[surface_index].References[0][1]
1189
surface_objs[surface_index].References = [(compound_filter, found_cface_names+tuple([cface_name]))]
1190
else:
1191
# create new mesh group for merged boundary
1192
surface_objs.append(create_mesh_group_and_set_mesh_size(mesh_object, doc, new_face_name,
1193
face_entity_dict['mesh size']))
1194
surface_objs[-1].References = [(compound_filter, tuple([cface_name]))]
1195
face_name_list.append(new_face_name)
1196
surface_objs_by_compound_face_names[cface_name] = surface_objs[-1]
1197
else:
1198
filtered_compound_faces.append(cface_name)
1199
# create new mesh group only once if needed
1200
if surface_object is None:
1201
surface_object = create_mesh_group_and_set_mesh_size(mesh_object, doc, face_entity_dict['name'],
1202
face_entity_dict['mesh size'])
1203
surface_objs_by_compound_face_names[cface_name] = surface_object
1204
1205
return surface_object, tuple(filtered_compound_faces)
1206
1207
def find_boundaries_with_entities_dict(mesh_object, compound_filter, entities_dict, doc, separate_boundaries=False):
1208
"""
1209
For all faces in entities_dict, the same face in compound filter is added to a Mesh Group.
1210
All faces with same name in entities_dict are merged into one Mesh Group with the original name.
1211
If separate_boundaries is True calls function :meth:`find_compound_filter_boundaries`
1212
with used_compound_face_names list.
1213
1214
:param mesh_object: FreeCAD mesh object
1215
:param compound_filter: FreeCAD compound filter
1216
:param entities_dict: entities dictionary
1217
:param doc: FreeCAD document.
1218
:param separate_boundaries: Boolean.
1219
:return: list containing MeshGroup objects with mesh size.
1220
"""
1221
surface_objs = []
1222
face_name_list = []
1223
all_found_cface_names = [] # needed only for separate boundaries
1224
surface_objs_by_cface_names = {}
1225
for num, face in enumerate(entities_dict['faces']):
1226
if face['name'] in face_name_list:
1227
# Old name, do not create new MeshGroup
1228
index_found = face_name_list.index(face['name'])
1229
found_cface_names = surface_objs[index_found].References[0][1]
1230
if separate_boundaries:
1231
cface_names = find_compound_filter_boundaries(compound_filter, face['geometric object'],
1232
used_compound_face_names=all_found_cface_names)
1233
all_found_cface_names.extend(cface_names)
1234
else:
1235
cface_names = find_compound_filter_boundaries(compound_filter, face['geometric object'])
1236
surface_obj, filtered_cface_names = merge_boundaries(mesh_object, compound_filter, doc, face,
1237
cface_names, face_name_list, surface_objs,
1238
surface_objs_by_cface_names,
1239
surface_object=surface_objs[index_found])
1240
if len(filtered_cface_names) > 0:
1241
surface_obj.References = [(compound_filter, found_cface_names+filtered_cface_names)]
1242
else:
1243
# New name, create new MeshGroup
1244
if separate_boundaries:
1245
cface_names = find_compound_filter_boundaries(compound_filter, face['geometric object'],
1246
used_compound_face_names=all_found_cface_names)
1247
all_found_cface_names.extend(cface_names)
1248
else:
1249
cface_names = find_compound_filter_boundaries(compound_filter, face['geometric object'])
1250
if all_found_cface_names is not None:
1251
all_found_cface_names.extend(cface_names)
1252
surface_obj, filtered_cface_names = merge_boundaries(mesh_object, compound_filter, doc, face,
1253
cface_names, face_name_list, surface_objs,
1254
surface_objs_by_cface_names, surface_object=None)
1255
if len(filtered_cface_names) > 0: # new surface_obj is already created
1256
surface_obj.References = [(compound_filter, filtered_cface_names)]
1257
surface_objs.append(surface_obj)
1258
face_name_list.append(face['name'])
1259
return surface_objs
1260
1261
def find_bodies_with_entities_dict(mesh_object, compound_filter, entities_dict, doc, point_search=True):
1262
"""
1263
For all solids in entities_dict, the same solid in compound filter is added to a Mesh Group.
1264
All solids with same name in entities_dict are merged into one Mesh Group with the original name.
1265
1266
:param mesh_object: FreeCAD mesh object
1267
:param compound_filter: FreeCAD compound filter
1268
:param entities_dict: entities dictionary
1269
:param doc: FreeCAD document.
1270
:param point_search: bool
1271
:return: list containing MeshGroup objects with mesh size.
1272
"""
1273
solid_objs = []
1274
solid_name_list = []
1275
for num, solid in enumerate(entities_dict['solids']):
1276
if solid['name'] in solid_name_list:
1277
# Old name, do not create new MeshGroup
1278
index_found = solid_name_list.index(solid['name'])
1279
found_csolid_names = solid_objs[index_found].References[0][1]
1280
csolid_names = find_compound_filter_solids(compound_filter, solid['geometric object'].Shape, point_search)
1281
found_csolid_names = found_csolid_names + csolid_names
1282
solid_objs[index_found].References = [(compound_filter, found_csolid_names)]
1283
else:
1284
# New name, create new MeshGroup
1285
solid_objs.append(create_mesh_group_and_set_mesh_size(mesh_object, doc, solid['name'], solid['mesh size']))
1286
csolid_names = find_compound_filter_solids(compound_filter, solid['geometric object'].Shape, point_search)
1287
solid_objs[-1].References = [(compound_filter, csolid_names)]
1288
solid_name_list.append(solid['name'])
1289
return solid_objs
1290
1291
def define_mesh_sizes_with_mesh_groups(mesh_object, mesh_group_list, doc, ignore_list=None):
1292
"""
1293
Meshregions are needed to have regionwise mesh density parameters.
1294
The mesh element length is the third parameter given in makeMeshRegion.
1295
Each mesh group in mesh_group_list needs to know its
1296
mesh size (created with function :meth:`create_mesh_group_and_set_mesh_size`).
1297
1298
:param mesh_object: FreeCAD mesh object
1299
:param mesh_group_list: list containing MeshGroups
1300
:param doc: FreeCAD document.
1301
:param ignore_list: None or list containing solid names which mesh size is not defined.
1302
"""
1303
if ignore_list is None:
1304
ignore_list = []
1305
for mesh_group in mesh_group_list:
1306
if mesh_group.Label not in ignore_list:
1307
mesh_region = ObjectsFem.makeMeshRegion(doc, mesh_object, mesh_group.mesh_size, mesh_group.Name+'_region')
1308
mesh_region.References = [(mesh_group.References[0][0], mesh_group.References[0][1])]
1309
1310
def define_mesh_sizes(mesh_object, compound_filter, entities_dict, doc, point_search=True, ignore_list=None):
1311
"""
1312
Meshregions are needed to have regionwise mesh density parameters.
1313
The mesh element length is the third parameter given in makeMeshRegion.
1314
1315
:param mesh_object: FreeCAD mesh object
1316
:param compound_filter: FreeCAD compound filter
1317
:param entities_dict: entities dictionary
1318
:param doc: FreeCAD document.
1319
:param point_search: bool
1320
:param ignore_list: None or list containing solid names which mesh size is not defined.
1321
"""
1322
if ignore_list is None:
1323
ignore_list = []
1324
solid_objs = []
1325
solid_name_list = []
1326
for num, solid in enumerate(entities_dict['solids']):
1327
if solid['name'] in ignore_list:
1328
continue
1329
if solid['name'] in solid_name_list:
1330
# Old name, do not create new MeshGroup
1331
index_found = solid_name_list.index(solid['name'])
1332
found_csolid_names = solid_objs[index_found].References[0][1]
1333
csolid_names = find_compound_filter_solids(compound_filter, solid['geometric object'].Shape, point_search)
1334
solid_objs[index_found].References = [(compound_filter, found_csolid_names+csolid_names)]
1335
else:
1336
# New name, create new MeshGroup
1337
solid_objs.append(ObjectsFem.makeMeshRegion(doc, mesh_object, solid['mesh size'], solid['name']+'_region'))
1338
csolid_names = find_compound_filter_solids(compound_filter, solid['geometric object'].Shape, point_search)
1339
solid_objs[-1].References = [(compound_filter, csolid_names)]
1340
solid_name_list.append(solid['name'])
1341
1342
1343
1344