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

1"""Module to export space boundaries as .stl for use in CFD.""" 

2 

3import ast 

4import logging 

5import os 

6from pathlib import Path 

7 

8import pandas as pd 

9from OCC.Core.StlAPI import StlAPI_Writer 

10from OCC.Core.TopoDS import TopoDS_Shape 

11from stl import mesh, stl 

12 

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 

18 

19logger = logging.getLogger(__name__) 

20 

21 

22class ExportIdfForCfd(ITask): 

23 """ Export Idf shapes as .stl for use in CFD applications. 

24 

25 See detailed explanation in the run function below. 

26 """ 

27 

28 reads = ('elements', 'idf') 

29 

30 def run(self, elements, idf): 

31 """Run CFD export depending on settings. 

32 

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. 

40 

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. 

45 

46 """ 

47 if not self.playground.sim_settings.cfd_export: 

48 return 

49 

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) 

58 

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!") 

66 

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. 

70 

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) 

88 

89 def export_bounds_to_stl(self, elements: dict, stl_name: str, 

90 stl_dir: str): 

91 """Export space boundaries to stl file. 

92 

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. 

99 

100 Args: 

101 stl_dir: directory of exported stl files 

102 elements: dict[guid: element] 

103 stl_name: name of the stl file. 

104 """ 

105 

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) 

111 

112 def export_single_bound_to_stl(self, bound: SpaceBoundary, stl_dir: str, 

113 stl_name: str): 

114 """Export a single bound to stl. 

115 

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) 

131 

132 @staticmethod 

133 def write_triang_face(shape: TopoDS_Shape, name): 

134 """Write triangulated face to stl file. 

135 

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) 

143 

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. 

147 

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) 

163 

164 @staticmethod 

165 def combined_space_stl(stl_name: str, paths): 

166 """Combine the stl files per space in stl files. 

167 

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) 

189 

190 @staticmethod 

191 def export_space_bound_list(elements: dict, paths: Path): 

192 """Exports a list of spaces and space boundaries. 

193 

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") 

211 

212 @staticmethod 

213 def combine_stl_files(stl_name: str, paths: Path): 

214 """Combine stl files. 

215 

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) 

231 

232 @staticmethod 

233 def combine_space_stl_files(stl_name: str, space_name: str, paths: Path): 

234 """Combine the stl file of spaces. 

235 

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) 

243 

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)