Coverage for bim2sim / plugins / PluginOpenFOAM / bim2sim_openfoam / openfoam_elements / people.py: 0%

96 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-18 09:34 +0000

1import stl 

2from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Transform 

3from OCC.Core.StlAPI import StlAPI_Reader, StlAPI_Writer 

4from OCC.Core.TopoDS import TopoDS_Shape 

5from stl import mesh 

6from bim2sim.plugins.PluginOpenFOAM.bim2sim_openfoam.openfoam_elements.openfoam_base_boundary_conditions import \ 

7 OpenFOAMBaseBoundaryFields 

8from bim2sim.plugins.PluginOpenFOAM.bim2sim_openfoam.openfoam_elements.openfoam_base_element import \ 

9 OpenFOAMBaseElement 

10from bim2sim.utilities.pyocc_tools import PyOCCTools 

11from bim2sim.plugins.PluginOpenFOAM.bim2sim_openfoam.utils.openfoam_utils import \ 

12 OpenFOAMUtils as of_utils 

13 

14 

15body_part_boundary_conditions = { 

16 # detailed temperatures represent clothing/skin temperatures according to 

17 # Yamamoto et al. 2023, Case 3 (3rd) (average for left and right 

18 # temperatures), and face temperature has been defined according to Zhu 

19 # et al. (2007). All body part temperatures have been mapped to MORPHEUS 

20 # body part definitions (19 body parts). 

21 'FullBody': 

22 { 

23 'T': 32, 

24 'power_fraction': 1, 

25 'hr_hc': 0 

26 }, 

27 'manikinsittinghead': 

28 { 

29 'T': 36, 

30 'power_fraction': 0.3, 

31 'hr_hc': 0 

32 }, 

33 'manikinsittingbody': 

34 { 

35 'T': 30, 

36 'power_fraction': 0.7, 

37 'hr_hc': 0 

38 }, 

39 'abdomen': 

40 { 

41 'T': 27.25, 

42 'power_fraction': 0, 

43 'hr_hc': 7.81 

44 }, 

45 

46 'head_back': 

47 { 

48 'T': 32.43, 

49 'power_fraction': 0, 

50 'hr_hc': 9.89 

51 }, 

52 'head_face': 

53 { 

54 'T': 35.9, 

55 'power_fraction': 0, 

56 'hr_hc': 9.92 

57 }, 

58 'left_foot': 

59 { 

60 'T': 29.33, 

61 'power_fraction': 0, 

62 'hr_hc': 20.615 

63 }, 

64 'left_hand': 

65 { 

66 'T': 32, 

67 'power_fraction': 0, 

68 'hr_hc': 8.2 

69 }, 

70 'left_lower_arm': 

71 { 

72 'T': 26.225, 

73 'power_fraction': 0, 

74 'hr_hc': 7.815 

75 }, 

76 'left_lower_leg': 

77 { 

78 'T': 25.21, 

79 'power_fraction': 0, 

80 'hr_hc': 14.32 

81 }, 

82 'left_shoulder': 

83 { 

84 'T': 25.865, 

85 'power_fraction': 0, 

86 'hr_hc': 8.35 

87 }, 

88 'left_upper_arm': 

89 { 

90 'T': 26.225, 

91 'power_fraction': 0, 

92 'hr_hc': 7.815 

93 }, 

94 'left_upper_leg': 

95 { 

96 'T': 26.195, 

97 'power_fraction': 0, 

98 'hr_hc': 4.06 

99 }, 

100 'neck': 

101 { 

102 'T': 31.25, 

103 'power_fraction': 0, 

104 'hr_hc': 9.27 

105 }, 

106 'right_foot': 

107 { 

108 'T': 29.33, 

109 'power_fraction': 0, 

110 'hr_hc': 20.615 

111 }, 

112 'right_hand': 

113 { 

114 'T': 32, 

115 'power_fraction': 0, 

116 'hr_hc': 8.2 

117 }, 

118 'right_lower_arm': 

119 { 

120 'T': 26.225, 

121 'power_fraction': 0, 

122 'hr_hc': 7.815 

123 }, 

124 'right_lower_leg': 

125 { 

126 'T': 25.21, 

127 'power_fraction': 0, 

128 'hr_hc': 14.32 

129 }, 

130 'right_shoulder': 

131 { 

132 'T': 25.865, 

133 'power_fraction': 0, 

134 'hr_hc': 8.35 

135 }, 

136 'right_upper_arm': 

137 { 

138 'T': 26.225, 

139 'power_fraction': 0, 

140 'hr_hc': 7.815 

141 }, 

142 'right_upper_leg': 

143 { 

144 'T': 26.195, 

145 'power_fraction': 0, 

146 'hr_hc': 4.06 

147 }, 

148 'thorax': 

149 { 

150 'T': 26.645, 

151 'power_fraction': 0, 

152 'hr_hc': 7.985 

153 }, 

154} 

155 

156 

157class BodyPart(OpenFOAMBaseBoundaryFields, OpenFOAMBaseElement): 

158 def __init__(self, person, key, shape, bbox_min_max=None): 

159 super().__init__() 

160 self.radiation_model = person.radiation_model 

161 self.key = key 

162 self.solid_name = person.solid_name + '_' + key 

163 self.stl_name = self.solid_name + '.stl' 

164 self.bbox_min_max = bbox_min_max 

165 self.stl_file_path_name = (person.triSurface_path.as_posix() + '/' + 

166 self.stl_name) 

167 self.patch_info_type = person.patch_info_type 

168 self.refinement_level = person.refinement_level 

169 self.tri_geom = PyOCCTools.triangulate_bound_shape(shape) 

170 self.point_in_shape = PyOCCTools.get_center_of_volume(self.tri_geom) 

171 if not bbox_min_max: 

172 self.bbox_min_max = PyOCCTools.simple_bounding_box(shape) 

173 self.power = body_part_boundary_conditions[key]['power_fraction'] * person.power 

174 self.temperature = body_part_boundary_conditions[key]['T'] 

175 self.area = PyOCCTools.get_shape_area(self.tri_geom) 

176 self.scaled_surface = PyOCCTools.scale_shape_absolute(self.tri_geom, 

177 person.scale_surface_factor) 

178 self.hr_hc = body_part_boundary_conditions[key]['hr_hc'] 

179 self.heat_flux = body_part_boundary_conditions[key][ 

180 'hr_hc']*(self.temperature-21) 

181 # todo: remove hardcoded 21 degC and replace with actual 

182 # indoor air temperature 

183 

184 def set_boundary_conditions(self): 

185 if self.radiation_model == 'none': 

186 qr = 'none' 

187 else: 

188 qr = 'qr' 

189 if self.power == 0: 

190 # self.T = \ 

191 # {'type': 'externalWallHeatFluxTemperature', 

192 # 'mode': 'flux', 

193 # 'qr': f"{qr}", 

194 # 'q': f'{self.heat_flux}', 

195 # 'qrRelaxation': 0.003, 

196 # 'relaxation': 1.0, 

197 # 'kappaMethod': 'fluidThermo', 

198 # 'kappa': 'fluidThermo', 

199 # 'value': f'uniform {self.temperature + 273.15}' 

200 # } 

201 self.T = {'type': 'fixedValue', 

202 'value': f'uniform ' 

203 f'{of_utils.float_cutoff(self.temperature + 273.15)}' 

204 } 

205 else: 

206 self.T = \ 

207 {'type': 'externalWallHeatFluxTemperature', 

208 'mode': 'power', 

209 'qr': f"{qr}", 

210 'Q': f'{of_utils.float_cutoff(self.power)}', 

211 'qrRelaxation': 0.003, 

212 'relaxation': 1.0, 

213 'kappaMethod': 'fluidThermo', 

214 'kappa': 'fluidThermo', 

215 'value': f'uniform ' 

216 f'{of_utils.float_cutoff(self.temperature + 273.15)}' 

217 } 

218 

219 

220class People(OpenFOAMBaseBoundaryFields, OpenFOAMBaseElement): 

221 def __init__(self, shape, trsf, person_path, triSurface_path, 

222 people_type, radiation_model, scale, 

223 bbox_min_max=None, solid_name='person', power=120, temperature=32, 

224 increase_small_refinement=0.10, 

225 increase_large_refinement=0.20, add_scaled_shape = False): 

226 

227 super().__init__() 

228 self.radiation_model = radiation_model 

229 self.bbox_min_max = bbox_min_max 

230 self.solid_name = solid_name + '_' + people_type 

231 self.stl_name = self.solid_name + '.stl' 

232 self.triSurface_path = triSurface_path 

233 self.stl_file_path_name = (triSurface_path.as_posix() + '/' + 

234 self.stl_name) 

235 self.patch_info_type = 'wall' 

236 self.refinement_level = [4, 4] 

237 self.tri_geom = PyOCCTools.triangulate_bound_shape(shape) 

238 # export person geometry for refinementRegion of person 

239 stl_writer = StlAPI_Writer() 

240 stl_writer.SetASCIIMode(True) 

241 stl_writer.Write(self.tri_geom, self.stl_file_path_name) 

242 self.point_in_shape = PyOCCTools.get_center_of_volume(self.tri_geom) 

243 self.power = power 

244 self.temperature = temperature 

245 self.scale_surface_factor = scale 

246 if not bbox_min_max: 

247 self.bbox_min_max = PyOCCTools.simple_bounding_box(shape) 

248 body_shapes_dict = self.split_body_part_shapes(person_path, shape, 

249 triSurface_path, trsf) 

250 self.body_parts_dict = {key: BodyPart(self, key, value) 

251 for key, value in body_shapes_dict.items()} 

252 self.area = PyOCCTools.get_shape_area(self.tri_geom) 

253 if add_scaled_shape: 

254 self.scaled_surface = PyOCCTools.create_offset_shape(self.tri_geom, 

255 0.03) 

256 else: 

257 self.scaled_surface = None 

258 

259 # self.refinement_zone_small = [] 

260 # self.refinement_zone_small.append([c - increase_small_refinement for c 

261 # in self.bbox_min_max[0]]) 

262 # self.refinement_zone_small.append([c + increase_small_refinement for c 

263 # in self.bbox_min_max[1]]) 

264 # self.refinement_zone_level_small = [0, 

265 # self.refinement_level[0]] 

266 # self.refinement_zone_large = [] 

267 # self.refinement_zone_large.append( 

268 # [c - increase_large_refinement for c in 

269 # self.bbox_min_max[0]]) 

270 # self.refinement_zone_large.append( 

271 # [c + increase_large_refinement for c in 

272 # self.bbox_min_max[1]]) 

273 # self.refinement_zone_level_large = [0, 

274 # self.refinement_level[0]-1] 

275 

276 @staticmethod 

277 def split_body_part_shapes(person_path, full_shape, triSurface_path, trsf): 

278 person_meshes = [] 

279 for m in mesh.Mesh.from_multi_file(person_path): 

280 person_meshes.append(m) 

281 if len(person_meshes) > 1: 

282 temp_path = triSurface_path / 'Temp/person' 

283 temp_path.mkdir(exist_ok=True) 

284 for m in person_meshes: 

285 curr_name = temp_path.as_posix() + '/' + str(m.name, 

286 encoding='utf-8') + '.stl' 

287 with open(curr_name, 'wb+') as output_file: 

288 m.save(str(m.name, encoding='utf-8'), output_file, 

289 mode=stl.Mode.ASCII) 

290 output_file.close() 

291 part_dict = {} 

292 for part_stl in temp_path.glob('*'): 

293 part_name = part_stl.name.split('.')[0] 

294 part_shape = TopoDS_Shape() 

295 stl_reader = StlAPI_Reader() 

296 stl_reader.Read(part_shape, str(part_stl)) 

297 part_shape = BRepBuilderAPI_Transform(part_shape, trsf).Shape() 

298 part_dict.update({part_name: part_shape}) 

299 return part_dict 

300 elif len(person_meshes) == 1: 

301 # keep original shape 

302 return {'FullBody': full_shape} 

303 if len(person_meshes) == 0: 

304 raise Exception('No meshes found') 

305 

306 def set_boundary_conditions(self): 

307 # for body_part in self.body_parts_dict.values(): 

308 # body_part.set_boundary_conditions() 

309 if self.radiation_model == 'none': 

310 qr = 'none' 

311 else: 

312 qr = 'qr' 

313 self.T = \ 

314 {'type': 'externalWallHeatFluxTemperature', 

315 'mode': 'power', 

316 'qr': f"{qr}", 

317 'Q': f'{of_utils.float_cutoff(self.power)}', 

318 'qrRelaxation': 0.003, 

319 'relaxation': 1.0, 

320 'kappaMethod': 'fluidThermo', 

321 'kappa': 'fluidThermo', 

322 'value': f'uniform ' 

323 f'{of_utils.float_cutoff(self.temperature + 273.15)}' 

324 } 

325