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
« 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
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 },
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}
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
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 }
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):
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
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]
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')
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 }