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