Coverage for bim2sim/plugins/PluginOpenFOAM/bim2sim_openfoam/task/create_openfoam_geometry.py: 0%
1271 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-01 10:24 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-01 10:24 +0000
1import math
2import pathlib
3import random
4import shutil
5import tempfile
6import logging
7from pathlib import Path
9import numpy as np
10import stl
11from OCC.Core.BRep import BRep_Tool
12from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Common
13from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeVertex, \
14 BRepBuilderAPI_Transform, BRepBuilderAPI_MakeEdge
15from OCC.Core.BRepExtrema import BRepExtrema_DistShapeShape
16from OCC.Core.BRepLib import BRepLib_FuseEdges
17from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
18from OCC.Core.Extrema import Extrema_ExtFlag_MIN
19from OCC.Core.StlAPI import StlAPI_Writer, StlAPI_Reader
20from OCC.Core.TopAbs import TopAbs_SOLID
21from OCC.Core.TopExp import TopExp_Explorer
22from OCC.Core.TopOpeBRep import TopOpeBRep_ShapeIntersector
23from OCC.Core.TopoDS import TopoDS_Compound, TopoDS_Builder, TopoDS_Shape
24from OCC.Core.gp import gp_Pnt, gp_XYZ, gp_Trsf, gp_Ax1, gp_Dir, gp_Vec
25from stl import mesh
27from bim2sim.elements.mapping.units import ureg
28from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus.utils.utils_visualization import \
29 VisualizationUtils
30from bim2sim.plugins.PluginOpenFOAM.bim2sim_openfoam.openfoam_elements.airterminal import \
31 AirTerminal
32from bim2sim.plugins.PluginOpenFOAM.bim2sim_openfoam.openfoam_elements.furniture import \
33 Furniture, Table
34from bim2sim.plugins.PluginOpenFOAM.bim2sim_openfoam.openfoam_elements.heater import \
35 Heater
36from bim2sim.plugins.PluginOpenFOAM.bim2sim_openfoam.openfoam_elements.people import \
37 People
38from bim2sim.plugins.PluginOpenFOAM.bim2sim_openfoam.openfoam_elements.stlbound import \
39 StlBound
40from bim2sim.plugins.PluginOpenFOAM.bim2sim_openfoam.utils.openfoam_utils \
41 import OpenFOAMUtils
42from bim2sim.tasks.base import ITask
43from bim2sim.utilities.common_functions import filter_elements
44from bim2sim.utilities.pyocc_tools import PyOCCTools
46logger = logging.getLogger(__name__)
49class CreateOpenFOAMGeometry(ITask):
50 """This ITask initializes the OpenFOAM Geometry.
51 """
53 reads = ('openfoam_case', 'elements')
54 touches = ('openfoam_case', 'openfoam_elements')
56 single_use = False
58 def __init__(self, playground):
59 super().__init__(playground)
61 def run(self, openfoam_case, elements):
62 openfoam_elements = dict()
63 self.init_zone(openfoam_case,
64 elements, openfoam_elements,
65 space_guid=self.playground.sim_settings.select_space_guid)
66 # todo: add geometry for heater and air terminals
67 self.init_heater(openfoam_case, elements, openfoam_elements)
68 self.init_airterminals(openfoam_case, elements, openfoam_elements,
69 self.playground.sim_settings.inlet_type,
70 self.playground.sim_settings.outlet_type)
71 self.get_base_surface(openfoam_case, openfoam_elements)
72 self.init_furniture(openfoam_case, elements, openfoam_elements)
73 self.init_people(openfoam_case, elements, openfoam_elements)
74 # setup geometry for constant
75 self.export_stlbound_triSurface(openfoam_case, openfoam_elements)
76 self.export_heater_triSurface(openfoam_elements)
77 self.export_airterminal_triSurface(openfoam_elements)
78 self.export_furniture_triSurface(openfoam_elements)
79 self.export_people_triSurface(openfoam_elements)
80 if self.playground.sim_settings.adjust_refinements:
81 self.adjust_refinements(openfoam_case, openfoam_elements)
83 return openfoam_case, openfoam_elements
85 def get_base_surface(self, openfoam_case, openfoam_elements):
86 furniture_surface = None
87 stl_bounds = filter_elements(openfoam_elements, 'StlBound')
88 floor = []
89 for bound in stl_bounds:
90 if 'floor' in bound.bound_element_type.lower():
91 # and bound.bound.top_bottom == 'BOTTOM':
92 floor.append(bound)
93 if len(floor) == 1:
94 furniture_surface = floor[0].tri_geom
95 elif len(floor) > 1:
96 logger.warning('more than 1 floor surface detected, using largest '
97 'floor surface.')
98 fla = 0
99 for fl in floor:
100 if fl.bound_area > fla:
101 furniture_surface = fl
102 fla = fl.bound_area
103 logger.warning(f'Multiple floor surfaces ({len(floor)} detected, '
104 f'largest surface has area = {fla}m2. Merge '
105 f'floor surfaces to prevent errors.')
106 fused_floor = PyOCCTools.fuse_shapes([f.tri_geom for f in floor])
107 furniture_surface = fused_floor
108 logger.warning(f'Merged floor surfaces have a total floor area of '
109 f'{PyOCCTools.get_shape_area(fused_floor)}m2.')
110 else:
111 raise NotImplementedError('NO FLOOR SURFACE FOUND FOR FURNITURE '
112 'POSITION.')
113 openfoam_case.furniture_surface = furniture_surface
115 @staticmethod
116 def init_zone(openfoam_case, elements, openfoam_elements,
117 space_guid='2RSCzLOBz4FAK$_wE8VckM'):
118 # guid '2RSCzLOBz4FAK$_wE8VckM' Single office has no 2B bounds
119 # guid '3$f2p7VyLB7eox67SA_zKE' Traffic area has 2B bounds
121 openfoam_case.current_zone = elements[space_guid]
122 if not openfoam_case.current_zone.fixed_heat_flow_rate_persons:
123 openfoam_case.current_zone.fixed_heat_flow_rate_persons = 0
124 openfoam_case.floor_area = openfoam_case.current_zone.net_area.m
125 openfoam_case.current_bounds = openfoam_case.current_zone.space_boundaries
126 if hasattr(openfoam_case.current_zone, 'space_boundaries_2B'): # todo
127 # remove 2b
128 openfoam_case.current_bounds += openfoam_case.current_zone.space_boundaries_2B
129 for bound in openfoam_case.current_bounds:
130 new_stl_bound = StlBound(bound, openfoam_case.radiation_model)
131 openfoam_elements[new_stl_bound.solid_name] = new_stl_bound
132 # openfoam_case.stl_bounds.append(new_stl_bound)
134 def init_heater(self, openfoam_case, elements, openfoam_elements):
135 # create Shape for heating in front of window unless space heater is
136 # available in ifc.
137 if ((self.playground.sim_settings.add_floorheating) and
138 (self.playground.sim_settings.add_heating)):
139 # floor heating is added in set_boundary_conditions.py if applicable
140 return
141 elif not self.playground.sim_settings.add_heating:
142 return
144 heater_window = None
145 bim2sim_heaters = filter_elements(elements, 'SpaceHeater')
146 heater_shapes = []
147 if not hasattr(openfoam_case.current_zone, 'heaters'):
148 openfoam_case.current_zone.heaters = []
149 if bim2sim_heaters:
150 # todo: get product shape of space heater
151 # identify space heater in current zone (maybe flag is already
152 # set from preprocessing in bim2sim
153 # get TopoDS_Shape for further preprocessing of the shape.
154 for space_heater in bim2sim_heaters:
155 if PyOCCTools.obj2_in_obj1(
156 obj1=openfoam_case.current_zone.space_shape,
157 obj2=space_heater.shape):
158 openfoam_case.current_zone.heaters.append(space_heater)
159 if openfoam_case.current_zone.heaters:
160 for b2s_heater in openfoam_case.current_zone.heaters:
161 # visualization to check if heaters are inside of current
162 # space boundaries
163 # VisualizationUtils.display_occ_shapes(
164 # [bim2sim_heaters[0].shape,
165 # bim2sim_heaters[1].shape,
166 # *[b.bound_shape for b in
167 # list(openfoam_case.current_bounds)[:-8]]])
168 heater_bbox_shape = PyOCCTools.simple_bounding_box(
169 b2s_heater.shape)
170 front_surface_min_max = (heater_bbox_shape[0],
171 (heater_bbox_shape[1][0],
172 heater_bbox_shape[0][1],
173 heater_bbox_shape[1][2]))
174 front_surface = PyOCCTools.make_faces_from_pnts([
175 gp_Pnt(*front_surface_min_max[0]),
176 gp_Pnt(front_surface_min_max[1][0],
177 front_surface_min_max[0][1],
178 front_surface_min_max[0][2]),
179 gp_Pnt(*front_surface_min_max[1]),
180 gp_Pnt(front_surface_min_max[0][0],
181 front_surface_min_max[0][1],
182 front_surface_min_max[1][2])])
183 back_surface_min_max = ((heater_bbox_shape[0][0],
184 heater_bbox_shape[1][1],
185 heater_bbox_shape[0][2]),
186 heater_bbox_shape[1])
187 back_surface = PyOCCTools.make_faces_from_pnts([
188 gp_Pnt(*back_surface_min_max[0]),
189 gp_Pnt(back_surface_min_max[1][0],
190 back_surface_min_max[0][1],
191 back_surface_min_max[0][2]),
192 gp_Pnt(*back_surface_min_max[1]),
193 gp_Pnt(back_surface_min_max[0][0],
194 back_surface_min_max[0][1],
195 back_surface_min_max[1][2])])
197 side_surface_left = PyOCCTools.make_faces_from_pnts([
198 gp_Pnt(*back_surface_min_max[0]),
199 gp_Pnt(*front_surface_min_max[0]),
200 gp_Pnt(front_surface_min_max[0][0],
201 front_surface_min_max[0][1],
202 front_surface_min_max[1][2]),
203 gp_Pnt(back_surface_min_max[0][0],
204 back_surface_min_max[0][1],
205 back_surface_min_max[1][2])]
206 )
207 side_surface_right = PyOCCTools.make_faces_from_pnts([
209 gp_Pnt(back_surface_min_max[1][0],
210 back_surface_min_max[1][1],
211 back_surface_min_max[0][2]),
212 gp_Pnt(front_surface_min_max[1][0],
213 front_surface_min_max[1][1],
214 front_surface_min_max[0][2]),
215 gp_Pnt(*front_surface_min_max[1]),
216 gp_Pnt(*back_surface_min_max[1])]
217 )
218 shape_list = [side_surface_left, side_surface_right,
219 front_surface, back_surface]
220 heater_shape = TopoDS_Compound()
221 builder = TopoDS_Builder()
222 builder.MakeCompound(heater_shape)
223 for shape in shape_list:
224 builder.Add(heater_shape, shape)
225 heater_shapes.append(heater_shape)
226 else:
227 openings = []
228 stl_bounds = filter_elements(openfoam_elements, 'StlBound')
229 for obj in stl_bounds:
230 if obj.bound_element_type == 'Window':
231 openings.append(obj)
232 if len(openings) == 1:
233 heater_window = openings[0]
234 elif len(openings) > 1:
235 if openfoam_case.current_zone.net_area.m < 35:
236 heater_window = openings[0]
237 else:
238 logger.warning('more than one heater required, '
239 'not implemented. '
240 'Heater may be unreasonably large. ')
241 heater_window = openings[0]
243 else:
244 logger.warning('No window found for positioning of heater.')
245 return
246 heater_shape = self.get_boundaries_of_heater(openfoam_case,
247 heater_window)
248 heater_shapes.append(heater_shape)
250 # heater_shape holds side surfaces of space heater.
251 for i, shape in enumerate(heater_shapes):
252 heater = Heater(f'heater{i}', shape,
253 openfoam_case.openfoam_triSurface_dir,
254 openfoam_case.radiation_model)
255 # abs(openfoam_case.current_zone.zone_heat_conduction))
256 openfoam_elements[heater.solid_name] = heater
258 @staticmethod
259 def get_boundaries_of_heater(openfoam_case, heater_window,
260 heater_depth=0.08):
261 move_reversed_flag = False
262 # get upper limit
263 window_min_max = PyOCCTools.simple_bounding_box(
264 heater_window.bound.bound_shape)
265 if isinstance(window_min_max[0], tuple):
266 new_list = []
267 for pnt in window_min_max:
268 new_list.append(gp_Pnt(gp_XYZ(pnt[0], pnt[1], pnt[2])))
269 window_min_max = new_list
270 heater_upper_limit = window_min_max[0].Z() - 0.05
271 heater_lower_limit = PyOCCTools.simple_bounding_box(
272 heater_window.bound.parent_bound.bound_shape)[0][2] + 0.12
273 heater_min_pre_X = window_min_max[0].X()
274 heater_min_pre_Y = window_min_max[0].Y()
275 heater_max_pre_X = window_min_max[1].X()
276 heater_max_pre_Y = window_min_max[1].Y()
277 heater_lower_center_pre_X = heater_window.bound.bound_center.X()
278 heater_lower_center_pre_Y = heater_window.bound.bound_center.Y()
279 heater_lower_center_Z = (heater_upper_limit -
280 heater_lower_limit) / 2 + heater_lower_limit
282 back_surface_pre = PyOCCTools.make_faces_from_pnts([
283 gp_Pnt(heater_min_pre_X, heater_min_pre_Y, heater_lower_limit),
284 gp_Pnt(heater_max_pre_X, heater_max_pre_Y, heater_lower_limit),
285 gp_Pnt(heater_max_pre_X, heater_max_pre_Y, heater_upper_limit),
286 gp_Pnt(heater_min_pre_X, heater_min_pre_Y, heater_upper_limit),
287 ]
288 )
289 distance_pre_moving = (
290 BRepExtrema_DistShapeShape(
291 back_surface_pre, BRepBuilderAPI_MakeVertex(
292 openfoam_case.current_zone.space_center).Shape(),
293 Extrema_ExtFlag_MIN).Value())
294 back_surface_moved = PyOCCTools.move_bound_in_direction_of_normal(
295 back_surface_pre, move_dist=0.05)
296 distance_post_moving = BRepExtrema_DistShapeShape(
297 back_surface_moved, BRepBuilderAPI_MakeVertex(
298 openfoam_case.current_zone.space_center).Shape(),
299 Extrema_ExtFlag_MIN).Value()
300 if distance_post_moving < distance_pre_moving:
301 back_surface = back_surface_moved
303 else:
304 move_reversed_flag = True
305 back_surface = PyOCCTools.move_bound_in_direction_of_normal(
306 back_surface_pre, move_dist=0.05, reverse=move_reversed_flag)
308 front_surface = PyOCCTools.move_bound_in_direction_of_normal(
309 back_surface, move_dist=heater_depth,
310 reverse=move_reversed_flag)
312 front_surface_min_max = PyOCCTools.simple_bounding_box(front_surface)
313 back_surface_min_max = PyOCCTools.simple_bounding_box(back_surface)
315 side_surface_left = PyOCCTools.make_faces_from_pnts([
316 gp_Pnt(*back_surface_min_max[0]),
317 gp_Pnt(*front_surface_min_max[0]),
318 gp_Pnt(front_surface_min_max[0][0], front_surface_min_max[0][1],
319 front_surface_min_max[1][2]),
320 gp_Pnt(back_surface_min_max[0][0], back_surface_min_max[0][1],
321 back_surface_min_max[1][2])]
322 )
323 side_surface_right = PyOCCTools.make_faces_from_pnts([
325 gp_Pnt(back_surface_min_max[1][0], back_surface_min_max[1][1],
326 back_surface_min_max[0][2]),
327 gp_Pnt(front_surface_min_max[1][0], front_surface_min_max[1][1],
328 front_surface_min_max[0][2]),
329 gp_Pnt(*front_surface_min_max[1]),
330 gp_Pnt(*back_surface_min_max[1])]
331 )
332 shape_list = [side_surface_left, side_surface_right,
333 front_surface, back_surface]
334 heater_shape = TopoDS_Compound()
335 builder = TopoDS_Builder()
336 builder.MakeCompound(heater_shape)
337 for shape in shape_list:
338 builder.Add(heater_shape, shape)
339 return heater_shape
341 def init_airterminals(self, openfoam_case, elements, openfoam_elements,
342 inlet_type, outlet_type):
343 if not self.playground.sim_settings.add_airterminals:
344 return
345 inlets = []
346 outlets = []
348 air_terminal_surface = None
349 stl_bounds = filter_elements(openfoam_elements, 'StlBound')
350 ceiling_roof = []
351 for bound in stl_bounds:
352 if (bound.bound_element_type in ['Ceiling', 'Roof']):
353 # or (
354 # bound.bound_element_type in ['Floor', 'InnerFloor']
355 # and bound.bound.top_bottom == 'TOP')):
356 ceiling_roof.append(bound)
357 if len(ceiling_roof) == 1:
358 air_terminal_surface = ceiling_roof[0]
359 elif len(ceiling_roof) > 1:
360 logger.warning('multiple ceilings/roofs detected. Not implemented. '
361 'Merge shapes before proceeding to avoid errors. ')
362 air_terminal_surface = ceiling_roof[0]
363 bim2sim_airterminals = filter_elements(elements, 'AirTerminal')
364 openfoam_case.current_zone.airterminals = []
365 if bim2sim_airterminals:
366 if not hasattr(openfoam_case.current_zone, 'airterminals'):
367 openfoam_case.current_zone.airterminals = []
368 for airterminal in bim2sim_airterminals:
369 if PyOCCTools.obj2_in_obj1(
370 obj1=openfoam_case.current_zone.space_shape,
371 obj2=airterminal.shape):
372 openfoam_case.current_zone.airterminals.append(airterminal)
373 if openfoam_case.current_zone.airterminals:
374 air_terminals = openfoam_case.current_zone.airterminals
375 # if 'AirTerminal' in [name.__class__.__name__ for name in
376 # list(elements.values())]:
377 # air_terminals = [a for a in list(elements.values()) if
378 # (a.__class__.__name__ == 'AirTerminal')]
379 # todo: get product shape of air terminals
380 # identify air terminals in current zone (maybe flag is already
381 # set from preprocessing in bim2sim
382 # get TopoDS_Shape for further preprocessing of the shape.
383 meshes = []
384 if inlet_type == 'Original':
385 at0 = air_terminals[0]
386 # at0_fcs = PyOCCTools.get_faces_from_shape(at0.shape)
387 # d = {PyOCCTools.get_center_of_face(f).Coord(): f for f in
388 # at0_fcs}
389 # sd = {k: v for k, v in sorted(d.items(), key=lambda x: x[0][2])}
390 # low_fc = list(sd.items())[0][1]
391 air_terminal_box, shapes, removed = (
392 PyOCCTools.remove_sides_of_bounding_box(
393 at0.shape, cut_top=False, cut_bottom=True,
394 cut_left=False, cut_right=False, cut_back=False,
395 cut_front=False))
396 inlet_base_shape = removed[0]
397 if 'schlitz' in at0.name.lower():
398 base_min_max = PyOCCTools.get_minimal_bounding_box(
399 inlet_base_shape)
400 x_dist = abs(base_min_max[0][0] - base_min_max[1][0])
401 y_dist = abs(base_min_max[0][1] - base_min_max[1][1])
402 cut_each_side_percent = 0.45
403 if x_dist < y_dist:
404 source_shape = PyOCCTools.make_faces_from_pnts([
405 gp_Pnt(base_min_max[0][0] +
406 cut_each_side_percent * x_dist,
407 base_min_max[0][1] + 0.01,
408 base_min_max[0][2]),
409 gp_Pnt(base_min_max[1][0] -
410 cut_each_side_percent * x_dist,
411 base_min_max[0][1] + 0.01,
412 base_min_max[0][2]),
413 gp_Pnt(base_min_max[1][
414 0] - cut_each_side_percent * x_dist,
415 base_min_max[1][1] - 0.01,
416 base_min_max[1][2]),
417 gp_Pnt(base_min_max[0][0] +
418 cut_each_side_percent * x_dist,
419 base_min_max[1][1] - 0.01, base_min_max[1][
420 2])])
421 else:
422 source_shape = PyOCCTools.make_faces_from_pnts([
423 gp_Pnt(base_min_max[0][0] + 0.01,
424 base_min_max[0][1] + cut_each_side_percent * y_dist,
425 base_min_max[0][2]),
426 gp_Pnt(base_min_max[1][0] - 0.01,
427 base_min_max[0][1] + cut_each_side_percent * y_dist,
428 base_min_max[0][2]),
429 gp_Pnt(base_min_max[1][0] - 0.01,
430 base_min_max[1][1] - cut_each_side_percent * y_dist,
431 base_min_max[1][2]),
432 gp_Pnt(base_min_max[0][0] + 0.01,
433 base_min_max[1][1] - cut_each_side_percent * y_dist,
434 base_min_max[1][2])])
435 else:
436 source_shape = PyOCCTools.scale_shape(inlet_base_shape, 0.2,
437 predefined_center=PyOCCTools.get_center_of_face(
438 inlet_base_shape))
439 diffuser_shape = PyOCCTools.triangulate_bound_shape(
440 inlet_base_shape, [source_shape])
441 if self.playground.sim_settings.outflow_direction == 'side':
442 blend_shape_pnts = PyOCCTools.get_points_of_face(
443 inlet_base_shape)
444 blend_shape_coords = [p.Coord() for p in blend_shape_pnts]
445 new_blend_shape_pnts = [gp_Pnt(x, y, z - 0.01)
446 for x, y, z in blend_shape_coords]
447 additional_face = PyOCCTools.make_faces_from_pnts(
448 new_blend_shape_pnts)
449 compound = TopoDS_Compound()
450 builder = TopoDS_Builder()
451 builder.MakeCompound(compound)
452 for shp in [diffuser_shape, additional_face]:
453 builder.Add(compound, shp)
454 diffuser_shape = PyOCCTools.triangulate_bound_shape(
455 compound)
456 elif (self.playground.sim_settings.outflow_direction ==
457 'angle45down'):
458 base_min_max = PyOCCTools.get_minimal_bounding_box(
459 source_shape)
460 x_dist = abs(base_min_max[0][0] - base_min_max[1][0])
461 y_dist = abs(base_min_max[0][1] - base_min_max[1][1])
462 if y_dist > x_dist:
463 center_line_x1 = gp_Pnt(base_min_max[0][0] + x_dist/2,
464 base_min_max[0][1],
465 base_min_max[0][2])
466 center_line_x2 = gp_Pnt(base_min_max[1][0] + x_dist/2,
467 base_min_max[1][1],
468 base_min_max[1][2])
469 # todo: add lowered points (decreased z-coord) for
470 # new tilted face.
472 elif inlet_type == 'SimpleStlDiffusor':
473 for m in mesh.Mesh.from_multi_file(
474 Path(
475 __file__).parent.parent / 'assets' / 'geometry' /
476 'air' / 'drallauslass_ersatzmodell.stl'):
477 meshes.append(m)
478 else:
479 for m in mesh.Mesh.from_multi_file(
480 Path(
481 __file__).parent.parent / 'assets' / 'geometry' /
482 'air' / 'AirTerminal.stl'):
483 meshes.append(m)
484 # print(str(m.name, encoding='utf-8'))
485 temp_path = openfoam_case.openfoam_triSurface_dir / 'Temp'
486 temp_path.mkdir(exist_ok=True)
487 for m in meshes:
488 curr_name = temp_path.as_posix() + '/' + str(m.name,
489 encoding='utf-8') + '.stl'
490 with open(curr_name, 'wb+') as output_file:
491 m.save(str(m.name, encoding='utf-8'), output_file,
492 mode=stl.Mode.ASCII)
493 output_file.close()
494 # read individual files from temp directory.
495 if inlet_type == 'Original':
496 pass
497 elif inlet_type == 'SimpleStlDiffusor':
498 diffuser_shape = TopoDS_Shape()
499 stl_reader = StlAPI_Reader()
500 stl_reader.Read(diffuser_shape,
501 temp_path.as_posix() + '/' + "origin-w_drallauslass.stl")
502 source_shape = TopoDS_Shape()
503 stl_reader = StlAPI_Reader()
504 stl_reader.Read(source_shape,
505 temp_path.as_posix() + '/' + "origin-inlet.stl")
506 air_terminal_box = None # TopoDS_Shape()
507 # stl_reader = StlAPI_Reader()
508 # stl_reader.Read(air_terminal_box,
509 # temp_path.as_posix() + '/' + "box_3.stl")
510 else:
511 diffuser_shape = TopoDS_Shape()
512 stl_reader = StlAPI_Reader()
513 stl_reader.Read(diffuser_shape,
514 temp_path.as_posix() + '/' + "model_24.stl")
515 source_shape = TopoDS_Shape()
516 stl_reader = StlAPI_Reader()
517 stl_reader.Read(source_shape,
518 temp_path.as_posix() + '/' + "inlet_1.stl")
519 air_terminal_box = TopoDS_Shape()
520 stl_reader = StlAPI_Reader()
521 stl_reader.Read(air_terminal_box,
522 temp_path.as_posix() + '/' + "box_3.stl")
524 air_terminal_compound = TopoDS_Compound()
525 builder = TopoDS_Builder()
526 builder.MakeCompound(air_terminal_compound)
527 shapelist = [shp for shp in [diffuser_shape, source_shape,
528 air_terminal_box] if
529 shp is not None]
530 for shp in shapelist:
531 builder.Add(air_terminal_compound, shp)
532 air_terminal_compound = BRepLib_FuseEdges(
533 air_terminal_compound).Shape()
534 air_terminal_compound = PyOCCTools.simple_bounding_box_shape(
535 air_terminal_compound)
537 compound_bbox = PyOCCTools.simple_bounding_box(
538 air_terminal_compound)
539 compound_center = PyOCCTools.get_center_of_shape(
540 air_terminal_compound).Coord()
541 compound_center_lower = gp_Pnt(compound_center[0],
542 compound_center[1],
543 compound_bbox[0][2])
544 for airt in air_terminals:
546 airt_center = PyOCCTools.get_center_of_shape(airt.shape).Coord()
547 airt_bbox = PyOCCTools.simple_bounding_box(
548 airt.shape)
549 airt_center_lower = gp_Pnt(airt_center[0],
550 airt_center[1],
551 airt_bbox[0][2]).Coord()
552 # new compounds
553 if 'zuluft' in airt.name.lower():
554 trsf_inlet = gp_Trsf()
555 trsf_inlet.SetTranslation(compound_center_lower,
556 gp_Pnt(*airt_center_lower))
557 inlet_shape = BRepBuilderAPI_Transform(
558 air_terminal_compound,
559 trsf_inlet).Shape()
560 inlet_diffuser_shape = None
561 inlet_source_shape = None
562 inlet_box_shape = None
563 if diffuser_shape:
564 inlet_diffuser_shape = BRepBuilderAPI_Transform(
565 diffuser_shape,
566 trsf_inlet).Shape()
567 if source_shape:
568 inlet_source_shape = BRepBuilderAPI_Transform(
569 source_shape,
570 trsf_inlet).Shape()
571 if air_terminal_box:
572 inlet_box_shape = BRepBuilderAPI_Transform(
573 air_terminal_box,
574 trsf_inlet).Shape()
575 inlet_shapes = [inlet_diffuser_shape, inlet_source_shape,
576 inlet_box_shape]
577 inlet_min_max = PyOCCTools.simple_bounding_box(inlet_shape)
578 inlet_min_max_box = BRepPrimAPI_MakeBox(
579 gp_Pnt(*inlet_min_max[0]),
580 gp_Pnt(
581 *inlet_min_max[1]))
582 faces = PyOCCTools.get_faces_from_shape(
583 inlet_min_max_box.Shape())
584 shell = PyOCCTools.make_shell_from_faces(faces)
585 inlet_solid = PyOCCTools.make_solid_from_shell(shell)
586 inlet_shapes.extend(
587 [inlet_min_max_box.Shape(), inlet_min_max])
588 inlet = AirTerminal(f'inlet_{len(inlets)}', inlet_shapes,
589 openfoam_case.openfoam_triSurface_dir,
590 inlet_type)
591 inlet.solid = inlet_solid
592 inlets.append(inlet)
593 if 'abluft' in airt.name.lower():
594 trsf_outlet = gp_Trsf()
595 trsf_outlet.SetTranslation(compound_center_lower,
596 gp_Pnt(*airt_center_lower))
597 outlet_shape = BRepBuilderAPI_Transform(
598 air_terminal_compound,
599 trsf_outlet).Shape()
600 # Todo: remove hardcoded rotations
601 apply_rotation = None
602 box_diff = (abs(PyOCCTools.get_shape_volume(
603 PyOCCTools.simple_bounding_box_shape([
604 outlet_shape, airt.shape])) -
605 PyOCCTools.get_shape_volume(
606 outlet_shape)) /
607 PyOCCTools.get_shape_volume(
608 outlet_shape))
609 if box_diff > 0.4:
610 apply_rotation = True
611 if apply_rotation:
612 outlet_shape = PyOCCTools.rotate_by_deg(outlet_shape,
613 axis='z',
614 rotation=90
615 )
616 outlet_diffuser_shape = None
617 outlet_source_shape = None
618 outlet_box_shape = None
619 if outlet_type != 'None':
620 outlet_diffuser_shape = BRepBuilderAPI_Transform(
621 diffuser_shape,
622 trsf_outlet).Shape()
623 if apply_rotation:
624 outlet_diffuser_shape = PyOCCTools.rotate_by_deg(
625 outlet_diffuser_shape,
626 axis='z',
627 rotation=90)
628 if source_shape:
629 outlet_source_shape = BRepBuilderAPI_Transform(
630 source_shape,
631 trsf_outlet).Shape()
632 if apply_rotation:
633 outlet_source_shape = PyOCCTools.rotate_by_deg(
634 outlet_source_shape,
635 axis='z',
636 rotation=90)
638 if air_terminal_box:
639 outlet_box_shape = BRepBuilderAPI_Transform(
640 air_terminal_box,
641 trsf_outlet).Shape()
642 if apply_rotation:
643 outlet_box_shape = PyOCCTools.rotate_by_deg(
644 outlet_box_shape,
645 axis='z',
646 rotation=90)
647 outlet_shapes = [outlet_diffuser_shape, outlet_source_shape,
648 outlet_box_shape]
649 outlet_min_max = PyOCCTools.simple_bounding_box(
650 outlet_shape)
651 outlet_min_max_box = BRepPrimAPI_MakeBox(
652 gp_Pnt(*outlet_min_max[0]),
653 gp_Pnt(
654 *outlet_min_max[1]))
655 faces = PyOCCTools.get_faces_from_shape(
656 outlet_min_max_box.Shape())
657 shell = PyOCCTools.make_shell_from_faces(faces)
658 outlet_solid = PyOCCTools.make_solid_from_shell(shell)
659 outlet_shapes.extend(
660 [outlet_min_max_box.Shape(), outlet_min_max])
661 outlet = AirTerminal(f'outlet{len(outlets)}', outlet_shapes,
662 openfoam_case.openfoam_triSurface_dir,
663 outlet_type)
664 outlet.solid = outlet_solid
665 outlets.append(outlet)
666 cut_ceiling = PyOCCTools.triangulate_bound_shape(
667 air_terminal_surface.bound.bound_shape,
668 [*[inl.solid for inl in inlets],
669 *[out.solid for out in outlets]])
671 air_terminal_surface.tri_geom = cut_ceiling
672 air_terminal_surface.bound_area = PyOCCTools.get_shape_area(
673 cut_ceiling)
674 # export stl geometry of surrounding surfaces again (including cut
675 # ceiling)
676 # create instances of air terminal class and return them?
677 if len(outlets) == 0 or len(inlets) == 0:
678 # check for cases with overflow from other spaces
679 stl_bounds = filter_elements(openfoam_elements, 'StlBound')
680 door_sbs = [sb for sb in stl_bounds
681 if 'door' in sb.bound_element_type.lower()]
682 door_outlet_height = 0.02
684 if len(outlets) == 0:
685 # define additional outlet below door
687 # case 1 (simplification):
688 # all doors are used as outlets
689 for i, dsb in enumerate(door_sbs):
690 dsb_shape = dsb.tri_geom
691 dsb_min_max = PyOCCTools.get_minimal_bounding_box(
692 dsb_shape)
693 dsb_outlet_cut_shape = PyOCCTools.make_faces_from_pnts([
694 gp_Pnt(*dsb_min_max[0]),
695 gp_Pnt(dsb_min_max[1][0], dsb_min_max[1][1],
696 dsb_min_max[0][2]),
697 gp_Pnt(dsb_min_max[1][0], dsb_min_max[1][1],
698 dsb_min_max[0][
699 2] + door_outlet_height),
700 gp_Pnt(dsb_min_max[0][0], dsb_min_max[0][1],
701 dsb_min_max[0][
702 2] + door_outlet_height)])
703 new_door_shape = PyOCCTools.triangulate_bound_shape(
704 dsb_shape, [dsb_outlet_cut_shape])
705 dsb.tri_geom = new_door_shape
706 dsb.bound_area = PyOCCTools.get_shape_area(
707 new_door_shape)
708 outlet_shapes = [None, dsb_outlet_cut_shape, None,
709 PyOCCTools.simple_bounding_box_shape(
710 dsb_outlet_cut_shape),
711 PyOCCTools.get_minimal_bounding_box(
712 dsb_outlet_cut_shape)]
713 outlet = AirTerminal(f'outlet_overflow_{i}',
714 outlet_shapes,
715 openfoam_case.openfoam_triSurface_dir,
716 inlet_outlet_type='None')
717 outlets.append(outlet)
718 # todo: case 2 (only doors to rooms with outlets are
719 # considered
720 # for overflow)
722 if len(inlets) == 0:
723 # define additional inlet at upper part of door
724 for i, dsb in enumerate(door_sbs):
725 dsb_shape = dsb.tri_geom
726 dsb_min_max = PyOCCTools.get_minimal_bounding_box(
727 dsb_shape)
728 dsb_inlet_cut_shape = PyOCCTools.make_faces_from_pnts([
729 gp_Pnt(dsb_min_max[0][0], dsb_min_max[0][1],
730 dsb_min_max[1][2] - door_outlet_height),
731 gp_Pnt(dsb_min_max[1][0], dsb_min_max[1][1],
732 dsb_min_max[1][2] - door_outlet_height),
733 gp_Pnt(*dsb_min_max[1]),
734 gp_Pnt(dsb_min_max[0][0], dsb_min_max[0][1],
735 dsb_min_max[1][2])])
736 new_door_shape = PyOCCTools.triangulate_bound_shape(
737 dsb_shape, [dsb_inlet_cut_shape])
738 dsb.tri_geom = new_door_shape
739 dsb.bound_area = PyOCCTools.get_shape_area(
740 new_door_shape)
741 inlet_shapes = [None, dsb_inlet_cut_shape, None,
742 PyOCCTools.simple_bounding_box_shape(
743 dsb_inlet_cut_shape),
744 PyOCCTools.get_minimal_bounding_box(
745 dsb_inlet_cut_shape)]
746 inlet = AirTerminal(f'inlet_overflow_{i}',
747 inlet_shapes,
748 openfoam_case.openfoam_triSurface_dir,
749 inlet_outlet_type='None')
750 inlets.append(inlet)
751 else:
752 ceiling_roof = []
753 for bound in stl_bounds:
754 if (bound.bound_element_type in ['Ceiling', 'Roof']):
755 # or (
756 # bound.bound_element_type in ['Floor', 'InnerFloor']
757 # and bound.bound.top_bottom == 'TOP')):
758 ceiling_roof.append(bound)
759 if len(ceiling_roof) == 1:
760 air_terminal_surface = ceiling_roof[0]
761 elif len(ceiling_roof) > 1:
762 logger.warning(
763 'multiple ceilings/roofs detected. Not implemented. '
764 'Merge shapes before proceeding to avoid errors. ')
765 air_terminal_surface = ceiling_roof[0]
766 # todo: add simsettings for outlet choice!
767 inlet, outlet = self.create_airterminal_shapes(
768 openfoam_case,
769 air_terminal_surface,
770 inlet_type, outlet_type)
771 inlets.append(inlet)
772 outlets.append(outlet)
773 for inlet in inlets:
774 openfoam_elements[inlet.solid_name] = inlet
775 for outlet in outlets:
776 openfoam_elements[outlet.solid_name] = outlet
778 def create_airterminal_shapes(self, openfoam_case, air_terminal_surface,
779 inlet_type, outlet_type):
780 surf_min_max = PyOCCTools.simple_bounding_box(
781 air_terminal_surface.bound.bound_shape)
782 lx = surf_min_max[1][0] - surf_min_max[0][0]
783 ly = surf_min_max[1][1] - surf_min_max[0][1]
784 if ly > lx:
785 div_wall_distance = ly / 4
786 half_distance = lx / 2
787 inlet_pos = [surf_min_max[0][0] + half_distance, surf_min_max[0][
788 1] + div_wall_distance, surf_min_max[0][2] - 0.02]
789 outlet_pos = [surf_min_max[0][0] + half_distance, surf_min_max[1][
790 1] - div_wall_distance, surf_min_max[1][2] - 0.02]
791 else:
792 div_wall_distance = lx / 4
793 half_distance = ly / 2
794 inlet_pos = [surf_min_max[0][0] + div_wall_distance,
795 surf_min_max[0][
796 1] + half_distance, surf_min_max[0][2] - 0.02]
797 outlet_pos = [surf_min_max[0][0] + div_wall_distance,
798 surf_min_max[1][
799 1] - half_distance, surf_min_max[1][2] - 0.02]
801 # split multifile in single stl files, otherwise air terminal cannot
802 # be read properly
803 meshes = []
804 if inlet_type == 'SimpleStlDiffusor':
805 for m in mesh.Mesh.from_multi_file(
806 Path(__file__).parent.parent / 'assets' / 'geometry' /
807 'air' / 'drallauslass_ersatzmodell.stl'):
808 meshes.append(m)
809 else:
810 for m in mesh.Mesh.from_multi_file(
811 Path(__file__).parent.parent / 'assets' / 'geometry' /
812 'air' / 'AirTerminal.stl'):
813 meshes.append(m)
814 # print(str(m.name, encoding='utf-8'))
815 temp_path = openfoam_case.openfoam_triSurface_dir / 'Temp'
816 temp_path.mkdir(exist_ok=True)
817 for m in meshes:
818 curr_name = temp_path.as_posix() + '/' + str(m.name,
819 encoding='utf-8') + '.stl'
820 with open(curr_name, 'wb+') as output_file:
821 m.save(str(m.name, encoding='utf-8'), output_file,
822 mode=stl.Mode.ASCII)
823 output_file.close()
824 # read individual files from temp directory.
825 if inlet_type == 'SimpleStlDiffusor':
826 diffuser_shape = TopoDS_Shape()
827 stl_reader = StlAPI_Reader()
828 stl_reader.Read(diffuser_shape,
829 temp_path.as_posix() + '/' + "origin-w_drallauslass.stl")
830 source_shape = TopoDS_Shape()
831 stl_reader = StlAPI_Reader()
832 stl_reader.Read(source_shape,
833 temp_path.as_posix() + '/' + "origin-inlet.stl")
834 air_terminal_box = None # TopoDS_Shape()
835 # stl_reader = StlAPI_Reader()
836 # stl_reader.Read(air_terminal_box,
837 # temp_path.as_posix() + '/' + "box_3.stl")
838 else:
839 diffuser_shape = TopoDS_Shape()
840 stl_reader = StlAPI_Reader()
841 stl_reader.Read(diffuser_shape,
842 temp_path.as_posix() + '/' + "model_24.stl")
843 source_shape = TopoDS_Shape()
844 stl_reader = StlAPI_Reader()
845 stl_reader.Read(source_shape,
846 temp_path.as_posix() + '/' + "inlet_1.stl")
847 air_terminal_box = TopoDS_Shape()
848 stl_reader = StlAPI_Reader()
849 stl_reader.Read(air_terminal_box,
850 temp_path.as_posix() + '/' + "box_3.stl")
852 air_terminal_compound = TopoDS_Compound()
853 builder = TopoDS_Builder()
854 builder.MakeCompound(air_terminal_compound)
855 shapelist = [shape for shape in [diffuser_shape, source_shape,
856 air_terminal_box] if shape is not None]
857 for shape in shapelist:
858 builder.Add(air_terminal_compound, shape)
859 air_terminal_compound = BRepLib_FuseEdges(air_terminal_compound).Shape()
860 air_terminal_compound = PyOCCTools.simple_bounding_box_shape(
861 air_terminal_compound)
863 compound_bbox = PyOCCTools.simple_bounding_box(air_terminal_compound)
864 compound_center = PyOCCTools.get_center_of_shape(
865 air_terminal_compound).Coord()
866 compound_center_lower = gp_Pnt(compound_center[0], compound_center[1],
867 compound_bbox[0][2])
869 # new compounds
870 trsf_inlet = gp_Trsf()
871 trsf_inlet.SetTranslation(compound_center_lower, gp_Pnt(*inlet_pos))
872 inlet_shape = BRepBuilderAPI_Transform(air_terminal_compound,
873 trsf_inlet).Shape()
874 inlet_diffuser_shape = None
875 inlet_source_shape = None
876 inlet_box_shape = None
877 if diffuser_shape:
878 inlet_diffuser_shape = BRepBuilderAPI_Transform(diffuser_shape,
879 trsf_inlet).Shape()
880 if source_shape:
881 inlet_source_shape = BRepBuilderAPI_Transform(source_shape,
882 trsf_inlet).Shape()
883 if air_terminal_box:
884 inlet_box_shape = BRepBuilderAPI_Transform(air_terminal_box,
885 trsf_inlet).Shape()
886 inlet_shapes = [inlet_diffuser_shape, inlet_source_shape,
887 inlet_box_shape]
888 trsf_outlet = gp_Trsf()
889 trsf_outlet.SetTranslation(compound_center_lower, gp_Pnt(*outlet_pos))
890 outlet_shape = BRepBuilderAPI_Transform(air_terminal_compound,
891 trsf_outlet).Shape()
892 outlet_diffuser_shape = None
893 outlet_source_shape = None
894 outlet_box_shape = None
895 if outlet_type != 'None':
896 outlet_diffuser_shape = BRepBuilderAPI_Transform(diffuser_shape,
897 trsf_outlet).Shape()
898 if source_shape:
899 outlet_source_shape = BRepBuilderAPI_Transform(source_shape,
900 trsf_outlet).Shape()
901 if air_terminal_box:
902 outlet_box_shape = BRepBuilderAPI_Transform(air_terminal_box,
903 trsf_outlet).Shape()
904 outlet_shapes = [outlet_diffuser_shape, outlet_source_shape,
905 outlet_box_shape]
906 outlet_min_max = PyOCCTools.simple_bounding_box(outlet_shape)
907 outlet_min_max_box = BRepPrimAPI_MakeBox(gp_Pnt(*outlet_min_max[0]),
908 gp_Pnt(
909 *outlet_min_max[1]))
910 faces = PyOCCTools.get_faces_from_shape(outlet_min_max_box.Shape())
911 shell = PyOCCTools.make_shell_from_faces(faces)
912 outlet_solid = PyOCCTools.make_solid_from_shell(shell)
913 inlet_min_max = PyOCCTools.simple_bounding_box(inlet_shape)
914 inlet_min_max_box = BRepPrimAPI_MakeBox(gp_Pnt(*inlet_min_max[0]),
915 gp_Pnt(
916 *inlet_min_max[1]))
917 faces = PyOCCTools.get_faces_from_shape(inlet_min_max_box.Shape())
918 shell = PyOCCTools.make_shell_from_faces(faces)
919 inlet_solid = PyOCCTools.make_solid_from_shell(shell)
920 cut_ceiling = PyOCCTools.triangulate_bound_shape(
921 air_terminal_surface.bound.bound_shape, [inlet_solid, outlet_solid])
922 inlet_shapes.extend([inlet_min_max_box.Shape(), inlet_min_max])
923 outlet_shapes.extend([outlet_min_max_box.Shape(), outlet_min_max])
925 air_terminal_surface.tri_geom = cut_ceiling
926 air_terminal_surface.bound_area = PyOCCTools.get_shape_area(cut_ceiling)
927 # export stl geometry of surrounding surfaces again (including cut
928 # ceiling)
929 # create instances of air terminal class and return them?
930 inlet = AirTerminal('inlet', inlet_shapes,
931 openfoam_case.openfoam_triSurface_dir, inlet_type)
932 outlet = AirTerminal('outlet', outlet_shapes,
933 openfoam_case.openfoam_triSurface_dir,
934 outlet_type)
935 # export moved inlet and outlet shapes
937 return inlet, outlet
939 def adjust_refinements(self, case, openfoam_elements):
940 """
941 Compute surface and region refinements for air terminals and other
942 interior elements.
943 """
944 bM_size = self.playground.sim_settings.mesh_size
945 if self.playground.sim_settings.add_airterminals:
946 air_terminals = filter_elements(openfoam_elements, 'AirTerminal')
947 for air_t in air_terminals:
948 if ('INLET' in air_t.air_type.upper() and
949 self.playground.sim_settings.inlet_type == 'Plate') or \
950 ('OUTLET' in air_t.air_type.upper() and \
951 self.playground.sim_settings.outlet_type == 'Plate'):
952 diff = air_t.diffuser.tri_geom
953 box = air_t.box.tri_geom
954 dist = OpenFOAMUtils.get_min_refdist_between_shapes(
955 diff, box)
956 ref_level = OpenFOAMUtils.get_refinement_level(dist,
957 bM_size)
958 air_t.diffuser.refinement_level = ref_level
959 air_t.box.refinement_level = ref_level
960 air_t.refinement_zone_level_small[1] = \
961 air_t.diffuser.refinement_level[0]
962 air_t.refinement_zone_level_large[1] = \
963 air_t.diffuser.refinement_level[0] - 1
964 else:
965 for part in [p for p in [air_t.diffuser, air_t.box,
966 air_t.source_sink] if p.tri_geom is not
967 None]:
968 verts, edges = OpenFOAMUtils.detriangulize(
969 OpenFOAMUtils, part.tri_geom)
970 min_dist = OpenFOAMUtils.get_min_internal_dist(verts)
971 edge_lengths = OpenFOAMUtils.get_edge_lengths(edges)
972 median_dist = np.median(edge_lengths)
973 self.logger.info(f"{air_t.solid_name}:\tPrev: "
974 f"{part.refinement_level}")
975 part.refinement_level = \
976 OpenFOAMUtils.get_refinement_level(min_dist, bM_size,
977 median_dist)
978 self.logger.info(f"{air_t.solid_name}:\tNEW: "
979 f"{part.refinement_level}")
980 air_t.refinement_zone_level_small[1] = \
981 air_t.diffuser.refinement_level[0]
982 air_t.refinement_zone_level_large[1] = \
983 air_t.diffuser.refinement_level[0] - 1
985 interior = dict() # Add other interior equipment and topoDS Shape
986 if self.playground.sim_settings.add_heating:
987 heaters = filter_elements(openfoam_elements, 'Heater')
988 interior.update({h: h.heater_surface.tri_geom for h in heaters})
989 if self.playground.sim_settings.add_furniture:
990 furniture = filter_elements(openfoam_elements, 'Furniture')
991 interior.update({f: f.tri_geom for f in furniture})
992 if self.playground.sim_settings.add_people:
993 people = filter_elements(openfoam_elements, 'People')
994 interior.update({p: p.tri_geom for p in people})
995 for int_elem, int_geom in interior.items():
996 self.logger.info(f"Updating refinements for {int_elem.solid_name}.")
997 verts, edges = OpenFOAMUtils.detriangulize(OpenFOAMUtils, int_geom)
998 int_dist = OpenFOAMUtils.get_min_internal_dist(verts)
999 edge_lengths = OpenFOAMUtils.get_edge_lengths(edges)
1000 median_int_dist = np.median(edge_lengths)
1001 wall_dist = OpenFOAMUtils.get_min_refdist_between_shapes(
1002 int_geom, case.current_zone.space_shape)
1003 if wall_dist > 1e-6:
1004 obj_dist = wall_dist
1005 else:
1006 obj_dist = 1000
1007 if len(interior) > 1:
1008 for key, obj_geom in interior.items():
1009 if key == int_elem:
1010 continue
1011 new_dist = OpenFOAMUtils.get_min_refdist_between_shapes(
1012 int_geom, obj_geom)
1013 if new_dist < obj_dist and new_dist > 1e-6:
1014 obj_dist = new_dist
1015 if wall_dist > 1e-6:
1016 min_dist_ext = min(wall_dist, obj_dist)
1017 else:
1018 min_dist_ext = obj_dist
1019 ref_level_reg = OpenFOAMUtils.get_refinement_level(min_dist_ext,
1020 bM_size, median_int_dist)
1021 if int_dist < min_dist_ext:
1022 ref_level_surf = OpenFOAMUtils.get_refinement_level(
1023 int_dist, bM_size, median_int_dist)
1024 else:
1025 ref_level_surf = ref_level_reg
1026 self.logger.info(f"{int_elem.solid_name}:\tPREV: "
1027 f"{int_elem.refinement_level},\tNEW: {ref_level_surf}")
1028 int_elem.refinement_level = ref_level_surf
1030 def init_furniture(self, openfoam_case, elements, openfoam_elements):
1031 if not self.playground.sim_settings.add_furniture:
1032 return
1033 furniture_surface = openfoam_case.furniture_surface
1035 if 'Furniture' in [name.__class__.__name__ for name in
1036 list(elements.values())]:
1037 # todo: get product shape of furniture
1038 # identify furniture in current zone (maybe flag is already
1039 # set from preprocessing in bim2sim
1040 # get TopoDS_Shape for further preprocessing of the shape.
1041 raise NotImplementedError('Furniture found in bim2sim, it cannot '
1042 'be handled yet. No furniture is added.')
1043 pass
1044 furniture = self.create_furniture_shapes(openfoam_case,
1045 furniture_surface)
1046 if isinstance(furniture, list):
1047 for elem in furniture:
1048 openfoam_elements[elem.solid_name] = elem
1049 else:
1050 openfoam_elements[furniture.solid_name] = furniture
1052 def create_furniture_shapes(self, openfoam_case, furniture_surface,
1053 x_gap=0.8, y_gap=0.8, side_gap=0.8):
1054 doors = []
1055 for bound in openfoam_case.current_bounds:
1056 if bound.bound_element:
1057 if "DOOR" in bound.bound_element.element_type.upper():
1058 doors.append(bound)
1059 meshes = []
1060 chair_shape = None
1061 desk_shape = None
1062 table = None
1063 furniture_setting = self.playground.sim_settings.furniture_setting
1064 furniture_path = (Path(__file__).parent.parent / 'assets' / 'geometry' /
1065 'furniture')
1066 furniture_shapes = []
1068 if furniture_setting in ['Office', 'Concert', 'Meeting', 'Classroom',
1069 'GroupTable', 'TwoSideTable']:
1070 chair_shape = TopoDS_Shape()
1071 stl_reader = StlAPI_Reader()
1072 stl_reader.Read(chair_shape,
1073 furniture_path.as_posix() + '/' +
1074 "DIN1729_ChairH460.stl")
1075 furniture_shapes.append(chair_shape)
1076 if furniture_setting in ['Office', 'Meeting', 'Classroom',
1077 'GroupTable', 'TwoSideTable']:
1078 desk_shape = TopoDS_Shape()
1079 stl_reader = StlAPI_Reader()
1080 stl_reader.Read(desk_shape,
1081 furniture_path.as_posix() + '/' +
1082 "Table1400x800H760.stl")
1083 table = Table(furniture_setting, desk_shape,
1084 triSurface_path=openfoam_case.openfoam_triSurface_dir,
1085 furniture_type='Table',
1086 bbox_min_max=PyOCCTools.simple_bounding_box(
1087 [desk_shape]),
1088 chair_bbox_min_max=PyOCCTools.simple_bounding_box(
1089 [chair_shape]))
1090 chair_shapes = [BRepBuilderAPI_Transform(chair_shape,
1091 tr).Shape() for tr in
1092 table.chair_trsfs]
1093 furniture_shapes += chair_shapes
1094 furniture_shapes.append(desk_shape)
1096 # furniture_path = (Path(__file__).parent.parent / 'assets' / 'geometry' /
1097 # 'furniture_people_compositions')
1098 # furniture_shapes = []
1099 # if self.playground.sim_settings.furniture_setting in ['Office',
1100 # 'Concert',
1101 # 'Meeting',
1102 # 'Classroom']:
1103 # chair_shape = TopoDS_Shape()
1104 # stl_reader = StlAPI_Reader()
1105 # stl_reader.Read(chair_shape,
1106 # furniture_path.as_posix() + '/' +
1107 # "new_compChair.stl")
1108 # furniture_shapes.append(chair_shape)
1109 # if self.playground.sim_settings.furniture_setting in ['Office',
1110 # 'Meeting']:
1111 # desk_shape = TopoDS_Shape()
1112 # stl_reader = StlAPI_Reader()
1113 # stl_reader.Read(desk_shape,
1114 # furniture_path.as_posix() + '/' +
1115 # "new_compDesk.stl")
1116 # furniture_shapes.append(desk_shape)
1117 # elif self.playground.sim_settings.furniture_setting in ['Classroom']:
1118 # desk_shape = TopoDS_Shape()
1119 # stl_reader = StlAPI_Reader()
1120 # stl_reader.Read(desk_shape,
1121 # furniture_path.parent.as_posix() + 'furniture/' +
1122 # "Table1200x600H760.stl")
1123 # furniture_shapes.append(desk_shape)
1125 furniture_compound = TopoDS_Compound()
1126 builder = TopoDS_Builder()
1127 builder.MakeCompound(furniture_compound)
1128 shapelist = [shape for shape in furniture_shapes if shape is not None]
1129 for shape in shapelist:
1130 builder.Add(furniture_compound, shape)
1131 requested_amount = self.playground.sim_settings.furniture_amount
1133 # todo: Algorithm for furniture setup based on furniture amount,
1134 # limited by furniture_surface area (or rather lx-ly-dimensions of the
1135 # area)
1137 if self.playground.sim_settings.furniture_setting in ['Concert',
1138 'Classroom',
1139 'Office',
1140 'Meeting',
1141 'GroupTable', 'TwoSideTable']:
1142 # todo: remove Office and Meeting setup here and replace by
1143 # appropriate other setup
1144 # Meeting: 1 two-sided table (rotate every other table by 180deg)
1145 # Office: similar to meeting, but spread tables in Office.
1146 # calculate amount of rows
1147 space_bbox = PyOCCTools.simple_bounding_box_shape([
1148 openfoam_case.current_zone.space_shape])
1149 orientation_dict = {'north': gp_Dir(0, 1, 0),
1150 'east': gp_Dir(1, 0, 0),
1151 'south': gp_Dir(0, -1, 0),
1152 'west': gp_Dir(-1, 0, 0)}
1154 space_bbox_min, space_bbox_max = PyOCCTools.simple_bounding_box([
1155 openfoam_case.current_zone.space_shape])
1156 north = space_bbox_max[1]
1157 south = space_bbox_min[1]
1158 east = space_bbox_max[0]
1159 west = space_bbox_min[0]
1160 # transform to origin
1161 if self.playground.sim_settings.furniture_orientation == 'door':
1162 # todo: Check if door is north/east/south/west
1163 if doors:
1164 if abs(doors[0].bound_center.X()-east) < 1e-3:
1165 orientation_dir = orientation_dict['east']
1166 elif abs(doors[0].bound_center.X()-west) < 1e-3:
1167 orientation_dir = orientation_dict['west']
1168 elif abs(doors[0].bound_center.Y()-south) < 1e-3:
1169 orientation_dir = orientation_dict['south']
1170 else:
1171 orientation_dir = orientation_dict['north']
1172 else:
1173 self.logger.warning("Furniture orientation 'Door' "
1174 "requested, but no door found. "
1175 "Furniture orientation is set to "
1176 "'north'.")
1177 orientation_dir = orientation_dict['north']
1178 elif self.playground.sim_settings.furniture_orientation == 'window':
1179 # todo: Check if window is north/east/south/west
1180 windows = []
1181 for bound in openfoam_case.current_bounds:
1182 if bound.bound_element:
1183 if "WINDOW" in bound.bound_element.element_type.upper():
1184 windows.append(bound)
1185 if windows:
1186 if abs(windows[0].bound_center.X()-east) < 1e-3:
1187 orientation_dir = orientation_dict['east']
1188 elif abs(windows[0].bound_center.X()-west) < 1e-3:
1189 orientation_dir = orientation_dict['west']
1190 elif abs(windows[0].bound_center.Y()-south) < 1e-3:
1191 orientation_dir = orientation_dict['south']
1192 else:
1193 orientation_dir = orientation_dict['north']
1194 else:
1195 self.logger.warning("Furniture orientation 'window' "
1196 "requested, but no window found. "
1197 "Furniture orientation is set to "
1198 "'north'.")
1199 orientation_dir = orientation_dict['north']
1200 elif self.playground.sim_settings.furniture_orientation in list(
1201 orientation_dict.keys()):
1202 orientation_dir = \
1203 orientation_dict[
1204 self.playground.sim_settings.furniture_orientation]
1205 elif (self.playground.sim_settings.furniture_orientation in
1206 ['short_side', 'long_side']):
1207 bbox_min, bbox_max = PyOCCTools.simple_bounding_box(space_bbox)
1208 box_x = bbox_max[0] - bbox_min[0]
1209 box_y = bbox_max[1] - bbox_min[1]
1210 if (self.playground.sim_settings.furniture_orientation ==
1211 'short_side'):
1212 if box_x < box_y:
1213 orientation_dir = gp_Dir(0, -1, 0)
1214 else:
1215 orientation_dir = gp_Dir(-1, 0, 0)
1216 else:
1217 if box_x > box_y:
1218 orientation_dir = gp_Dir(0, -1, 0)
1219 else:
1220 orientation_dir = gp_Dir(-1, 0, 0)
1221 else:
1222 self.logger.warning("Furniture orientation not implemented,"
1223 "Furniture orientation is set to 'north'.")
1224 orientation_dir = orientation_dict['north']
1226 trsf_to_origin = (
1227 PyOCCTools.transform_set_to_origin_based_on_surface(
1228 orientation_dir, space_bbox,
1229 ref_dir=gp_Vec(0, 1, 0)))
1230 trsf_back_to_global = trsf_to_origin.Inverted()
1231 furniture_surface_at_origin = BRepBuilderAPI_Transform(
1232 furniture_surface, trsf_to_origin).Shape()
1233 door_shapes_at_origin = []
1234 if doors:
1235 for door in doors:
1236 door_at_origin = BRepBuilderAPI_Transform(
1237 door.bound_shape, trsf_to_origin).Shape()
1238 door_shapes_at_origin.append(door_at_origin)
1239 if self.playground.sim_settings.furniture_setting == 'Concert':
1240 min_x_space = 0.5 # space for each seat SBauVO NRW 2019
1241 min_y_distance = 0.4 # between rows SBauVO NRW 2019
1242 max_rows_per_block = 15 # SBauVO NRW: max 30 rows per block
1243 max_obj_single_escape = 10 # SBauVO NRW: max 10 seats per
1244 # row if only a single escape route is available
1245 max_obj_two_escape = 20 # SBauVO NRW: max 20 seats in a row
1246 # if two escape routes are available
1247 if requested_amount <= 200:
1248 escape_route_width = 0.9
1249 else:
1250 escape_route_width = 1.2
1251 furniture_locations_origin, furniture_trsfs_origin = (
1252 self.generate_grid_positions_w_constraints(
1253 furniture_surface_at_origin, furniture_compound,
1254 requested_amount, min_x_space, min_y_distance,
1255 max_rows_per_block, max_obj_single_escape,
1256 max_obj_two_escape, escape_route_width, door_shapes_at_origin,
1257 min_dist_all_sides=0.15, allow_skip_scanline=True))
1258 elif self.playground.sim_settings.furniture_setting in [
1259 'Classroom', 'TwoSideTable']:
1260 min_x_space = 0.0 # space for each seat SBauVO NRW 2019
1261 min_y_distance = 1.5 # between rows SBauVO NRW 2019
1262 max_rows_per_block = 5 # SBauVO NRW: max 30 rows per block
1263 max_obj_single_escape = 5 # SBauVO NRW: max 10 seats per
1264 # row if only a single escape route is available
1265 max_obj_two_escape = 10 # SBauVO NRW: max 20 seats in a row
1266 # if two escape routes are available
1267 if requested_amount <= 200:
1268 escape_route_width = 0.9
1269 else:
1270 escape_route_width = 1.2
1271 chair_bbox = PyOCCTools.simple_bounding_box([chair_shape])
1272 add_chair_depth = chair_bbox[1][1] - chair_bbox[0][1]
1273 furniture_locations_origin, furniture_trsfs_origin = (
1274 self.generate_grid_positions_w_constraints(
1275 furniture_surface_at_origin, desk_shape,
1276 requested_amount, min_x_space, min_y_distance,
1277 max_rows_per_block, max_obj_single_escape,
1278 max_obj_two_escape, escape_route_width, door_shapes_at_origin,
1279 min_distance_last_row=add_chair_depth+0.15,
1280 min_dist_all_sides=0.15))
1281 elif self.playground.sim_settings.furniture_setting in [
1282 'GroupTable']:
1283 chair_bbox = PyOCCTools.simple_bounding_box([chair_shape])
1284 add_chair_depth = chair_bbox[1][1] - chair_bbox[0][1]
1285 table_bbox = PyOCCTools.simple_bounding_box([desk_shape])
1286 add_table_width = table_bbox[1][0] - table_bbox[0][0]
1287 min_x_space = 1.5 + add_table_width # space for each seat
1288 # SBauVO NRW 2019
1289 min_y_distance = 1.5 # between rows SBauVO NRW 2019
1290 max_rows_per_block = 5 # SBauVO NRW: max 30 rows per block
1291 max_obj_single_escape = 5 # SBauVO NRW: max 10 seats per
1292 # row if only a single escape route is available
1293 max_obj_two_escape = 10 # SBauVO NRW: max 20 seats in a row
1294 # if two escape routes are available
1295 if requested_amount <= 200:
1296 escape_route_width = 0.0# 0.9
1297 else:
1298 escape_route_width = 1.2
1299 min_seats_single_escape = 1
1300 min_rows_per_block = 1
1302 furniture_locations_origin, furniture_trsfs_origin = (
1303 self.generate_grid_positions_w_constraints(
1304 furniture_surface_at_origin, desk_shape,
1305 requested_amount, min_x_space, min_y_distance,
1306 max_rows_per_block, max_obj_single_escape,
1307 max_obj_two_escape, escape_route_width, door_shapes_at_origin,
1308 min_dist_all_sides=add_chair_depth+0.15,
1309 min_seats_single_escape=min_seats_single_escape,
1310 min_rows_per_block=min_rows_per_block))
1311 else:
1312 furniture_locations_origin, furniture_trsfs_origin = (
1313 self.generate_grid_positions(
1314 furniture_surface_at_origin, furniture_compound,
1315 requested_amount, x_gap, y_gap, side_gap))
1316 furniture_items = []
1317 global_chair_trsfs = []
1318 furniture_locations = []
1319 furniture_trsfs = []
1320 trsf_back_to_global_translation = trsf_back_to_global.TranslationPart()
1322 for loc in furniture_locations_origin:
1323 furniture_locations.append(gp_Pnt(loc.XYZ() + \
1324 trsf_back_to_global_translation))
1325 for tr in furniture_trsfs_origin:
1326 furniture_trsfs.append(trsf_back_to_global.Multiplied(tr))
1327 for i, trsf in enumerate(furniture_trsfs):
1328 furniture_shape = BRepBuilderAPI_Transform(furniture_compound,
1329 trsf).Shape()
1330 furniture_min_max = PyOCCTools.simple_bounding_box(furniture_shape)
1331 if chair_shape:
1332 if table:
1333 for j, chair_trsf in enumerate(table.chair_trsfs):
1334 global_trsf = trsf.Multiplied(chair_trsf)
1335 global_chair_trsfs.append(global_trsf)
1336 new_chair_shape = BRepBuilderAPI_Transform(
1337 chair_shape, trsf.Multiplied(chair_trsf)).Shape()
1338 chair = Furniture(new_chair_shape,
1339 openfoam_case.openfoam_triSurface_dir,
1340 f'Tab{i}_Chair{j}')
1341 furniture_items.append(chair)
1342 else:
1343 new_chair_shape = BRepBuilderAPI_Transform(chair_shape,
1344 trsf).Shape()
1345 global_chair_trsfs.append(trsf)
1346 chair = Furniture(new_chair_shape,
1347 openfoam_case.openfoam_triSurface_dir,
1348 f'Chair{i}')
1349 furniture_items.append(chair)
1350 if desk_shape:
1351 new_desk_shape = BRepBuilderAPI_Transform(desk_shape,
1352 trsf).Shape()
1353 desk = Furniture(new_desk_shape,
1354 openfoam_case.openfoam_triSurface_dir,
1355 f'Desk{i}')
1356 furniture_items.append(desk)
1358 openfoam_case.furniture_trsfs = furniture_trsfs
1359 openfoam_case.chair_trsfs = global_chair_trsfs
1360 return furniture_items
1362 def init_people(self, openfoam_case, elements, openfoam_elements):
1363 if not self.playground.sim_settings.add_people:
1364 return
1365 furniture_surface = openfoam_case.furniture_surface
1366 people = self.create_people_shapes(openfoam_case, furniture_surface)
1367 if isinstance(people, list):
1368 for elem in people:
1369 openfoam_elements[elem.solid_name] = elem
1370 else:
1371 openfoam_elements[people.solid_name] = people
1373 def create_people_shapes(self, openfoam_case, furniture_surface):
1375 furniture_path = (Path(__file__).parent.parent / 'assets' / 'geometry' /
1376 'furniture_people_compositions')
1377 # people_shapes = []
1378 people_items = []
1379 people_rotations = None
1380 if self.playground.sim_settings.use_energyplus_people_amount:
1381 people_amount = math.ceil(openfoam_case.timestep_df.filter(
1382 like=openfoam_case.current_zone.guid.upper()
1383 +':Zone People'))
1384 else:
1385 people_amount = self.playground.sim_settings.people_amount
1387 if self.playground.sim_settings.people_setting in ['Seated']:
1388 available_trsfs = openfoam_case.chair_trsfs
1390 person_path = (furniture_path.as_posix() + '/' +
1391 "DIN1729_manikin_split_19parts.stl")
1392 part_meshes = []
1393 for m in mesh.Mesh.from_multi_file(person_path):
1394 part_meshes.append(m)
1395 combined_data = np.concatenate([m.data.copy() for m in part_meshes])
1396 combined_mesh = mesh.Mesh(combined_data)
1397 temp_path = openfoam_case.openfoam_triSurface_dir / 'Temp'
1398 temp_path.mkdir(exist_ok=True)
1399 combined_mesh.save(openfoam_case.openfoam_triSurface_dir /
1400 'Temp' / 'combined_person.stl')
1401 person_shape = TopoDS_Shape()
1402 stl_reader = StlAPI_Reader()
1403 stl_reader.Read(person_shape,
1404 str(openfoam_case.openfoam_triSurface_dir /
1405 'Temp' / 'combined_person.stl'))
1406 # people_shapes.append(person_shape)
1407 if people_amount > len(available_trsfs):
1408 people_amount = len(available_trsfs)
1409 elif (self.playground.sim_settings.people_setting in ['Standing'] and
1410 len(openfoam_case.chair_trsfs) == 0):
1411 person_path = (Path(__file__).parent.parent / 'assets' /
1412 'geometry' / 'people' / "manikin_standing.stl")
1413 person_shape = TopoDS_Shape()
1414 stl_reader = StlAPI_Reader()
1415 stl_reader.Read(person_shape, person_path.as_posix())
1416 # people_shapes.append(person_shape)
1417 # set requested_amount independently from people amount for
1418 # generating grid positions to allow for random distribution
1419 # within the larger number of grid positions.
1420 people_locations, available_trsfs = self.generate_grid_positions(
1421 furniture_surface, person_shape, requested_amount=1000,
1422 x_gap=0.6, y_gap=0.6, side_gap=0.4)
1423 random.seed(23)
1424 people_rotations = random.sample(range(359), len(available_trsfs))
1425 else:
1426 self.logger.warning('Standing people are currently not supported '
1427 'combined with furniture setups. No people '
1428 'are added.')
1429 return
1430 random.seed(42)
1431 random_people_choice = random.sample(range(len(available_trsfs)),
1432 people_amount)
1433 for i, trsf in enumerate(available_trsfs):
1434 if i not in random_people_choice:
1435 continue
1436 if len(people_items) == people_amount:
1437 break
1439 new_person_shape = BRepBuilderAPI_Transform(person_shape,
1440 trsf).Shape()
1441 if people_rotations:
1442 new_person_shape = PyOCCTools.rotate_by_deg(
1443 new_person_shape,
1444 rotation=people_rotations[i])
1445 person = People(
1446 new_person_shape, trsf, person_path,
1447 openfoam_case.openfoam_triSurface_dir, f'Person{i}',
1448 radiation_model=openfoam_case.radiation_model,
1449 power=openfoam_case.current_zone.fixed_heat_flow_rate_persons.to(
1450 ureg.watt).m,
1451 scale=self.playground.sim_settings.scale_person_for_eval,
1452 add_scaled_shape=self.playground.sim_settings.add_air_volume_evaluation)
1453 people_items.append(person)
1454 return people_items
1456 @staticmethod
1457 def export_stlbound_triSurface(openfoam_case, openfoam_elements):
1458 stl_bounds = filter_elements(openfoam_elements, 'StlBound')
1459 temp_stl_path = Path(
1460 tempfile.TemporaryDirectory(
1461 prefix='bim2sim_temp_stl_files_').name)
1462 temp_stl_path.mkdir(exist_ok=True)
1463 with (open(openfoam_case.openfoam_triSurface_dir /
1464 str("space_" + openfoam_case.current_zone.guid + ".stl"),
1465 'wb+') as output_file):
1466 for stl_bound in stl_bounds:
1467 stl_path_name = temp_stl_path.as_posix() + '/' + \
1468 stl_bound.solid_name + '.stl'
1469 stl_writer = StlAPI_Writer()
1470 stl_writer.SetASCIIMode(True)
1471 stl_writer.Write(stl_bound.tri_geom, stl_path_name)
1472 sb_mesh = mesh.Mesh.from_file(stl_path_name)
1473 sb_mesh.save(stl_bound.solid_name, output_file,
1474 mode=stl.Mode.ASCII)
1475 output_file.close()
1476 if temp_stl_path.exists() and temp_stl_path.is_dir():
1477 shutil.rmtree(temp_stl_path)
1479 @staticmethod
1480 def export_heater_triSurface(openfoam_elements):
1481 heaters = filter_elements(openfoam_elements, 'Heater')
1482 for heater in heaters:
1483 create_stl_from_shape_single_solid_name(
1484 heater.heater_surface.tri_geom,
1485 heater.heater_surface.stl_file_path_name,
1486 heater.heater_surface.solid_name)
1487 create_stl_from_shape_single_solid_name(
1488 heater.porous_media.tri_geom,
1489 heater.porous_media.stl_file_path_name,
1490 heater.porous_media.solid_name)
1492 @staticmethod
1493 def export_airterminal_triSurface(openfoam_elements):
1494 air_terminals = filter_elements(openfoam_elements, 'AirTerminal')
1495 for air_terminal in air_terminals:
1496 if air_terminal.diffuser.tri_geom:
1497 create_stl_from_shape_single_solid_name(
1498 air_terminal.diffuser.tri_geom,
1499 air_terminal.diffuser.stl_file_path_name,
1500 air_terminal.diffuser.solid_name)
1501 if air_terminal.source_sink.tri_geom:
1502 create_stl_from_shape_single_solid_name(
1503 air_terminal.source_sink.tri_geom,
1504 air_terminal.source_sink.stl_file_path_name,
1505 air_terminal.source_sink.solid_name)
1506 if air_terminal.box.tri_geom:
1507 create_stl_from_shape_single_solid_name(
1508 air_terminal.box.tri_geom,
1509 air_terminal.box.stl_file_path_name,
1510 air_terminal.box.solid_name)
1512 @staticmethod
1513 def export_furniture_triSurface(openfoam_elements):
1514 furnitures = filter_elements(openfoam_elements, 'Furniture')
1515 for furniture in furnitures:
1516 if furniture.tri_geom:
1517 create_stl_from_shape_single_solid_name(
1518 furniture.tri_geom,
1519 furniture.stl_file_path_name,
1520 furniture.solid_name)
1522 @staticmethod
1523 def export_people_triSurface(openfoam_elements):
1524 people = filter_elements(openfoam_elements, 'People')
1525 for person in people:
1526 for body_part in person.body_parts_dict.values():
1527 if body_part.tri_geom:
1528 create_stl_from_shape_single_solid_name(
1529 body_part.tri_geom,
1530 body_part.stl_file_path_name,
1531 body_part.solid_name)
1533 def generate_grid_positions(self, furniture_surface, obj_to_be_placed,
1534 requested_amount,
1535 x_gap=0.2, y_gap=0.35,
1536 side_gap=0.6):
1537 furniture_surface_z = PyOCCTools.get_center_of_shape(
1538 furniture_surface).Z()
1540 surf_min_max = PyOCCTools.simple_bounding_box(furniture_surface)
1541 lx = surf_min_max[1][0] - surf_min_max[0][0]
1542 ly = surf_min_max[1][1] - surf_min_max[0][1]
1544 compound_bbox = PyOCCTools.simple_bounding_box(obj_to_be_placed)
1545 lx_comp = compound_bbox[1][0] - compound_bbox[0][0]
1546 ly_comp = compound_bbox[1][1] - compound_bbox[0][1]
1548 compound_center = PyOCCTools.get_center_of_shape(
1549 PyOCCTools.simple_bounding_box_shape(obj_to_be_placed)).Coord()
1550 compound_center_lower = gp_Pnt(compound_center[0], compound_center[1],
1551 compound_bbox[0][2])
1553 x_max_number = math.floor((lx - side_gap * 2 + x_gap) / (lx_comp +
1554 x_gap))
1555 y_max_number = math.floor((ly - side_gap * 2 + y_gap) / (ly_comp +
1556 y_gap))
1558 max_amount = x_max_number * y_max_number
1559 if requested_amount > max_amount:
1560 self.logger.warning(
1561 f'You requested an amount of '
1562 f'{requested_amount}, but only '
1563 f'{max_amount} is possible. Using this maximum '
1564 f'allowed amount.')
1565 requested_amount = max_amount
1567 # set number of rows to maximum number in y direction
1568 obj_rows = y_max_number
1569 obj_locations = []
1570 for row in range(obj_rows):
1571 if row == 0:
1572 y_loc = (surf_min_max[0][1] + side_gap + (row * y_gap) +
1573 ly_comp / 2)
1574 else:
1575 y_loc = (surf_min_max[0][1] + side_gap +
1576 (row * (y_gap + ly_comp)) + ly_comp / 2)
1577 x_loc = surf_min_max[0][0] + side_gap
1578 for x_pos in range(x_max_number):
1579 if x_pos == 0:
1580 x_loc += lx_comp / 2
1581 else:
1582 x_loc += x_gap + lx_comp
1583 pos = gp_Pnt(x_loc, y_loc, furniture_surface_z)
1584 obj_locations.append(pos)
1585 if len(obj_locations) == requested_amount:
1586 break
1587 if len(obj_locations) == requested_amount:
1588 break
1589 obj_trsfs = PyOCCTools.generate_obj_trsfs(obj_locations,
1590 compound_center_lower)
1591 return obj_locations, obj_trsfs
1593 def generate_grid_positions_w_constraints(self, furniture_surface,
1594 obj_to_be_placed,
1595 requested_amount,
1596 min_x_space=0.5,
1597 min_y_distance=0.4,
1598 max_obj_rows_per_block=30,
1599 max_obj_single_escape=10,
1600 max_obj_two_escape=20,
1601 escape_route_width=1.2,
1602 doors=[],
1603 min_distance_last_row=0.0,
1604 min_dist_all_sides=0.1,
1605 min_rows_per_block=3,
1606 min_seats_single_escape=3,
1607 allow_skip_scanline=False):
1608 furniture_surface_z = PyOCCTools.get_center_of_shape(
1609 furniture_surface).Z()
1611 max_row_blocks = 0 # possible blocks of rows
1612 max_single_escape_blocks = 0 # possible blocks with single escape route
1613 max_double_escape_blocks = 0
1614 max_seats_single_escape_blocks = [0,0]
1615 max_seats_double_escape_blocks = [0]
1616 max_rows_per_block = [0]
1617 rotation_angle = 0
1618 switch = False
1619 surf_min_max = PyOCCTools.simple_bounding_box(furniture_surface)
1620 global_x_position = surf_min_max[0][0]
1621 global_y_position = surf_min_max[0][1]
1622 lx = surf_min_max[1][0] - surf_min_max[0][0]
1623 ly = surf_min_max[1][1] - surf_min_max[0][1]
1624 # if self.playground.sim_settings.furniture_orientation == 'long_side':
1625 # if temp_lx < temp_ly:
1626 # lx = temp_ly
1627 # ly = temp_lx
1628 # global_x_position = surf_min_max[0][1]
1629 # global_y_position = surf_min_max[0][0]
1630 # rotation_angle = 270
1631 # switch = True
1632 # else:
1633 # lx = temp_lx
1634 # ly = temp_ly
1635 # elif self.playground.sim_settings.furniture_orientation == "short_side":
1636 # if temp_lx > temp_ly:
1637 # lx = temp_ly
1638 # ly = temp_lx
1639 # global_x_position = surf_min_max[0][1]
1640 # global_y_position = surf_min_max[0][0]
1641 # rotation_angle = 270
1642 # switch = True
1643 # else:
1644 # lx = temp_lx
1645 # ly = temp_ly
1646 # else:
1647 # lx = temp_lx
1648 # ly = temp_ly
1650 global_y_position += min_distance_last_row
1651 ly -= min_distance_last_row
1652 global_x_position += min_dist_all_sides
1653 global_y_position += min_dist_all_sides
1654 lx -= 2*min_dist_all_sides
1655 ly -= 2*min_dist_all_sides
1657 compound_bbox = PyOCCTools.simple_bounding_box(obj_to_be_placed)
1658 lx_comp = compound_bbox[1][0] - compound_bbox[0][0]
1659 ly_comp = compound_bbox[1][1] - compound_bbox[0][1]
1661 lx_comp_width = max(lx_comp, min_x_space)
1662 ly_comp_width = ly_comp + min_y_distance
1664 compound_center = PyOCCTools.get_center_of_shape(
1665 PyOCCTools.simple_bounding_box_shape(obj_to_be_placed)).Coord()
1666 compound_center_lower = gp_Pnt(compound_center[0], compound_center[1],
1667 compound_bbox[0][2])
1668 # calculate footprint shape of setup
1669 x_diff = lx_comp_width - lx_comp
1670 if x_diff < 0:
1671 x_diff = 0
1672 footprint_shape = PyOCCTools.make_faces_from_pnts(
1673 [gp_Pnt(compound_bbox[0][0]
1674 - x_diff / 2,
1675 compound_bbox[0][1],
1676 compound_bbox[0][2]),
1677 gp_Pnt(compound_bbox[0][0] - x_diff / 2,
1678 compound_bbox[0][1] + ly_comp_width,
1679 compound_bbox[0][2]),
1680 gp_Pnt(compound_bbox[0][0] + lx_comp_width - x_diff / 2,
1681 compound_bbox[0][1] + ly_comp_width,
1682 compound_bbox[0][2]),
1683 gp_Pnt(compound_bbox[0][0] + lx_comp_width - x_diff / 2,
1684 compound_bbox[0][1],
1685 compound_bbox[0][2])])
1687 skip_scan_line = False
1688 if ly > lx and allow_skip_scanline:
1689 skip_scan_line = True
1691 if not skip_scan_line:
1692 # calculate areas in front of doors to guarantee escape
1693 door_escapes = []
1694 for door_shape in doors:
1695 reverse=False
1696 (min_box, max_box) = PyOCCTools.simple_bounding_box([
1697 door_shape])
1698 door_lower_pnt1 = gp_Pnt(*min_box)
1699 door_lower_pnt2 = gp_Pnt(max_box[0], max_box[1], min_box[2])
1700 base_line_pnt1 = door_lower_pnt1
1701 base_line_pnt2 = door_lower_pnt2
1702 door_width = door_lower_pnt1.Distance(door_lower_pnt2)
1703 # add square space in front of doors, depth = escape route width
1704 d = gp_Dir(gp_Vec(base_line_pnt1, base_line_pnt2))
1705 new_dir = d.Rotated(gp_Ax1(base_line_pnt1, gp_Dir(0, 0, 1)),
1706 math.radians(90))
1707 moved_pnt1 = PyOCCTools.move_bound_in_direction_of_normal(
1708 BRepBuilderAPI_MakeVertex(base_line_pnt1).Vertex(),
1709 escape_route_width, move_dir=new_dir, reverse=reverse)
1710 moved_dist = BRepExtrema_DistShapeShape(moved_pnt1,
1711 furniture_surface,
1712 Extrema_ExtFlag_MIN).Value()
1713 if abs(moved_dist) > 1e-3:
1714 reverse = True
1715 moved_pnt1 = PyOCCTools.move_bound_in_direction_of_normal(
1716 BRepBuilderAPI_MakeVertex(base_line_pnt1).Vertex(),
1717 max(escape_route_width, door_width), move_dir=new_dir, reverse=reverse)
1718 if reverse:
1719 moved_pnt2 = PyOCCTools.move_bound_in_direction_of_normal(
1720 BRepBuilderAPI_MakeVertex(base_line_pnt2).Vertex(),
1721 max(escape_route_width, door_width), move_dir=new_dir, reverse=reverse)
1722 else:
1723 moved_pnt2 = PyOCCTools.move_bound_in_direction_of_normal(
1724 BRepBuilderAPI_MakeVertex(base_line_pnt2).Vertex(),
1725 max(escape_route_width, door_width), move_dir=new_dir, reverse=reverse)
1726 add_escape_shape = PyOCCTools.make_faces_from_pnts([base_line_pnt1,
1727 base_line_pnt2,
1728 BRep_Tool.Pnt(moved_pnt2),
1729 BRep_Tool.Pnt(moved_pnt1)])
1730 door_escapes.append(add_escape_shape)
1731 swp_x1 = gp_Pnt(
1732 global_x_position, global_y_position, furniture_surface_z)
1733 swp_x2 = gp_Pnt(
1734 global_x_position, global_y_position+ly, furniture_surface_z)
1735 swp_dir_x = gp_Pnt(
1736 global_x_position+lx, global_y_position, furniture_surface_z)
1738 (translated_lines_x, intersection_points_x, min_t_x, min_delta_x,
1739 min_pnt_x) = (
1740 PyOCCTools.sweep_line_find_intersections_multiple_shapes(
1741 swp_x1, swp_x2, [PyOCCTools.extrude_face_in_direction(s) for s in door_escapes], gp_Dir(gp_Vec(swp_x1, swp_dir_x))))
1742 if door_escapes:
1743 inside_door_escape = True if min([BRepExtrema_DistShapeShape(
1744 BRepBuilderAPI_MakeEdge(swp_x1, swp_x2).Edge(), d,
1745 Extrema_ExtFlag_MIN).Value() for d in door_escapes]) < 1e-4 else (
1746 False)
1747 else:
1748 inside_door_escape = False
1749 # these intersections are relative to the global positions
1750 intersect_dict_x = {}
1751 for p in set([round(p[1], 3) for p in intersection_points_x]):
1752 count = 0
1753 for i in [round(p[1], 3) for p in intersection_points_x]:
1754 if i == p:
1755 count += 1
1756 if count > 4:
1757 intersect_dict_x.update({p: count})
1758 sorted_intersections_x = dict(sorted(intersect_dict_x.items()))
1761 unavail_x_pos = []
1762 temp_x_pos = 0
1763 reset_x_pos = False
1764 if inside_door_escape:
1765 k = 1
1766 else:
1767 k = 0
1768 for i, key in enumerate(list(sorted_intersections_x.keys())):
1769 if (i+k) % 2 == 0:
1770 if (key - temp_x_pos) < lx_comp_width * min_seats_single_escape:
1771 if unavail_x_pos and unavail_x_pos[-1][1] == temp_x_pos:
1772 unavail_x_pos[-1][1] = key
1773 else:
1774 unavail_x_pos.append([temp_x_pos, key])
1775 else:
1776 if unavail_x_pos and unavail_x_pos[-1][1] == temp_x_pos:
1777 unavail_x_pos[-1][1] = key
1778 else:
1779 unavail_x_pos.append([temp_x_pos, key])
1780 temp_x_pos = key
1781 if temp_x_pos != 0 and abs(lx - temp_x_pos)>1e-3:
1782 # todo: double check
1783 if abs(temp_x_pos - lx) < lx_comp_width * min_seats_single_escape:
1784 if unavail_x_pos and unavail_x_pos[-1][1] == temp_x_pos:
1785 unavail_x_pos[-1][1] = lx
1786 else:
1787 unavail_x_pos.append([temp_x_pos, lx])
1788 else:
1789 unavail_x_pos = []
1791 available_x_list = []
1792 if unavail_x_pos:
1793 for i, uxp in enumerate(unavail_x_pos):
1794 if uxp[1] - uxp[0] < escape_route_width:
1795 add_escape_dist = escape_route_width - (uxp[1] - uxp[0])
1796 else:
1797 add_escape_dist = 0
1798 if i == 0:
1799 # todo
1800 if uxp[0] < global_x_position:
1801 available_x_list.append('MinXEscape')
1802 elif uxp[0] > global_x_position:
1803 available_x_list.append(uxp[0])
1804 if i == len(unavail_x_pos) - 1:
1805 # todo
1806 x_width_available = abs(lx - uxp[1] - add_escape_dist)
1807 if abs(uxp[1] - lx) < 1e-3:
1808 available_x_list.append('MaxXEscape')
1809 else:
1810 available_x_list.append(x_width_available)
1811 continue
1812 else:
1813 x_width_available = unavail_x_pos[i + 1][0] - uxp[
1814 1] - add_escape_dist
1815 available_x_list.append(x_width_available)
1816 else:
1817 x_width_available = abs(lx - escape_route_width)
1818 available_x_list.append(x_width_available)
1820 if 'MinXEscape' in available_x_list and 'MaxXEscape' in \
1821 available_x_list:
1822 max_single_escape_blocks = 0
1823 max_seats_single_escape_blocks = [0, 0]
1824 for avail_x in available_x_list:
1825 if isinstance(avail_x, str):
1826 continue
1827 else:
1828 x_max_number = math.floor(avail_x / lx_comp_width)
1829 if x_max_number > max_obj_two_escape:
1830 temp_max_double_escape_blocks = avail_x / (
1831 escape_route_width + max_obj_two_escape * lx_comp_width)
1832 if temp_max_double_escape_blocks < 1:
1833 max_double_escape_blocks += 1
1834 if sum(max_seats_double_escape_blocks) == 0:
1835 max_seats_double_escape_blocks = [
1836 max_obj_two_escape] * max_double_escape_blocks
1837 else:
1838 max_seats_double_escape_blocks += [
1839 max_obj_two_escape] * max_double_escape_blocks
1840 else:
1841 max_double_escape_blocks += math.ceil(
1842 temp_max_double_escape_blocks)
1843 temp_max_seats = math.floor(((avail_x - (math.ceil(
1844 temp_max_double_escape_blocks))*
1845 escape_route_width)/lx_comp_width))
1846 add_seats = [
1847 temp_max_seats//math.ceil(
1848 temp_max_double_escape_blocks)
1849 for s in range(max_double_escape_blocks)]
1850 if (abs(sum(add_seats)-temp_max_seats) > 0 and
1851 add_seats):
1852 add_seats[-1] +=abs(sum(
1853 add_seats)-temp_max_seats)
1854 if add_seats:
1855 if sum(max_seats_double_escape_blocks) == 0:
1856 max_seats_single_escape_blocks = add_seats
1857 else:
1858 max_seats_single_escape_blocks += add_seats
1859 else:
1860 max_double_escape_blocks = 1
1861 max_seats_double_escape_blocks = [x_max_number]
1864 elif 'MinXEscape' in available_x_list or 'MaxXEscape' in available_x_list:
1865 if len(available_x_list) <= 2:
1866 for avail_x in available_x_list:
1867 if isinstance(avail_x, str):
1868 continue
1869 x_max_number = math.floor(avail_x/lx_comp_width)
1870 if x_max_number < max_obj_single_escape:
1871 set_single_number = x_max_number
1872 remaining_number = 0
1873 max_single_escape_blocks = 1
1874 else:
1875 set_single_number = max_obj_single_escape
1876 remaining_number = x_max_number - set_single_number
1877 max_single_escape_blocks = 1
1878 if 'MinXEscape' in available_x_list:
1879 max_seats_single_escape_blocks = [0, set_single_number]
1880 else:
1881 max_seats_single_escape_blocks = [set_single_number, 0]
1882 if remaining_number > min_seats_single_escape:
1883 remaining_x_width = math.floor(
1884 remaining_number*lx_comp_width)
1886 temp_max_double_escape_blocks = remaining_x_width / (
1887 escape_route_width + max_obj_two_escape * lx_comp_width)
1888 if temp_max_double_escape_blocks < 1:
1889 avail_seats = math.floor((
1890 remaining_x_width-escape_route_width) / (
1891 lx_comp_width))
1892 max_double_escape_blocks += 1
1893 max_seats_double_escape_blocks.append(
1894 avail_seats)
1895 else:
1896 max_double_escape_blocks += math.ceil(
1897 temp_max_double_escape_blocks)
1898 temp_max_seats = math.floor(
1899 ((avail_x - (math.ceil(
1900 temp_max_double_escape_blocks) *
1901 escape_route_width)) /
1902 lx_comp_width))
1903 add_seats = [temp_max_seats // math.ceil(
1904 temp_max_double_escape_blocks)
1905 for s in range(max_double_escape_blocks)]
1906 if (abs(sum(add_seats) - temp_max_seats) > 0 and
1907 add_seats):
1908 add_seats[-1] += abs(sum(
1909 add_seats) - temp_max_seats)
1910 if add_seats:
1911 if sum(max_seats_double_escape_blocks) == 0:
1912 max_seats_single_escape_blocks = add_seats
1913 else:
1914 max_seats_single_escape_blocks += add_seats
1916 else:
1917 for i, avail_x in enumerate(available_x_list):
1918 x_max_number = math.floor(avail_x / lx_comp_width)
1919 if x_max_number > 2*max_obj_single_escape:
1920 temp_max_double_escape_blocks = avail_x / (
1921 escape_route_width + max_obj_two_escape * lx_comp_width)
1922 if temp_max_double_escape_blocks < 1 and (i == 0 and len(
1923 available_x_list) == 1):
1924 max_single_escape_blocks = 2
1925 max_seats_single_escape_blocks = [max_obj_single_escape,
1926 max_obj_single_escape]
1927 else:
1928 if 0 < i < len(available_x_list):
1929 temp_max_double_escape_blocks = math.floor(
1930 temp_max_double_escape_blocks)
1931 else:
1932 temp_max_double_escape_blocks = math.ceil(
1933 temp_max_double_escape_blocks)
1934 max_double_escape_blocks += temp_max_double_escape_blocks
1935 temp_max_seats = math.floor(
1936 ((avail_x - (temp_max_double_escape_blocks *
1937 escape_route_width)) / lx_comp_width))
1938 if temp_max_double_escape_blocks > 0:
1939 add_seats = [temp_max_seats // temp_max_double_escape_blocks
1940 for s in range(temp_max_double_escape_blocks)]
1941 if (abs(sum(add_seats) - temp_max_seats) > 0 and
1942 add_seats):
1943 add_seats[-1] += abs(sum(
1944 add_seats) - temp_max_seats)
1945 if add_seats:
1946 if sum(max_seats_double_escape_blocks) == 0:
1947 max_seats_single_escape_blocks = add_seats
1948 else:
1949 max_seats_single_escape_blocks += add_seats
1951 remaining_x_width = avail_x - \
1952 (temp_max_double_escape_blocks*escape_route_width + sum(add_seats) *
1953 lx_comp_width)
1954 temp_num_seats_single_escape = math.floor(
1955 remaining_x_width/lx_comp_width)
1956 if not 0 < i < len(available_x_list):
1957 if temp_num_seats_single_escape < min_seats_single_escape:
1958 req_num_seats_single_escape = (
1959 min_seats_single_escape - temp_num_seats_single_escape)
1960 max_seats_double_escape_blocks[0] = \
1961 max_seats_double_escape_blocks[0] - req_num_seats_single_escape
1962 max_single_escape_blocks = 1
1964 if i == 0:
1965 max_seats_single_escape_blocks = [min_seats_single_escape,
1966 0]
1967 else:
1968 max_seats_single_escape_blocks = [0,
1969 min_seats_single_escape]
1970 elif (min_seats_single_escape < temp_num_seats_single_escape <
1971 2*min_seats_single_escape):
1972 if i == 0:
1973 max_seats_single_escape_blocks = [temp_num_seats_single_escape,
1974 0]
1975 else:
1976 max_seats_single_escape_blocks = [0,
1977 temp_num_seats_single_escape]
1978 max_single_escape_blocks = 1
1979 elif (2*min_seats_single_escape < temp_num_seats_single_escape
1980 <=2*max_obj_single_escape):
1981 smaller_half_of_seats = temp_num_seats_single_escape//2
1982 max_seats_single_escape_blocks = [smaller_half_of_seats,
1983 temp_num_seats_single_escape-smaller_half_of_seats]
1984 max_single_escape_blocks = 2
1985 elif temp_num_seats_single_escape > 2*max_obj_single_escape:
1986 max_seats_single_escape_blocks = [max_obj_single_escape,
1987 max_obj_single_escape]
1988 max_single_escape_blocks = 2
1989 else:
1990 raise NotImplementedError("The requested number of seats "
1991 "cannot be processed.")
1992 else:
1993 if x_max_number < min_seats_single_escape:
1994 max_seats_single_escape_blocks = [x_max_number, 0]
1995 max_single_escape_blocks = 1
1996 elif x_max_number < 2*min_seats_single_escape:
1997 max_seats_single_escape_blocks = [x_max_number, 0]
1998 max_single_escape_blocks = 1
1999 else:
2000 smaller_half_of_seats = x_max_number // 2
2001 max_seats_single_escape_blocks = \
2002 [smaller_half_of_seats, x_max_number -
2003 smaller_half_of_seats]
2004 max_single_escape_blocks = 2
2006 # # todo: generalize implementation using divmod
2007 # if x_max_number > 2*max_obj_single_escape:
2008 # x_width_available = lx - 2*escape_route_width
2009 # x_max_number = math.floor(x_width_available / lx_comp_width)
2010 # if x_max_number > (max_obj_two_escape + 2*max_obj_single_escape):
2011 # x_width_available = lx - 3*escape_route_width
2012 # x_max_number = math.floor(x_width_available / lx_comp_width)
2013 # if x_max_number > (2*max_obj_two_escape + 2*max_obj_single_escape):
2014 # self.logger.warning(f'More than '
2015 # f'{2*max_obj_two_escape + 2*max_obj_single_escape} '
2016 # f'seats in a row are not '
2017 # f'supported. Using the maximum of '
2018 # f'{2*max_obj_two_escape + 2*max_obj_single_escape} '
2019 # f'seats '
2020 # f'instead, subdivided in multiple blocks')
2021 # x_max_number = 2*max_obj_two_escape + 2*max_obj_single_escape
2023 y_width_available = ly - escape_route_width
2024 y_max_number = math.floor(y_width_available / ly_comp_width)
2025 if y_max_number > max_obj_rows_per_block:
2026 temp_max_row_blocks = y_width_available / (
2027 escape_route_width + max_obj_rows_per_block * ly_comp_width)
2028 if temp_max_row_blocks < 1:
2029 max_row_blocks = 1 # only one block with one escape route
2030 max_rows_per_block = [max_obj_rows_per_block]
2031 else:
2032 max_row_blocks = math.floor(temp_max_row_blocks)
2033 max_rows_per_block = [max_obj_rows_per_block]*max_row_blocks
2034 remaining_y_width = y_width_available - \
2035 (max_row_blocks * ly_comp_width
2036 * max_obj_rows_per_block + max_row_blocks*escape_route_width)
2037 temp_num_remaining_rows = math.floor(remaining_y_width /
2038 ly_comp_width)
2039 if temp_num_remaining_rows < min_rows_per_block:
2040 required_rows = min_rows_per_block - temp_num_remaining_rows
2041 max_rows_per_block[0] = max_rows_per_block[0]-required_rows
2042 max_rows_per_block.append(required_rows)
2043 max_row_blocks += 1
2044 else:
2045 max_rows_per_block.append(temp_num_remaining_rows)
2046 max_row_blocks += 1
2047 else:
2048 max_rows_per_block = [y_max_number]
2049 max_row_blocks = 1
2051 max_amount = (sum(max_seats_double_escape_blocks)+sum(
2052 max_seats_single_escape_blocks)) * sum(max_rows_per_block)
2053 if requested_amount > max_amount:
2054 self.logger.warning(
2055 f'You requested an amount of '
2056 f'{requested_amount}, but only '
2057 f'{max_amount} is possible. Using this maximum '
2058 f'allowed amount.')
2059 requested_amount = max_amount
2060 elif requested_amount < max_amount:
2061 # remove outer blocks to reduce total number of available chair
2062 # positions
2063 diff_amount = max_amount - requested_amount
2064 if (diff_amount >
2065 sum(max_rows_per_block)*max_seats_single_escape_blocks[1]):
2066 diff_amount -= max_seats_single_escape_blocks[1]*sum(max_rows_per_block)
2067 max_seats_single_escape_blocks[1] = 0
2068 max_single_escape_blocks = 1
2069 if diff_amount >sum(max_rows_per_block)*max_seats_single_escape_blocks[0]:
2070 diff_amount -= max_seats_single_escape_blocks[0]*sum(max_rows_per_block)
2071 max_seats_single_escape_blocks[0] = 0
2072 max_single_escape_blocks = 0
2074 # set number of rows to maximum number in y direction
2075 obj_locations = []
2076 y_loc = global_y_position
2077 if 'MinXEscape' in available_x_list:
2078 global_x_position += unavail_x_pos[0][1]
2079 for num_rows_in_block in max_rows_per_block:
2080 obj_rows = num_rows_in_block
2081 for row in range(obj_rows):
2082 if row == 0:
2083 y_loc += ly_comp / 2
2084 else:
2085 y_loc += ly_comp_width
2086 x_loc = global_x_position
2087 if max_seats_single_escape_blocks[0] > 0:
2088 for x_pos in range(max_seats_single_escape_blocks[0]):
2089 if x_pos == 0:
2090 x_loc += lx_comp_width / 2
2091 else:
2092 x_loc += lx_comp_width
2093 pos = gp_Pnt(x_loc, y_loc, furniture_surface_z)
2094 obj_locations.append(pos)
2095 if len(obj_locations) == requested_amount:
2096 break
2097 x_loc += lx_comp_width / 2
2098 if len(obj_locations) == requested_amount:
2099 break
2100 if max_seats_double_escape_blocks[0] > 0:
2101 for seats_in_row in max_seats_double_escape_blocks:
2102 x_loc += escape_route_width
2103 for x_pos in range(seats_in_row):
2104 if x_pos == 0:
2105 x_loc += lx_comp_width / 2
2106 else:
2107 x_loc += lx_comp_width
2108 pos = gp_Pnt(x_loc, y_loc, furniture_surface_z)
2109 obj_locations.append(pos)
2110 if len(obj_locations) == requested_amount:
2111 break
2112 x_loc += lx_comp_width / 2
2113 if len(obj_locations) == requested_amount:
2114 break
2115 if len(obj_locations) == requested_amount:
2116 break
2117 if max_seats_single_escape_blocks[1] > 0:
2118 if 'MinXEscape' in available_x_list and x_loc \
2119 == global_x_position:
2120 pass
2121 else:
2122 x_loc += escape_route_width
2123 for x_pos in range(max_seats_single_escape_blocks[1]):
2124 if x_pos == 0:
2125 x_loc += lx_comp_width / 2
2126 else:
2127 x_loc += lx_comp_width
2128 pos = gp_Pnt(x_loc, y_loc, furniture_surface_z)
2129 obj_locations.append(pos)
2130 if len(obj_locations) == requested_amount:
2131 break
2132 x_loc += lx_comp_width / 2
2133 if len(obj_locations) == requested_amount:
2134 break
2135 y_loc += ly_comp / 2 + escape_route_width
2136 if switch:
2137 old_obj_locations = obj_locations
2138 new_obj_locations = []
2139 for loc in obj_locations:
2140 new_obj_locations.append(gp_Pnt(loc.Y(), loc.X(), loc.Z()))
2141 obj_locations = new_obj_locations
2142 obj_trsfs = PyOCCTools.generate_obj_trsfs(obj_locations,
2143 compound_center_lower,
2144 rotation_angle)
2145 footprints = []
2146 for trsf in obj_trsfs:
2147 footprints.append(
2148 BRepBuilderAPI_Transform(footprint_shape, trsf).Shape())
2149 escape_shape = PyOCCTools.triangulate_bound_shape(furniture_surface,
2150 footprints)
2151 add_new_escape_shapes = []
2152 for door_shape in doors:
2153 reverse=False
2154 (min_box, max_box) = PyOCCTools.simple_bounding_box([
2155 door_shape])
2156 door_lower_pnt1 = gp_Pnt(*min_box)
2157 door_lower_pnt2 = gp_Pnt(max_box[0], max_box[1], min_box[2])
2158 base_line_pnt1 = door_lower_pnt1
2159 base_line_pnt2 = door_lower_pnt2
2160 door_width = door_lower_pnt1.Distance(door_lower_pnt2)
2161 p1 = PyOCCTools.get_points_of_minimum_point_shape_distance(
2162 door_lower_pnt1, escape_shape)
2163 p2 = PyOCCTools.get_points_of_minimum_point_shape_distance(
2164 door_lower_pnt2, escape_shape)
2165 if ((p1[0][2] or p2[0][2]) and BRepExtrema_DistShapeShape(
2166 door_shape, escape_shape,
2167 Extrema_ExtFlag_MIN).Value()) > 0.001:
2168 # add closest path to escape route
2169 # ensure that neither the lower points of the door nor
2170 # the door shape itself touches the escape
2171 # shape. The escape shape may be smaller than the escape
2172 # route shape
2173 if p1[0][1].Distance(p2[0][1]) > 0.9:
2174 # check of the closest points on the escape_shape are
2175 # very close to each other. In this case, the generation
2176 # of a new escape path cannot be guaranteed
2177 reverse = False
2178 # add_escape_shape = PyOCCTools.make_faces_from_pnts([p1[0][0],
2179 # p2[0][0],
2180 # p2[0][1], p1[0][1]])
2181 # if add_escape_shape.IsNull():
2182 add_escape_shape = PyOCCTools.get_projection_of_bounding_box(
2183 [BRepBuilderAPI_MakeVertex(
2184 p).Vertex()
2185 for p in [p1[0][0], p2[0][0],
2186 p2[0][1],
2187 p1[0][1]]], proj_type='z', value=p1[0][1].Z())
2188 add_new_escape_shapes.append(add_escape_shape)
2189 else:
2190 if p1[0][2] > p2[0][2]:
2191 base_line_pnt1 = p1[0][0]
2192 base_line_pnt2 = p1[0][1]
2193 else:
2194 base_line_pnt1 = p2[0][0]
2195 base_line_pnt2 = p2[0][1]
2197 # add square space in front of doors, depth = escape route width
2198 d = gp_Dir(gp_Vec(base_line_pnt1, base_line_pnt2))
2199 new_dir = d.Rotated(gp_Ax1(base_line_pnt1, gp_Dir(0, 0, 1)),
2200 math.radians(90))
2201 moved_pnt1 = PyOCCTools.move_bound_in_direction_of_normal(
2202 BRepBuilderAPI_MakeVertex(base_line_pnt1).Vertex(),
2203 max(escape_route_width, door_width),
2204 move_dir=new_dir, reverse=reverse)
2205 moved_dist = BRepExtrema_DistShapeShape(moved_pnt1,
2206 furniture_surface,
2207 Extrema_ExtFlag_MIN).Value()
2208 if abs(moved_dist) > 1e-3:
2209 reverse = True
2210 moved_pnt1 = PyOCCTools.move_bound_in_direction_of_normal(
2211 BRepBuilderAPI_MakeVertex(base_line_pnt1).Vertex(),
2212 max(escape_route_width, door_width), move_dir=new_dir, reverse=reverse)
2213 if reverse:
2214 moved_pnt2 = PyOCCTools.move_bound_in_direction_of_normal(
2215 BRepBuilderAPI_MakeVertex(base_line_pnt2).Vertex(),
2216 max(escape_route_width, door_width), move_dir=new_dir, reverse=reverse)
2217 else:
2218 moved_pnt2 = PyOCCTools.move_bound_in_direction_of_normal(
2219 BRepBuilderAPI_MakeVertex(base_line_pnt2).Vertex(),
2220 max(escape_route_width, door_width), move_dir=new_dir,
2221 reverse=reverse)
2222 add_escape_shape = PyOCCTools.make_faces_from_pnts([base_line_pnt1,
2223 base_line_pnt2,
2224 BRep_Tool.Pnt(moved_pnt2),
2225 BRep_Tool.Pnt(moved_pnt1)])
2226 add_new_escape_shapes.append(add_escape_shape)
2227 distance_new_escape = BRepExtrema_DistShapeShape(
2228 add_escape_shape, escape_shape).Value()
2229 distance_mp1 = BRepExtrema_DistShapeShape(
2230 moved_pnt1, escape_shape).Value()
2231 distance_mp2 = BRepExtrema_DistShapeShape(
2232 moved_pnt2, escape_shape).Value()
2233 if distance_new_escape > 1e-3:
2234 new_dist_pnts2 = PyOCCTools.get_points_of_minimum_point_shape_distance(
2235 BRep_Tool.Pnt(moved_pnt2), escape_shape)
2236 new_dist_pnts1 = (
2237 PyOCCTools.get_points_of_minimum_point_shape_distance(
2238 BRep_Tool.Pnt(moved_pnt1), escape_shape))
2239 add_escape_shape2 = PyOCCTools.make_faces_from_pnts([
2240 new_dist_pnts1[0][0], new_dist_pnts2[0][0],
2241 new_dist_pnts2[0][1], new_dist_pnts1[0][1]])
2242 add_new_escape_shapes.append(add_escape_shape2)
2243 elif (distance_mp1 and distance_mp2) > 1e-3:
2244 if reverse:
2245 min_dist_dir = gp_Dir(*[-1 * d for d in new_dir.Coord()])
2246 else:
2247 min_dist_dir = new_dir
2248 extruded_escape_shape = PyOCCTools.extrude_face_in_direction(escape_shape,
2249 0.1)
2250 min_p1_new = PyOCCTools.find_min_distance_along_direction(
2251 BRep_Tool.Pnt(moved_pnt1), min_dist_dir,
2252 extruded_escape_shape)
2253 min_p2_new = PyOCCTools.find_min_distance_along_direction(
2254 BRep_Tool.Pnt(moved_pnt2), min_dist_dir,
2255 extruded_escape_shape)
2257 add_escape_shape3 = \
2258 PyOCCTools.get_projection_of_bounding_box([moved_pnt1, moved_pnt2,
2259 *[BRepBuilderAPI_MakeVertex(
2260 p).Vertex()
2261 for p in
2262 [min_p2_new[1], min_p1_new[1]]]],
2263 proj_type='z',
2264 value=p1[0][1].Z())
2265 add_new_escape_shapes.append(add_escape_shape3)
2267 if add_new_escape_shapes:
2268 sewed_shape = PyOCCTools.fuse_shapes([escape_shape,
2269 *add_new_escape_shapes])
2270 area_escape_shape = PyOCCTools.get_shape_area(escape_shape)
2271 add_escape_areas = [PyOCCTools.get_shape_area(add_escape) for
2272 add_escape in add_new_escape_shapes]
2273 sewed_area = PyOCCTools.get_shape_area(sewed_shape)
2274 if (abs((area_escape_shape + sum(add_escape_areas)) - sewed_area)
2275 > 1):
2276 sewed_shape = PyOCCTools.fuse_shapes([escape_shape,
2277 *add_new_escape_shapes])
2278 else:
2279 sewed_shape = escape_shape
2280 # unified_sewed_shape = PyOCCTools.unify_shape(sewed_shape)
2281 solid_sewed_shape = PyOCCTools.make_solid_from_shape(PyOCCTools,
2282 sewed_shape)
2283 # unified_sewed_shape = PyOCCTools.unify_shape(solid_sewed_shape)
2285 cleaned_obj_trsfs = []
2286 cleaned_obj_locations = []
2287 cleaned_footprints = []
2288 escape_area = PyOCCTools.get_shape_area(sewed_shape)
2289 box_footprints = [PyOCCTools.enlarge_bounding_box_shape_in_dir(f) for
2290 f in footprints]
2291 solid_box_footprints = [PyOCCTools.make_solid_from_shape(
2292 PyOCCTools, ft) for ft in box_footprints]
2294 for i, footprint in enumerate(solid_box_footprints):
2295 foot_dist = BRepExtrema_DistShapeShape(sewed_shape, footprint,
2296 Extrema_ExtFlag_MIN).Value()
2297 if abs(foot_dist) < 1e-3:
2298 common_algo = BRepAlgoAPI_Common(sewed_shape, footprint)
2299 common_algo.Build()
2300 if not common_algo.IsDone():
2301 raise RuntimeError("Intersection computation failed.")
2302 overlap_shape = common_algo.Shape()
2303 # explorer = TopExp_Explorer(overlap_shape, TopAbs_SOLID)
2304 # has_overlap = explorer.More()
2305 if overlap_shape:
2306 overlap_area = PyOCCTools.get_shape_area(overlap_shape)
2307 # print(overlap_area)
2308 if overlap_area < 0.02: # allow small overlapping
2309 # with escape routes (10cm2)
2310 cleaned_obj_trsfs.append(obj_trsfs[i])
2311 cleaned_footprints.append(footprints[i])
2312 cleaned_obj_locations.append(obj_locations[i])
2313 else:
2314 cleaned_obj_trsfs.append(obj_trsfs[i])
2315 cleaned_footprints.append(footprints[i])
2316 cleaned_obj_locations.append(obj_locations[i])
2317 else:
2318 cleaned_obj_trsfs.append(obj_trsfs[i])
2319 cleaned_obj_locations.append(obj_locations[i])
2320 cleaned_footprints.append(footprints[i])
2322 self.logger.warning(f"removed {len(obj_trsfs)-len(cleaned_obj_trsfs)} "
2323 f"furniture objects to guarantee escape route "
2324 f"compliance. A total number of "
2325 f"{len(cleaned_obj_trsfs)} furniture elements is "
2326 f"positioned, based on "
2327 f"{self.playground.sim_settings.furniture_amount} "
2328 f"requested elements. ")
2329 return cleaned_obj_locations, cleaned_obj_trsfs
2332def create_stl_from_shape_single_solid_name(triangulated_shape,
2333 stl_file_path_name, solid_name):
2334 stl_writer = StlAPI_Writer()
2335 stl_writer.SetASCIIMode(True)
2336 stl_writer.Write(triangulated_shape, stl_file_path_name)
2337 sb_mesh = mesh.Mesh.from_file(stl_file_path_name)
2338 with (open(stl_file_path_name,
2339 'wb+') as output_file):
2340 sb_mesh.save(solid_name,
2341 output_file,
2342 mode=stl.Mode.ASCII)
2343 output_file.close()