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
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 17:09 +0000
1import csv
2from pathlib import Path
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
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."}
132class ExportLCA(ITask):
133 """Exports a CSV file with all relevant quantities of the BIM model"""
134 reads = ('ifc_files', 'elements')
135 final = True
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 )
153 def run(self, ifc_files, elements):
154 self.logger.info("Exporting LCA quantities to CSV")
156 self.export_materials(elements)
157 self.export_overview(elements)
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
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 )
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 ])
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)
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 ])
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