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

1import math 

2import pathlib 

3import random 

4import shutil 

5import tempfile 

6import logging 

7from pathlib import Path 

8 

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 

26 

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 

45 

46logger = logging.getLogger(__name__) 

47 

48 

49class CreateOpenFOAMGeometry(ITask): 

50 """This ITask initializes the OpenFOAM Geometry. 

51 """ 

52 

53 reads = ('openfoam_case', 'elements') 

54 touches = ('openfoam_case', 'openfoam_elements') 

55 

56 single_use = False 

57 

58 def __init__(self, playground): 

59 super().__init__(playground) 

60 

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) 

82 

83 return openfoam_case, openfoam_elements 

84 

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 

114 

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 

120 

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) 

133 

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 

143 

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])]) 

196 

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([ 

208 

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] 

242 

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) 

249 

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 

257 

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 

281 

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 

302 

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) 

307 

308 front_surface = PyOCCTools.move_bound_in_direction_of_normal( 

309 back_surface, move_dist=heater_depth, 

310 reverse=move_reversed_flag) 

311 

312 front_surface_min_max = PyOCCTools.simple_bounding_box(front_surface) 

313 back_surface_min_max = PyOCCTools.simple_bounding_box(back_surface) 

314 

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([ 

324 

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 

340 

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 = [] 

347 

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. 

471 

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") 

523 

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) 

536 

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: 

545 

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) 

637 

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]]) 

670 

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 

683 

684 if len(outlets) == 0: 

685 # define additional outlet below door 

686 

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) 

721 

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 

777 

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] 

800 

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") 

851 

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) 

862 

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]) 

868 

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]) 

924 

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 

936 

937 return inlet, outlet 

938 

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 

984 

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 

1029 

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 

1034 

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 

1051 

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 = [] 

1067 

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) 

1095 

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) 

1124 

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 

1132 

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) 

1136 

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)} 

1153 

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'] 

1225 

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 

1301 

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() 

1321 

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) 

1357 

1358 openfoam_case.furniture_trsfs = furniture_trsfs 

1359 openfoam_case.chair_trsfs = global_chair_trsfs 

1360 return furniture_items 

1361 

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 

1372 

1373 def create_people_shapes(self, openfoam_case, furniture_surface): 

1374 

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 

1386 

1387 if self.playground.sim_settings.people_setting in ['Seated']: 

1388 available_trsfs = openfoam_case.chair_trsfs 

1389 

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 

1438 

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 

1455 

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) 

1478 

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) 

1491 

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) 

1511 

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) 

1521 

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) 

1532 

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() 

1539 

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] 

1543 

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] 

1547 

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]) 

1552 

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)) 

1557 

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 

1566 

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 

1592 

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() 

1610 

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 

1649 

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 

1656 

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] 

1660 

1661 lx_comp_width = max(lx_comp, min_x_space) 

1662 ly_comp_width = ly_comp + min_y_distance 

1663 

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])]) 

1686 

1687 skip_scan_line = False 

1688 if ly > lx and allow_skip_scanline: 

1689 skip_scan_line = True 

1690 

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) 

1737 

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())) 

1759 

1760 

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 = [] 

1790 

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) 

1819 

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] 

1862 

1863 

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) 

1885 

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 

1915 

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 

1950 

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 

1963 

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 

2005 

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 

2022 

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 

2050 

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 

2073 

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] 

2196 

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) 

2256 

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) 

2266 

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) 

2284 

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] 

2293 

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]) 

2321 

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 

2330 

2331 

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()