Coverage for bim2sim/plugins/PluginLCA/bim2sim_lca/task/export_lca.py: 0%

82 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-12 17:09 +0000

1import csv 

2from pathlib import Path 

3 

4from bim2sim.elements.base_elements import Material 

5from bim2sim.elements.bps_elements import LayerSet, Layer, Site, Building, \ 

6 Storey, SpaceBoundary, ExtSpatialSpaceBoundary, SpaceBoundary2B 

7from bim2sim.elements.mapping.units import ureg 

8from bim2sim.tasks.base import ITask 

9from bim2sim.utilities.common_functions import filter_elements 

10 

11KG_names = { 

12 300: "Building Construction", 

13 310: "excavation/earthwork", 

14 311: "fabrication", 

15 312: "enclosure", 

16 313: "dewatering", 

17 314: "Excavation", 

18 319: "Other to KG 310: excavation/earthwork", 

19 320: "Foundation, Subgrade", 

20 321: "Subsoil Improvement", 

21 322: "Shallow Foundations and Base Slabs", 

22 323: "Deep Foundations", 

23 324: "Foundation Coverings", 

24 325: "Waterproofing and Cladding", 

25 326: "Drainage", 

26 329: "Miscellaneous to KG 320: Foundation, Substructure", 

27 330: "Exterior walls/vertical building structures, exterior", 

28 331: "Load-bearing exterior walls", 

29 332: "Non-bearing exterior walls", 

30 333: "Exterior Supports", 

31 334: "Exterior Wall Openings", 

32 335: "Exterior wall coverings, exterior", 

33 336: "Exterior wall claddings, interior", 

34 337: "Elemental exterior wall assemblies", 

35 338: "Light protection to KG 330: Exterior walls/vertical building structures, exterior", 

36 339: "Other to KG 330: Exterior Walls/Vertical Building Structures, Exterior", 

37 340: "Interior walls/vertical building structures, interior", 

38 341: "Load-bearing interior walls", 

39 342: "Non-bearing interior walls", 

40 343: "Interior Supports", 

41 344: "Interior wall openings", 

42 345: "Interior wall cladding", 

43 346: "Elemental interior wall constructions", 

44 347: "Light protection to KG 340: Interior walls/vertical building structures, interior", 

45 349: "Other to KG 340: Interior Walls/Vertical Building Structures, Interior", 

46 350: "Ceilings/Horizontal Building Structures", 

47 351: "Ceiling Structures", 

48 352: "Ceiling Openings", 

49 353: "Ceiling coverings", 

50 354: "Ceiling Coverings", 

51 355: "Elemental ceiling structures", 

52 359: "Other to KG 350: Ceilings/Horizontal Building Structures", 

53 360: "Roofs", 

54 361: "Roof Structures", 

55 362: "Roof Openings", 

56 363: "Roof Coverings", 

57 364: "Roof Coverings", 

58 365: "Elemental roof structures", 

59 366: "Light protection to KG 360: Roofs", 

60 369: "Other to KG 360: Roofs", 

61 400: "Building - technical installations", 

62 410: "Sewage, water, gas installations", 

63 411: "Sewage Plants", 

64 412: "Water Plants", 

65 413: "Gas installations", 

66 419: "Other to KG 410: sewage, water, gas installations", 

67 420: "Heat supply plants", 

68 421: "Heat generation plants", 

69 422: "Heat distribution networks", 

70 423: "Space heating surfaces", 

71 424: "Traffic heating surfaces", 

72 429: "Other to KG 420: Heat supply systems", 

73 430: "Ventilation and air-conditioning systems", 

74 431: "Ventilation systems", 

75 432: "Partial air conditioning systems", 

76 433: "Air conditioning systems", 

77 434: "Refrigeration plants", 

78 439: "Other to KG 430: Ventilation and air-conditioning systems", 

79 440: "Electrical installations", 

80 441: "High and medium voltage installations", 

81 442: "In-house power supply systems", 

82 443: "Low-voltage switchgear", 

83 444: "Low-voltage installation plants", 

84 445: "Lighting installations", 

85 446: "Lightning protection and grounding systems", 

86 447: "Catenary systems", 

87 449: "Other to KG 440: Electrical installations", 

88 450: "Communication, security and information technology installations", 

89 451: "Telecommunication systems", 

90 452: "Search and signal systems", 

91 453: "Time Service Installations", 

92 454: "Electroacoustic installations", 

93 455: "Audiovisual media and antenna systems", 

94 456: "Hazard detection and alarm systems", 

95 457: "Data transmission networks", 

96 458: "Traffic control systems", 

97 459: "Other KG 450: Communication, security and information technology systems, Conveyor systemsen", 

98 460: "Conveyor systems", 

99 461: "Elevator systems", 

100 462: "Escalators, moving walks", 

101 463: "Access systems", 

102 464: "Transportation systems", 

103 465: "crane systems", 

104 466: "Hydraulic plants", 

105 469: "Other to KG 460: Conveyor systems", 

106 470: "Use-specific and process engineering plants", 

107 471: "Kitchen technical installations", 

108 472: "Laundry, cleaning and bathing technical plants", 

109 473: "Media supply plants, medical and laboratory plants", 

110 474: "Fire extinguishing systems", 

111 475: "Process heating, cooling and air systems", 

112 476: "Other use-specific plants", 

113 477: "Process plants, water, wastewater and gases", 

114 478: "Process plants, solids, recyclables and waste", 

115 479: "Other to KG 470: Use-specific and process plants", 

116 480: "Building and plant automation", 

117 481: "Automation Equipment", 

118 482: "Control Cabinets, Automation Focuses", 

119 483: "Automation management", 

120 484: "Cables, conduits and installation systems", 

121 485: "Data transmission networks", 

122 489: "Other to KG 480: Building and Plant Automation", 

123 600: "Equipment and Artwork", 

124 610: "General Equipment", 

125 620: "Special Equipment", 

126 630: "Information Technology Equipment", 

127 640: "Artistic Equipment", 

128 690: "Other Equipment", 

129 000: "Cost group cannot be determined. Reason is lack of information."} 

130 

131 

132class ExportLCA(ITask): 

133 """Exports a CSV file with all relevant quantities of the BIM model""" 

134 reads = ('ifc_files', 'elements') 

135 final = True 

136 

137 def __init__(self, playground): 

138 super().__init__(playground) 

139 # some elements should not be exported or exported as relation to 

140 # others 

141 self.blacklist_elements = ( 

142 Site, 

143 Building, 

144 Storey, 

145 SpaceBoundary, 

146 ExtSpatialSpaceBoundary, 

147 SpaceBoundary2B, 

148 Material, 

149 LayerSet, 

150 Layer 

151 ) 

152 

153 def run(self, ifc_files, elements): 

154 self.logger.info("Exporting LCA quantities to CSV") 

155 

156 self.export_materials(elements) 

157 self.export_overview(elements) 

158 

159 def export_materials(self, elements): 

160 """Exports only the materials and its total volume and mass if density 

161 is given in the IFC""" 

162 export_path = Path( 

163 self.paths.export) / ("Material_quantities_" + self.prj_name + 

164 ".csv") 

165 materials = {} 

166 for mat in filter_elements(elements, Material): 

167 materials[mat] = { 

168 "name": mat.name, 

169 "density": mat.density, 

170 "volume": 0 * ureg.m ** 3, 

171 "mass": None 

172 } 

173 # todo: if we have the volume of each layer we can do this more straight 

174 # forward, until then this is a workaround 

175 for inst in elements.values(): 

176 if not isinstance(inst, self.blacklist_elements): 

177 # uniform materials 

178 if inst.material: 

179 if inst.volume: 

180 materials[inst.material]["volume"] += inst.volume 

181 if hasattr(inst, "layerset"): 

182 if inst.layerset: 

183 for layer in inst.layerset.layers: 

184 if inst.net_area and layer.thickness: 

185 materials[layer.material]["volume"] += \ 

186 inst.net_area * layer.thickness 

187 if hasattr(inst, 'material_set'): 

188 if inst.material_set: 

189 for fraction, material in inst.material_set.items(): 

190 if not "unknown" in fraction: 

191 materials[material]["volume"] += \ 

192 inst.volume * fraction 

193 

194 # calculate mass if density is given in IFC 

195 with open(file=export_path, mode='w', newline='') as file: 

196 writer = csv.writer(file, delimiter=';', quotechar='"', 

197 quoting=csv.QUOTE_MINIMAL) 

198 writer.writerow( 

199 ["Name", 

200 "Density [kg/m³]", 

201 "Total Volume[m³]", 

202 "Total Mass[kg]"] 

203 ) 

204 

205 for mat in materials.keys(): 

206 writer.writerow([ 

207 mat.name, 

208 self.ureg_to_str(materials[mat]["density"], 

209 ureg.kg / ureg.m ** 3), 

210 self.ureg_to_str(materials[mat]["volume"], ureg.m ** 3), 

211 self.ureg_to_str( 

212 materials[mat]["volume"] * materials[mat]["density"], 

213 ureg.kg) if materials[mat]["density"] else "-" 

214 ]) 

215 

216 def export_overview(self, elements): 

217 export_path = Path( 

218 self.paths.export) / \ 

219 ("Quantities_overview_" + self.prj_name + ".csv") 

220 with open(file=export_path, mode='w', newline='') as file: 

221 writer = csv.writer(file, delimiter=';', quotechar='"', 

222 quoting=csv.QUOTE_MINIMAL) 

223 writer.writerow( 

224 ['GUID', 'Storey', 'Type', 'Cost Group Number', 

225 'Cost Group Name', 'Name', 'Material Type', 'Material Name', 

226 'Material Density [kg/m³]', 

227 'Material Specific Heat Capacity [kJ/ (kg K)]', 

228 'Material Thermal Conductivity [W/(m K)]', 

229 'Area[m²]', 'Thickness[m] / Fraction [-]', 'Volume [m³]' 

230 ]) 

231 for inst in elements.values(): 

232 if not isinstance(inst, self.blacklist_elements): 

233 # General information 

234 inst_guid = inst.guid 

235 storey_names = ", ".join( 

236 [storey.name for storey in inst.storeys]) 

237 inst_cls_name = inst.__class__.__name__ 

238 inst_cost_group = inst.cost_group 

239 inst_cost_group_name = KG_names[inst.cost_group] 

240 inst_name = inst.name 

241 inst_material_type = 'Uniform' if inst.material else '-' 

242 inst_material_name = inst.material.name if inst.material \ 

243 else '-' 

244 inst_mat_dens = self.ureg_to_str( 

245 inst.material.density, ureg.kg / ureg.m ** 3) \ 

246 if inst.material else '-' 

247 inst_mat_heat_capac = self.ureg_to_str( 

248 inst.material.spec_heat_capacity, 

249 ureg.kilojoule / (ureg.kg * ureg.K)) \ 

250 if inst.material else '-' 

251 inst_mat_conduc = self.ureg_to_str( 

252 inst.material.thermal_conduc, 

253 ureg.W / ureg.m / ureg.K) if inst.material else '-' 

254 inst_area = self.ureg_to_str( 

255 inst.net_area, ureg.m ** 2) if inst.net_area \ 

256 else self.ureg_to_str(inst.gross_area, ureg.m ** 2) 

257 inst_width = self.ureg_to_str(inst.width, ureg.m) 

258 inst_vol = self.ureg_to_str(inst.volume, ureg.m ** 3) 

259 

260 # export instance itself 

261 writer.writerow([ 

262 inst_guid, 

263 storey_names, 

264 inst_cls_name, 

265 inst.cost_group, 

266 inst_cost_group_name, 

267 inst_name, 

268 inst_material_type, 

269 inst_material_name, 

270 inst_mat_dens, 

271 inst_mat_heat_capac, 

272 inst_mat_conduc, 

273 inst_area, 

274 inst_width, 

275 inst_vol 

276 ]) 

277 # export elements layers 

278 if hasattr(inst, 'layerset'): 

279 if inst.layerset: 

280 for layer in inst.layerset.layers: 

281 layer_cls_name = layer.__class__.__name__ 

282 layer_mat_name = layer.material.name \ 

283 if layer.material else '-' 

284 writer.writerow([ 

285 inst_guid, 

286 storey_names, 

287 inst_cls_name, 

288 inst_cost_group, 

289 inst_cost_group_name, 

290 inst_name, 

291 layer_cls_name, 

292 layer_mat_name, 

293 self.ureg_to_str(layer.material.density, 

294 ureg.kg / ureg.m ** 3) 

295 if layer.material else '-', 

296 self.ureg_to_str( 

297 layer.material.spec_heat_capacity, 

298 ureg.kilojoule / (ureg.kg * ureg.K)) 

299 if layer.material else '-', 

300 self.ureg_to_str( 

301 layer.material.thermal_conduc, 

302 ureg.W / ureg.m / ureg.K) 

303 if layer.material else '-', 

304 inst_area, 

305 self.ureg_to_str(layer.thickness, ureg.m), 

306 self.ureg_to_str(layer.volume, ureg.m ** 3), 

307 ]) 

308 # export elements constituent sets 

309 if hasattr(inst, 'material_set'): 

310 if inst.material_set: 

311 for fraction, material in inst.material_set.items(): 

312 writer.writerow([ 

313 inst_guid, 

314 storey_names, 

315 inst_cls_name, 

316 inst_cost_group, 

317 inst_cost_group_name, 

318 inst_name, 

319 "Material Constituent", 

320 material.name, 

321 self.ureg_to_str(material.density, 

322 ureg.kg / ureg.m ** 3), 

323 self.ureg_to_str( 

324 material.spec_heat_capacity, 

325 ureg.kilojoule / (ureg.kg * ureg.K)), 

326 self.ureg_to_str(material.thermal_conduc, 

327 ureg.W / ureg.m / ureg.K), 

328 inst_area, 

329 fraction if 'unknown' in fraction 

330 else self.ureg_to_str(fraction, 

331 ureg.dimensionless), 

332 "-" if 'unknown' in fraction 

333 else inst.volume * fraction 

334 ]) 

335 

336 @staticmethod 

337 def ureg_to_str(value, unit, n_digits=3, ): 

338 """Transform pint unit to human readable value with given unit.""" 

339 if value is not None and not isinstance(value, float): 

340 return round(value.to(unit).m, n_digits) 

341 elif value is None: 

342 return "-" 

343 else: 

344 return value