Coverage for bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_idf_cfd_export.py: 0%
122 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
1"""Module to export space boundaries as .stl for use in CFD."""
3import ast
4import logging
5import os
6from pathlib import Path
8import pandas as pd
9from OCC.Core.StlAPI import StlAPI_Writer
10from OCC.Core.TopoDS import TopoDS_Shape
11from stl import mesh, stl
13from bim2sim.elements.bps_elements import SpaceBoundary
14from bim2sim.tasks.base import ITask
15from bim2sim.utilities.common_functions import filter_elements, \
16 get_spaces_with_bounds
17from bim2sim.utilities.pyocc_tools import PyOCCTools
19logger = logging.getLogger(__name__)
22class ExportIdfForCfd(ITask):
23 """ Export Idf shapes as .stl for use in CFD applications.
25 See detailed explanation in the run function below.
26 """
28 reads = ('elements', 'idf')
30 def run(self, elements, idf):
31 """Run CFD export depending on settings.
33 This task exports space boundaries as .stl for use as geometric input
34 files in CFD simulations. This task exports the space boundary
35 geometry in two different ways: (1) all space boundaries are exported as
36 stl files independent of their corresponding space and combined in a
37 single stl file, and (2) the space boundaries are and labelled within
38 the resulting and combined stl files for a more distinct usage in the
39 CFD application.
41 Args:
42 elements (dict): dictionary in the format dict[guid: element],
43 holds preprocessed elements including space boundaries.
44 idf (IDF): eppy idf, EnergyPlus input file.
46 """
47 if not self.playground.sim_settings.cfd_export:
48 return
50 logger.info("IDF Postprocessing for CFD started...")
51 logger.info("Export STL for CFD")
52 # stl_name = str(idf.idfname).replace('.idf', '')
53 # stl_name = stl_name.replace(str(self.paths.export)+'/', '')
54 stl_name = self.prj_name
55 base_stl_dir = self.paths.export / "STL/"
56 base_stl_dir = base_stl_dir.as_posix()
57 os.makedirs(base_stl_dir, exist_ok=True)
59 self.export_bounds_to_stl(elements, stl_name, base_stl_dir)
60 self.export_bounds_per_space_to_stl(elements, stl_name, base_stl_dir)
61 self.export_2b_bounds_to_stl(elements, stl_name, base_stl_dir)
62 self.combine_stl_files(stl_name, self.paths)
63 self.export_space_bound_list(elements, self.paths)
64 self.combined_space_stl(stl_name, self.paths)
65 logger.info("IDF Postprocessing for CFD finished!")
67 def export_2b_bounds_to_stl(self, elements: dict, stl_name: str,
68 stl_dir: str):
69 """Export generated 2b space boundaries to stl for CFD purposes.
71 Args:
72 stl_dir: directory of exported stl files
73 elements: dict[guid: element]
74 stl_name: name of the stl file.
75 """
76 spaces = get_spaces_with_bounds(elements)
77 for space_obj in spaces:
78 if not hasattr(space_obj, "b_bound_shape"):
79 continue
80 if PyOCCTools.get_shape_area(space_obj.b_bound_shape) > 0:
81 name = space_obj.guid + "_2B"
82 this_name = \
83 stl_dir + str(stl_name) + "_cfd_" + str(name) + ".stl"
84 triang_face = PyOCCTools.triangulate_bound_shape(
85 space_obj.b_bound_shape)
86 # Export to STL
87 self.write_triang_face(triang_face, this_name)
89 def export_bounds_to_stl(self, elements: dict, stl_name: str,
90 stl_dir: str):
91 """Export space boundaries to stl file.
93 This function exports space boundary geometry to an stl file as
94 triangulated surfaces. Only physical (i.e., non-virtual) space
95 boundaries are considered here.
96 The geometry of opening space boundaries is removed from their
97 underlying parent surfaces (e.g., Wall) before exporting to stl,
98 so boundaries do not overlap.
100 Args:
101 stl_dir: directory of exported stl files
102 elements: dict[guid: element]
103 stl_name: name of the stl file.
104 """
106 bounds = filter_elements(elements, SpaceBoundary)
107 for bound in bounds:
108 if not bound.physical:
109 continue
110 self.export_single_bound_to_stl(bound, stl_dir, stl_name)
112 def export_single_bound_to_stl(self, bound: SpaceBoundary, stl_dir: str,
113 stl_name: str):
114 """Export a single bound to stl.
116 Args:
117 bound: SpaceBoundary instance
118 stl_dir: directory of exported stl files
119 stl_name: name of the stl file.
120 """
121 name = bound.guid
122 this_name = stl_dir + str(stl_name) + "_cfd_" + str(name) + ".stl"
123 bound.cfd_face = bound.bound_shape
124 opening_shapes = []
125 if bound.opening_bounds:
126 opening_shapes = [s.bound_shape for s in bound.opening_bounds]
127 triang_face = PyOCCTools.triangulate_bound_shape(bound.cfd_face,
128 opening_shapes)
129 # Export to STL
130 self.write_triang_face(triang_face, this_name)
132 @staticmethod
133 def write_triang_face(shape: TopoDS_Shape, name):
134 """Write triangulated face to stl file.
136 Args:
137 shape: TopoDS_Shape
138 name: path and name of the stl
139 """
140 stl_writer = StlAPI_Writer()
141 stl_writer.SetASCIIMode(True)
142 stl_writer.Write(shape, name)
144 def export_bounds_per_space_to_stl(self, elements: dict, stl_name: str,
145 base_stl_dir: str):
146 """Export stl bounds per space in individual directories.
148 Args:
149 elements: dict[guid: element]
150 stl_name: name of the stl file.
151 base_stl_dir: directory of exported stl files
152 """
153 spaces = get_spaces_with_bounds(elements)
154 for space_obj in spaces:
155 space_name = space_obj.guid
156 stl_dir = base_stl_dir + "/" + space_name + "/"
157 os.makedirs(os.path.dirname(stl_dir), exist_ok=True)
158 for bound in space_obj.space_boundaries:
159 if not bound.physical:
160 continue
161 self.export_single_bound_to_stl(bound, stl_dir, stl_name)
162 self.combine_space_stl_files(stl_name, space_name, self.paths)
164 @staticmethod
165 def combined_space_stl(stl_name: str, paths):
166 """Combine the stl files per space in stl files.
168 Args:
169 stl_name: name of the stl file.
170 paths: BIM2SIM paths
171 """
172 sb_dict = pd.read_csv(paths.export / 'space_bound_list.csv').drop(
173 'Unnamed: 0', axis=1)
174 with open(paths.export / str(
175 stl_name + "_combined_STL.stl")) as output_file:
176 output_data = output_file.read()
177 for index, row in sb_dict.iterrows():
178 space_id = row['space_id']
179 new_space_id = space_id.replace('$', '___')
180 bound_ids = ast.literal_eval(row['bound_ids'])
181 for id in bound_ids:
182 id_new = id.replace('$', '___')
183 new_string = 'space_' + new_space_id + '_bound_' + id_new
184 new_string = new_string.upper()
185 output_data = output_data.replace(id_new, new_string)
186 with open(paths.export / str(stl_name + "_space_combined_STL.stl"),
187 'w+') as new_file:
188 new_file.write(output_data)
190 @staticmethod
191 def export_space_bound_list(elements: dict, paths: Path):
192 """Exports a list of spaces and space boundaries.
194 Args:
195 elements: dict[guid: element]
196 paths: BIM2SIM paths
197 """
198 stl_dir = paths.export.as_posix() + '/'
199 space_bound_df = pd.DataFrame(columns=["space_id", "bound_ids"])
200 spaces = get_spaces_with_bounds(elements)
201 for space in spaces:
202 bound_names = []
203 for bound in space.space_boundaries:
204 bound_names.append(bound.guid)
205 space_bound_df = pd.concat(
206 [space_bound_df,
207 pd.DataFrame({'space_id': space.guid,
208 'bound_ids': [bound_names]})],
209 ignore_index=True)
210 space_bound_df.to_csv(stl_dir + "space_bound_list.csv")
212 @staticmethod
213 def combine_stl_files(stl_name: str, paths: Path):
214 """Combine stl files.
216 Args:
217 stl_name: name of the stl file
218 paths: BIM2SIM paths
219 """
220 stl_dir = paths.export.as_posix() + '/'
221 with open(stl_dir + stl_name + "_combined_STL.stl", 'wb+') \
222 as output_file:
223 for i in os.listdir(stl_dir + 'STL/'):
224 if os.path.isfile(os.path.join(stl_dir + 'STL/', i)) \
225 and (stl_name + "_cfd_") in i:
226 sb_mesh = mesh.Mesh.from_file(stl_dir + 'STL/' + i)
227 mesh_name = "cfd_" +i.split("_cfd_", 1)[-1]
228 mesh_name = mesh_name.replace(".stl", "")
229 mesh_name = mesh_name.replace("$", "___")
230 sb_mesh.save(mesh_name, output_file, mode=stl.Mode.ASCII)
232 @staticmethod
233 def combine_space_stl_files(stl_name: str, space_name: str, paths: Path):
234 """Combine the stl file of spaces.
236 Args:
237 stl_name: name of the stl file
238 space_name: name of the space
239 paths: BIM2SIM paths
240 """
241 stl_dir = paths.export.as_posix() + '/'
242 os.makedirs(os.path.dirname(stl_dir + "space_stl/"), exist_ok=True)
244 with open(stl_dir + "space_stl/" + "space_" + space_name + ".stl",
245 'wb+') as output_file:
246 for i in os.listdir(stl_dir + 'STL/' + space_name + "/"):
247 if os.path.isfile(os.path.join(stl_dir + 'STL/'
248 + space_name + "/", i)) \
249 and (stl_name + "_cfd_") in i:
250 sb_mesh = mesh.Mesh.from_file(stl_dir + 'STL/'
251 + space_name + "/" + i)
252 mesh_name = "cfd_" + i.split("_cfd_", 1)[-1]
253 mesh_name = mesh_name.replace(".stl", "")
254 mesh_name = mesh_name.replace("$", "___")
255 sb_mesh.save(mesh_name, output_file, mode=stl.Mode.ASCII)