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

95 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-01 10:24 +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 

11 

12body_part_boundary_conditions = { 

13 # detailed temperatures represent clothing/skin temperatures according to 

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

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

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

17 # body part definitions (19 body parts). 

18 'FullBody': 

19 { 

20 'T': 32, 

21 'power_fraction': 1, 

22 'hr_hc': 0 

23 }, 

24 'manikinsittinghead': 

25 { 

26 'T': 36, 

27 'power_fraction': 0.3, 

28 'hr_hc': 0 

29 }, 

30 'manikinsittingbody': 

31 { 

32 'T': 30, 

33 'power_fraction': 0.7, 

34 'hr_hc': 0 

35 }, 

36 'abdomen': 

37 { 

38 'T': 27.25, 

39 'power_fraction': 0, 

40 'hr_hc': 7.81 

41 }, 

42 

43 'head_back': 

44 { 

45 'T': 32.43, 

46 'power_fraction': 0, 

47 'hr_hc': 9.89 

48 }, 

49 'head_face': 

50 { 

51 'T': 35.9, 

52 'power_fraction': 0, 

53 'hr_hc': 9.92 

54 }, 

55 'left_foot': 

56 { 

57 'T': 29.33, 

58 'power_fraction': 0, 

59 'hr_hc': 20.615 

60 }, 

61 'left_hand': 

62 { 

63 'T': 32, 

64 'power_fraction': 0, 

65 'hr_hc': 8.2 

66 }, 

67 'left_lower_arm': 

68 { 

69 'T': 26.225, 

70 'power_fraction': 0, 

71 'hr_hc': 7.815 

72 }, 

73 'left_lower_leg': 

74 { 

75 'T': 25.21, 

76 'power_fraction': 0, 

77 'hr_hc': 14.32 

78 }, 

79 'left_shoulder': 

80 { 

81 'T': 25.865, 

82 'power_fraction': 0, 

83 'hr_hc': 8.35 

84 }, 

85 'left_upper_arm': 

86 { 

87 'T': 26.225, 

88 'power_fraction': 0, 

89 'hr_hc': 7.815 

90 }, 

91 'left_upper_leg': 

92 { 

93 'T': 26.195, 

94 'power_fraction': 0, 

95 'hr_hc': 4.06 

96 }, 

97 'neck': 

98 { 

99 'T': 31.25, 

100 'power_fraction': 0, 

101 'hr_hc': 9.27 

102 }, 

103 'right_foot': 

104 { 

105 'T': 29.33, 

106 'power_fraction': 0, 

107 'hr_hc': 20.615 

108 }, 

109 'right_hand': 

110 { 

111 'T': 32, 

112 'power_fraction': 0, 

113 'hr_hc': 8.2 

114 }, 

115 'right_lower_arm': 

116 { 

117 'T': 26.225, 

118 'power_fraction': 0, 

119 'hr_hc': 7.815 

120 }, 

121 'right_lower_leg': 

122 { 

123 'T': 25.21, 

124 'power_fraction': 0, 

125 'hr_hc': 14.32 

126 }, 

127 'right_shoulder': 

128 { 

129 'T': 25.865, 

130 'power_fraction': 0, 

131 'hr_hc': 8.35 

132 }, 

133 'right_upper_arm': 

134 { 

135 'T': 26.225, 

136 'power_fraction': 0, 

137 'hr_hc': 7.815 

138 }, 

139 'right_upper_leg': 

140 { 

141 'T': 26.195, 

142 'power_fraction': 0, 

143 'hr_hc': 4.06 

144 }, 

145 'thorax': 

146 { 

147 'T': 26.645, 

148 'power_fraction': 0, 

149 'hr_hc': 7.985 

150 }, 

151} 

152 

153 

154class BodyPart(OpenFOAMBaseBoundaryFields, OpenFOAMBaseElement): 

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

156 super().__init__() 

157 self.radiation_model = person.radiation_model 

158 self.key = key 

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

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

161 self.bbox_min_max = bbox_min_max 

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

163 self.stl_name) 

164 self.patch_info_type = person.patch_info_type 

165 self.refinement_level = person.refinement_level 

166 self.tri_geom = PyOCCTools.triangulate_bound_shape(shape) 

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

168 if not bbox_min_max: 

169 self.bbox_min_max = PyOCCTools.simple_bounding_box(shape) 

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

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

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

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

174 person.scale_surface_factor) 

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

176 self.heat_flux = body_part_boundary_conditions[key][ 

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

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

179 # indoor air temperature 

180 

181 def set_boundary_conditions(self): 

182 if self.radiation_model == 'none': 

183 qr = 'none' 

184 else: 

185 qr = 'qr' 

186 if self.power == 0: 

187 # self.T = \ 

188 # {'type': 'externalWallHeatFluxTemperature', 

189 # 'mode': 'flux', 

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

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

192 # 'qrRelaxation': 0.003, 

193 # 'relaxation': 1.0, 

194 # 'kappaMethod': 'fluidThermo', 

195 # 'kappa': 'fluidThermo', 

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

197 # } 

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

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

200 } 

201 else: 

202 self.T = \ 

203 {'type': 'externalWallHeatFluxTemperature', 

204 'mode': 'power', 

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

206 'Q': f'{self.power}', 

207 'qrRelaxation': 0.003, 

208 'relaxation': 1.0, 

209 'kappaMethod': 'fluidThermo', 

210 'kappa': 'fluidThermo', 

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

212 } 

213 

214 

215class People(OpenFOAMBaseBoundaryFields, OpenFOAMBaseElement): 

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

217 people_type, radiation_model, scale, 

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

219 increase_small_refinement=0.10, 

220 increase_large_refinement=0.20, add_scaled_shape = False): 

221 

222 super().__init__() 

223 self.radiation_model = radiation_model 

224 self.bbox_min_max = bbox_min_max 

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

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

227 self.triSurface_path = triSurface_path 

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

229 self.stl_name) 

230 self.patch_info_type = 'wall' 

231 self.refinement_level = [4, 4] 

232 self.tri_geom = PyOCCTools.triangulate_bound_shape(shape) 

233 # export person geometry for refinementRegion of person 

234 stl_writer = StlAPI_Writer() 

235 stl_writer.SetASCIIMode(True) 

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

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

238 self.power = power 

239 self.temperature = temperature 

240 self.scale_surface_factor = scale 

241 if not bbox_min_max: 

242 self.bbox_min_max = PyOCCTools.simple_bounding_box(shape) 

243 body_shapes_dict = self.split_body_part_shapes(person_path, shape, 

244 triSurface_path, trsf) 

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

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

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

248 if add_scaled_shape: 

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

250 0.03) 

251 else: 

252 self.scaled_surface = None 

253 

254 # self.refinement_zone_small = [] 

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

256 # in self.bbox_min_max[0]]) 

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

258 # in self.bbox_min_max[1]]) 

259 # self.refinement_zone_level_small = [0, 

260 # self.refinement_level[0]] 

261 # self.refinement_zone_large = [] 

262 # self.refinement_zone_large.append( 

263 # [c - increase_large_refinement for c in 

264 # self.bbox_min_max[0]]) 

265 # self.refinement_zone_large.append( 

266 # [c + increase_large_refinement for c in 

267 # self.bbox_min_max[1]]) 

268 # self.refinement_zone_level_large = [0, 

269 # self.refinement_level[0]-1] 

270 

271 @staticmethod 

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

273 person_meshes = [] 

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

275 person_meshes.append(m) 

276 if len(person_meshes) > 1: 

277 temp_path = triSurface_path / 'Temp/person' 

278 temp_path.mkdir(exist_ok=True) 

279 for m in person_meshes: 

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

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

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

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

284 mode=stl.Mode.ASCII) 

285 output_file.close() 

286 part_dict = {} 

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

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

289 part_shape = TopoDS_Shape() 

290 stl_reader = StlAPI_Reader() 

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

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

293 part_dict.update({part_name: part_shape}) 

294 return part_dict 

295 elif len(person_meshes) == 1: 

296 # keep original shape 

297 return {'FullBody': full_shape} 

298 if len(person_meshes) == 0: 

299 raise Exception('No meshes found') 

300 

301 def set_boundary_conditions(self): 

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

303 # body_part.set_boundary_conditions() 

304 if self.radiation_model == 'none': 

305 qr = 'none' 

306 else: 

307 qr = 'qr' 

308 self.T = \ 

309 {'type': 'externalWallHeatFluxTemperature', 

310 'mode': 'power', 

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

312 'Q': f'{self.power}', 

313 'qrRelaxation': 0.003, 

314 'relaxation': 1.0, 

315 'kappaMethod': 'fluidThermo', 

316 'kappa': 'fluidThermo', 

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

318 } 

319