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