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

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

2 

3import ast 

4import logging 

5import os 

6 

7import pandas as pd 

8from OCC.Core.StlAPI import StlAPI_Writer 

9from OCC.Core.TopoDS import TopoDS_Shape 

10from stl import mesh, stl 

11 

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 

17 

18logger = logging.getLogger(__name__) 

19 

20 

21class ExportIdfForCfd(ITask): 

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

23 

24 See detailed explanation in the run function below. 

25 """ 

26 

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

28 

29 def run(self, elements, idf): 

30 """Run CFD export depending on settings. 

31 

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. 

39 

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. 

44 

45 """ 

46 if not self.playground.sim_settings.cfd_export: 

47 return 

48 

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) 

55 

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

63 

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. 

67 

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) 

85 

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

87 stl_dir: str): 

88 """Export space boundaries to stl file. 

89 

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. 

96 

97 Args: 

98 stl_dir: directory of exported stl files 

99 elements: dict[guid: element] 

100 stl_name: name of the stl file. 

101 """ 

102 

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) 

108 

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

110 stl_name: str): 

111 """Export a single bound to stl. 

112 

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) 

128 

129 @staticmethod 

130 def write_triang_face(shape: TopoDS_Shape, name): 

131 """Write triangulated face to stl file. 

132 

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) 

140 

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. 

144 

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) 

160 

161 @staticmethod 

162 def combined_space_stl(stl_name: str, paths): 

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

164 

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) 

186 

187 @staticmethod 

188 def export_space_bound_list(elements: dict, paths: str): 

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

190 

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

206 

207 @staticmethod 

208 def combine_stl_files(stl_name: str, paths: str): 

209 """Combine stl files. 

210 

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) 

226 

227 @staticmethod 

228 def combine_space_stl_files(stl_name: str, space_name: str, paths: str): 

229 """Combine the stl file of spaces. 

230 

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) 

238 

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)