1doc = App.newDocument('cube geometry')
2import sys
3import os
4import time
5import math
6import FreeCAD
7
8sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
9import FreeCADBatchFEMTools
10
11
12def cut_xy_plane(part, cut_len, cut_width, cut_height):
13    doc.recompute()
14    doc.recompute()
15    reduced_name = part.Name + '_xy'
16    tool_box = doc.addObject("Part::Box", "CutBox" + reduced_name + '_obj')
17    tool_box.Length = cut_len
18    tool_box.Width = cut_width
19    tool_box.Height = cut_height
20    c_x = part.Shape.BoundBox.Center.x
21    c_y = part.Shape.BoundBox.Center.y
22    tool_box.Placement = App.Placement(App.Vector(c_x-cut_len/2., c_y-cut_width/2., -cut_height),
23                                       App.Rotation(App.Vector(0, 0, 1), 0))
24
25    cut_part = doc.addObject("Part::Cut", reduced_name + '_obj')
26    cut_part.Base = part
27    cut_part.Tool = tool_box
28    return cut_part
29
30def print_line(line_str, start_time):
31    """
32    Prints elapsed time and flushes stdout.
33
34    :param line_str: A string.
35    :param start_time: A float.
36    """
37    elapsed_time = round(time.time() - start_time, 1)
38    print('Elapsed time: {} seconds'.format(elapsed_time))
39    print(line_str)
40    sys.stdout.flush()
41    return elapsed_time
42
43def create_air_geometry(entities_list, length, width, height, shift, mesh_size):
44    """
45    Creates air around created parts in part_list.
46
47    :param entities_list: A list containing entities dicts.
48    :param length: A float.
49    :param width: A float.
50    :param height: A float.
51    :param shift: A float.
52    :param mesh_size: A float or None.
53
54    :return: Air part object.
55    """
56    bounding_box_part = doc.addObject("Part::Box", "air_obj")
57    bounding_box_part.Length = length
58    bounding_box_part.Width = width
59    bounding_box_part.Height = height
60    bounding_box_part.Placement = App.Placement(App.Vector(-shift, -shift, 0),
61                                                App.Rotation(App.Vector(0, 0, 1), 0))
62    doc.recompute()
63
64    solid_objects = [bounding_box_part] + [solid['main object'] for solid in entities_list]
65    air_part = FreeCADBatchFEMTools.create_xor_object(solid_objects, doc)
66    doc.recompute()
67    # remove already created faces
68    faces_in_symmetry_plane = FreeCADBatchFEMTools.faces_with_vertices_in_symmetry_plane(air_part.Shape.Faces,
69                                                                                         plane='xy')
70    used_faces = []
71    for part in entities_list:
72        used_faces.extend([face_entity['geometric object'] for face_entity in part['faces']])
73    FreeCADBatchFEMTools.remove_compare_faces_from_list(used_faces, faces_in_symmetry_plane)
74    faces = []
75    for face in faces_in_symmetry_plane:
76        FreeCADBatchFEMTools.add_entity_in_list(faces, 'beta0', face)
77    solids = []
78    FreeCADBatchFEMTools.add_entity_in_list(solids, 'air', air_part, {'mesh size': mesh_size})
79    entities_dict = FreeCADBatchFEMTools.create_entities_dict('air', faces, solids, main_object=air_part)
80    doc.recompute()
81
82    return entities_dict
83
84def create_sphere_geometry(nof_spheres, mesh_size, diameter, distance):
85    """
86    Create sphere geometry. Spheres are moved down 10% and cut from xy plane
87    because caused problems with air otherwise.
88
89    :param nof_spheres: Number of spheres.
90    :param mesh_size: Mesh size of spheres.
91    :param diameter: Diameter of spheres.
92    :param distance: Distance between spheres.
93
94    :return: A list containing entities dictionaries.
95    """
96    sphere_entities_dict_list = []
97    r = diameter/2.0
98    shift = diameter/10.0
99    n = int(math.ceil(math.sqrt(nof_spheres)))
100    bbox_len = n*(diameter+distance)
101    counter = 0
102    for i in range(n):
103        for j in range(n):
104            sphere_name = 'sphere{:04d}'.format(counter + 1)
105            sphere = doc.addObject('Part::Sphere', sphere_name + '_obj')
106            sphere.Radius = r
107            sphere.Placement = App.Placement(App.Vector(r + i*(diameter+distance), r + j*(diameter+distance), r-shift),
108                                             App.Rotation(App.Vector(0, 0, 1), 0))
109            doc.recompute()
110            sphere = cut_xy_plane(sphere, bbox_len, bbox_len, diameter)
111            doc.recompute()
112            # create entities dict
113            face_picks = [('alpha1', 0), ('beta0', 1)]
114            faces = FreeCADBatchFEMTools.pick_faces_from_geometry(sphere, face_picks)
115            solids = []
116            FreeCADBatchFEMTools.add_entity_in_list(solids, sphere_name, sphere, {'mesh size': mesh_size})
117            entities_dict = FreeCADBatchFEMTools.create_entities_dict(sphere_name, faces, solids, sphere)
118            sphere_entities_dict_list.append(entities_dict)
119            doc.recompute()
120            counter += 1
121            if counter == nof_spheres:
122                return sphere_entities_dict_list
123
124def create_cube_geometry(nof_cubes, mesh_size, length, width, height, cube_distance):
125    """
126    Create cube geometry.
127
128    :param nof_cubes: Number of cubes.
129    :param mesh_size: Mesh size of cubes.
130    :param length: Length of cubes.
131    :param width: Width of cubes.
132    :param height: Height of cubes.
133    :param cube_distance: Distance between cubes.
134
135    :return: A list containing entities dictionaries.
136    """
137    cube_entities_dict_list = []
138    n = int(math.ceil(math.sqrt(nof_cubes)))
139    counter = 0
140    for i in range(n):
141        for j in range(n):
142            cube_name = 'cube{:04d}'.format(counter + 1)
143            cube = doc.addObject('Part::Box', cube_name + '_obj')
144            cube.Length = length
145            cube.Width = width
146            cube.Height = height
147            cube.Placement = App.Placement(App.Vector(i * (length+cube_distance), j * (width+cube_distance), 0),
148                                           App.Rotation(App.Vector(0, 0, 1), 0))
149            doc.recompute()
150            # create entities dict
151            face_picks = [('alpha0', 5), ('alpha1', 0), ('beta0', 4), ('beta1', 2), ('gamma0', 3), ('gamma1', 1)]
152            faces = FreeCADBatchFEMTools.pick_faces_from_geometry(cube, face_picks)
153            solids = []
154            FreeCADBatchFEMTools.add_entity_in_list(solids, cube_name, cube, {'mesh size': mesh_size})
155            entities_dict = FreeCADBatchFEMTools.create_entities_dict(cube_name, faces, solids, cube)
156            cube_entities_dict_list.append(entities_dict)
157            doc.recompute()
158            counter += 1
159            if counter == nof_cubes:
160                return cube_entities_dict_list
161
162
163def create_geometry(nof_cubes, mesh_size, find_boundaries_from_entities_dict, find_solids_from_entities_dict, directory,
164                    add_air, point_search, use_spheres, mesh_size_air):
165    """
166    Creates geometry for cubes/spheres and exports unv file.
167
168    :param nof_cubes: Number of cubes/spheres created.
169    :param mesh_size: Mesh size for cubes/spheres.
170    :param find_boundaries_from_entities_dict: 'True' or 'False'.
171    :param find_solids_from_entities_dict: 'True' or 'False'.
172    :param directory: Path to directory of this script.
173    :param add_air: A boolean. Is air added to geometry.
174    :param point_search: A boolean.
175    :param use_spheres: A boolean.
176    :param mesh_size_air: A float.
177
178    :return: A list containing tuples (name, execution time).
179    """
180    total_start_time = time.time()
181    entities_list = []
182    return_time_list = []
183    cube_edge_length, cube_distance = 200, 20
184    mesh_size_max = max(mesh_size, mesh_size_air)
185    if use_spheres:
186        print_line('Creating sphere geometry...', total_start_time)
187        entities_list.extend(create_sphere_geometry(nof_cubes, mesh_size, cube_edge_length, cube_distance))
188    else:
189        print_line('Creating cube geometry...', total_start_time)
190        entities_list.extend(create_cube_geometry(nof_cubes, mesh_size, cube_edge_length, cube_edge_length,
191                                                  cube_edge_length, cube_distance))
192    if add_air:
193        n = int(math.ceil(math.sqrt(nof_cubes)))
194        air_edge_length = n*(cube_edge_length+cube_distance) + cube_distance
195        air_height = cube_edge_length+2*cube_distance
196        print_line("Creating air geometry...", total_start_time)
197        entities_list.append(create_air_geometry(entities_list, air_edge_length, air_edge_length, air_height,
198                                                 cube_distance, mesh_size_air))
199    entities_list.reverse()  # give air first to get mesh sizes correctly
200    print_line("Merging entities dictionaries...", total_start_time)
201    entities_dict = FreeCADBatchFEMTools.merge_entities_dicts(entities_list, 'All', default_mesh_size=mesh_size_max,
202                                                              add_prefixes={'solids': False, 'faces': True})
203    print_line("Getting solids from entities dictionaries...", total_start_time)
204    solid_objects = FreeCADBatchFEMTools.get_solids_from_entities_dict(entities_dict)
205    print_line("Creating mesh object and compound filter...", total_start_time)
206    mesh_object, compound_filter = FreeCADBatchFEMTools.create_mesh_object_and_compound_filter(solid_objects,
207                                                                                               mesh_size_max, doc)
208    if find_boundaries_from_entities_dict.lower() == 'true':
209        print_line("Finding boundaries...", total_start_time)
210        start_time = time.time()
211        FreeCADBatchFEMTools.find_boundaries_with_entities_dict(mesh_object, compound_filter,
212                                                                entities_dict, doc)
213        return_time_list.append(('-Find boundaries:', time.time() - start_time))
214    if find_solids_from_entities_dict.lower() == 'true':
215        print_line("Finding bodies...", total_start_time)
216        start_time = time.time()
217        body_mesh_groups = FreeCADBatchFEMTools.find_bodies_with_entities_dict(mesh_object, compound_filter,
218                                                                               entities_dict, doc, point_search)
219        return_time_list.append(('-Find solids:', time.time() - start_time))
220    print_line("Defining mesh sizes...", total_start_time)
221    start_time = time.time()
222    # ignore air from mesh definition, mesh_object forces air to largest mesh
223    if find_solids_from_entities_dict.lower() == 'true':
224        FreeCADBatchFEMTools.define_mesh_sizes_with_mesh_groups(mesh_object, body_mesh_groups, doc, ignore_list=['air'])
225    else:  # in this case mesh sizes needs to be found
226        FreeCADBatchFEMTools.define_mesh_sizes(mesh_object, compound_filter, entities_dict, doc, point_search,
227                                               ignore_list=['air'])
228    return_time_list.append(('-Define mesh sizes:', time.time() - start_time))
229    FreeCADBatchFEMTools.fit_view()
230    start_time = time.time()
231    print_line("Creating mesh...", total_start_time)
232    FreeCADBatchFEMTools.create_mesh(mesh_object)
233    return_time_list.append(('-Create mesh:', time.time() - start_time))
234    print_line("Exporting unv...", total_start_time)
235    FreeCADBatchFEMTools.export_unv(os.path.join(directory, 'cubemeshtest.unv'), mesh_object)
236    print_line("Geometry done", total_start_time)
237    return return_time_list + [('-Total time:', time.time() - total_start_time)]
238
239
240script_directory = os.path.dirname(__file__)
241
242with open(os.path.join(script_directory, 'cubemeshtestparameters.txt')) as f:
243    [number_of_cubes, cube_mesh_size, find_boundaries, find_solids, create_air, air_mesh_size,
244     find_with_points, create_spheres, append_file] = f.read().split()
245
246create_air = create_air.lower() == 'true'
247find_with_points = find_with_points.lower() == 'true'
248create_spheres = create_spheres.lower() == 'true'
249if air_mesh_size == 'None':
250    air_mesh_size = cube_mesh_size
251try:
252    elapsed_times = create_geometry(int(number_of_cubes), float(cube_mesh_size), find_boundaries, find_solids,
253                                    script_directory, create_air, find_with_points, create_spheres, float(air_mesh_size))
254except Exception:
255    import traceback
256    print(str(traceback.format_exc()))
257else:
258    if create_air:
259        info_line = '\nexecution times with {} cubes (air: {}, spheres: {}, fb: {}, '.format(number_of_cubes, create_air,
260                                                                                             create_spheres, find_boundaries)
261        info_line += 'fs: {}, mesh_size: {}, air_mesh_size: {}, point_search: {}):'.format(find_solids, cube_mesh_size,
262                                                                                           air_mesh_size, find_with_points)
263    else:
264        info_line = '\nexecution times with {} cubes (air: {}, spheres: {}, fb: {}, '.format(number_of_cubes, create_air,
265                                                                                             create_spheres, find_boundaries)
266        info_line += 'fs: {}, mesh_size: {}, point_search: {}):'.format(find_solids, cube_mesh_size, find_with_points)
267    print(info_line)
268    for time_tuple in elapsed_times:
269        print(time_tuple[0], round(time_tuple[1], 1))
270    sys.stdout.flush()
271    if append_file.lower() == 'true':
272        with open(os.path.join(script_directory, 'cubemeshtestexecutiontimes.txt'), 'a') as f:
273            f.write(info_line + '\n')
274            for time_tuple in elapsed_times:
275                f.write('{} {}\n'.format(time_tuple[0], round(time_tuple[1], 1)))
276            f.write('\n')
277if not FreeCAD.GuiUp:
278  exit()
279