File size: 35,021 Bytes
31a0845
 
82c492d
 
7f43faa
31a0845
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7f43faa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31a0845
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7f43faa
 
31a0845
 
 
82c492d
7f43faa
 
 
 
ad4d6a0
7f43faa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31a0845
 
 
7f43faa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31a0845
 
 
ad4d6a0
31a0845
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7f43faa
 
 
 
 
 
 
 
 
 
 
 
31a0845
 
 
 
 
 
 
 
7f43faa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31a0845
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7f43faa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82c492d
31a0845
 
 
 
 
 
 
 
 
 
 
 
7f43faa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31a0845
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7f43faa
 
31a0845
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7f43faa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31a0845
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
"""
Material Library for HVAC Load Calculator
Updated 2025-05-17: Removed original materials and constructions, deleted ASHRAE materials starting with 'R' followed by number (e.g., R01, R25), verified constructions use valid materials.
Updated 2025-05-17: Added all materials and constructions from ASHRAE_2005_HOF_Materials.idf with Australian-specific embodied carbon and prices.
Updated 2025-05-17: Added GlazingMaterial and DoorMaterial classes, included library_glazing_materials and library_door_materials with comprehensive data.
Updated 2025-05-16: Removed mass_per_meter, added thermal_mass method, updated CSV handling.
Updated 2025-05-16: Fixed U-value calculation in Material.get_u_value.
Updated 2025-05-16: Updated MaterialCategory to Finishing Materials, Structural Materials, Sub-Structural Materials, Insulation.
Updated 2025-05-16: Fixed Construction.get_thermal_mass to use avg_specific_heat.

Developed by: Dr Majed Abuseif, Deakin University
© 2025
"""

from typing import Dict, List, Optional, Tuple
from enum import Enum
import pandas as pd

class MaterialCategory(Enum):
    FINISHING_MATERIALS = "Finishing Materials"
    STRUCTURAL_MATERIALS = "Structural Materials"
    SUB_STRUCTURAL_MATERIALS = "Sub-Structural Materials"
    INSULATION = "Insulation"

class ThermalMass(Enum):
    HIGH = "High"
    MEDIUM = "Medium"
    LOW = "Low"
    NO_MASS = "No Mass"

class Material:
    def __init__(self, name: str, category: MaterialCategory, conductivity: float, density: float,
                 specific_heat: float, default_thickness: float, embodied_carbon: float,
                 solar_absorption: float, price: float, is_library: bool = True):
        self.name = name
        self.category = category
        self.conductivity = max(0.01, conductivity)  # W/m·K
        self.density = max(1.0, density)  # kg/m³
        self.specific_heat = max(100.0, specific_heat)  # J/kg·K
        self.default_thickness = max(0.01, default_thickness)  # m
        self.embodied_carbon = max(0.0, embodied_carbon)  # kgCO₂e/kg
        self.solar_absorption = min(max(0.0, solar_absorption), 1.0)
        self.price = max(0.0, price)  # USD/m²
        self.is_library = is_library

    def get_thermal_mass(self) -> ThermalMass:
        if self.density < 100.0 or self.specific_heat < 800.0:
            return ThermalMass.NO_MASS
        elif self.density > 2000.0 and self.specific_heat > 800.0:
            return ThermalMass.HIGH
        elif 1000.0 <= self.density <= 2000.0 and 800.0 <= self.specific_heat <= 1200.0:
            return ThermalMass.MEDIUM
        else:
            return ThermalMass.LOW

    def get_u_value(self) -> float:
        return self.conductivity / self.default_thickness if self.default_thickness > 0 else 0.1

class GlazingMaterial:
    def __init__(self, name: str, u_value: float, shgc: float, embodied_carbon: float, price: float, is_library: bool = True):
        self.name = name
        self.u_value = max(0.1, u_value)  # W/m²·K
        self.shgc = min(max(0.0, shgc), 1.0)  # Solar Heat Gain Coefficient
        self.embodied_carbon = max(0.0, embodied_carbon)  # kgCO₂e/m²
        self.price = max(0.0, price)  # USD/m²
        self.is_library = is_library

class DoorMaterial:
    def __init__(self, name: str, u_value: float, solar_absorption: float, embodied_carbon: float, price: float, is_library: bool = True):
        self.name = name
        self.u_value = max(0.1, u_value)  # W/m²·K
        self.solar_absorption = min(max(0.0, solar_absorption), 1.0)
        self.embodied_carbon = max(0.0, embodied_carbon)  # kgCO₂e/m²
        self.price = max(0.0, price)  # USD/m²
        self.is_library = is_library

class Construction:
    def __init__(self, name: str, component_type: str, layers: List[Dict], is_library: bool = True):
        self.name = name
        self.component_type = component_type
        self.layers = layers or []
        self.is_library = is_library
        self.u_value = self.calculate_u_value()
        self.total_thickness = sum(layer["thickness"] for layer in self.layers)
        self.embodied_carbon = sum(layer["material"].embodied_carbon * layer["material"].density * layer["thickness"]
                                  for layer in self.layers)
        self.solar_absorption = max(layer["material"].solar_absorption for layer in self.layers) if self.layers else 0.6
        self.price = sum(layer["material"].price * layer["thickness"] / layer["material"].default_thickness
                         for layer in self.layers)

    def calculate_u_value(self) -> float:
        if not self.layers:
            return 0.1
        r_total = sum(layer["thickness"] / layer["material"].conductivity for layer in self.layers)
        return 1 / r_total if r_total > 0 else 0.1

    def get_thermal_mass(self) -> ThermalMass:
        if not self.layers:
            return ThermalMass.NO_MASS
        total_thickness = self.total_thickness
        if total_thickness == 0:
            return ThermalMass.NO_MASS
        avg_density = sum(layer["material"].density * layer["thickness"] for layer in self.layers) / total_thickness
        avg_specific_heat = sum(layer["material"].specific_heat * layer["thickness"] for layer in self.layers) / total_thickness
        if avg_density < 100.0 or avg_specific_heat < 800.0:
            return ThermalMass.NO_MASS
        elif avg_density > 2000.0 and avg_specific_heat > 800.0:
            return ThermalMass.HIGH
        elif 1000.0 <= avg_density <= 2000.0 and 800.0 <= avg_specific_heat <= 1200.0:
            return ThermalMass.MEDIUM
        else:
            return ThermalMass.LOW

class MaterialLibrary:
    def __init__(self):
        self.library_materials = self.initialize_materials()
        self.library_constructions = self.initialize_constructions()
        self.library_glazing_materials = self.initialize_glazing_materials()
        self.library_door_materials = self.initialize_door_materials()

    def initialize_materials(self) -> Dict[str, Material]:
        materials = [
            # ASHRAE 2005 HOF Materials (excluding 'R'-prefixed materials)
            Material("F04 Wall air space resistance", MaterialCategory.INSULATION, 0.01, 1.0, 1000.0, 0.01, 0.01, 0.5, 0.5),
            Material("F05 Ceiling air space resistance", MaterialCategory.INSULATION, 0.01, 1.0, 1000.0, 0.01, 0.01, 0.5, 0.5),
            Material("F06 EIFS finish", MaterialCategory.FINISHING_MATERIALS, 0.72, 1856.0, 840.0, 0.0095, 0.3, 0.5, 17.6),
            Material("F07 25mm stucco", MaterialCategory.FINISHING_MATERIALS, 0.72, 1856.0, 840.0, 0.0254, 0.2, 0.6, 14.1),
            Material("F08 Metal surface", MaterialCategory.SUB_STRUCTURAL_MATERIALS, 45.28, 7824.0, 500.0, 0.001, 2.2, 0.7, 25.0),
            Material("F09 25mm cement plaster", MaterialCategory.FINISHING_MATERIALS, 0.72, 1856.0, 840.0, 0.0254, 0.2, 0.6, 14.1),
            Material("F10 13mm gypsum board", MaterialCategory.FINISHING_MATERIALS, 0.16, 800.0, 1090.0, 0.0127, 0.25, 0.4, 5.1),
            Material("F11 16mm gypsum board", MaterialCategory.FINISHING_MATERIALS, 0.16, 800.0, 1090.0, 0.0159, 0.25, 0.4, 6.4),
            Material("F12 19mm gypsum board", MaterialCategory.FINISHING_MATERIALS, 0.16, 800.0, 1090.0, 0.0191, 0.25, 0.4, 7.6),
            Material("F13 13mm cement plaster", MaterialCategory.FINISHING_MATERIALS, 0.72, 1856.0, 840.0, 0.0127, 0.2, 0.6, 7.1),
            Material("F14 13mm lime plaster", MaterialCategory.FINISHING_MATERIALS, 0.72, 1600.0, 840.0, 0.0127, 0.2, 0.5, 6.1),
            Material("F15 22mm cement plaster", MaterialCategory.FINISHING_MATERIALS, 0.72, 1856.0, 840.0, 0.0222, 0.2, 0.6, 12.4),
            Material("F16 Acoustic tile", MaterialCategory.FINISHING_MATERIALS, 0.06, 368.0, 590.0, 0.0191, 1.0, 0.4, 14.0),
            Material("F17 13mm slag", MaterialCategory.FINISHING_MATERIALS, 0.16, 960.0, 1090.0, 0.0127, 0.2, 0.5, 1.0),
            Material("F18 25mm slag", MaterialCategory.FINISHING_MATERIALS, 0.16, 960.0, 1090.0, 0.0254, 0.2, 0.5, 1.9),
            Material("G01 13mm gypsum board", MaterialCategory.FINISHING_MATERIALS, 0.16, 800.0, 1090.0, 0.0127, 0.25, 0.4, 5.1),
            Material("G01a 19mm gypsum board", MaterialCategory.FINISHING_MATERIALS, 0.16, 800.0, 1090.0, 0.0191, 0.25, 0.4, 7.6),
            Material("G02 25mm cement plaster", MaterialCategory.FINISHING_MATERIALS, 0.72, 1856.0, 840.0, 0.0254, 0.2, 0.6, 14.1),
            Material("G03 13mm lime plaster", MaterialCategory.FINISHING_MATERIALS, 0.72, 1600.0, 840.0, 0.0127, 0.25, 0.5, 6.1),
            Material("G04 13mm cement plaster", MaterialCategory.FINISHING_MATERIALS, 0.72, 1856.0, 840.0, 0.0127, 0.2, 0.6, 7.1),
            Material("G05 25mm wood", MaterialCategory.SUB_STRUCTURAL_MATERIALS, 0.15, 608.0, 1630.0, 0.0254, 0.3, 0.5, 15.4),
            Material("G06 19mm wood", MaterialCategory.SUB_STRUCTURAL_MATERIALS, 0.15, 608.0, 1630.0, 0.0191, 0.3, 0.5, 11.6),
            Material("I01 25mm insulation board", MaterialCategory.INSULATION, 0.03, 43.0, 1210.0, 0.0254, 2.5, 0.5, 1.1),
            Material("I02 50mm insulation board", MaterialCategory.INSULATION, 0.03, 43.0, 1210.0, 0.0508, 2.5, 0.5, 2.2),
            Material("I03 75mm insulation board", MaterialCategory.INSULATION, 0.03, 43.0, 1210.0, 0.0762, 2.5, 0.5, 3.3),
            Material("M01 100mm brick", MaterialCategory.STRUCTURAL_MATERIALS, 0.89, 1920.0, 790.0, 0.1016, 0.3, 0.7, 19.5),
            Material("M02 100mm face brick", MaterialCategory.STRUCTURAL_MATERIALS, 1.33, 2000.0, 790.0, 0.1016, 0.3, 0.7, 20.3),
            Material("M03 150mm brick", MaterialCategory.STRUCTURAL_MATERIALS, 0.89, 1920.0, 790.0, 0.1524, 0.3, 0.7, 29.3),
            Material("M04 200mm concrete block", MaterialCategory.STRUCTURAL_MATERIALS, 0.51, 800.0, 920.0, 0.2032, 0.2, 0.65, 13.0),
            Material("M05 200mm concrete block", MaterialCategory.STRUCTURAL_MATERIALS, 1.11, 1280.0, 920.0, 0.2032, 0.2, 0.65, 20.8),
            Material("M06 150mm concrete block", MaterialCategory.STRUCTURAL_MATERIALS, 0.51, 800.0, 920.0, 0.1524, 0.2, 0.65, 9.8),
            Material("M07 100mm concrete block", MaterialCategory.STRUCTURAL_MATERIALS, 0.51, 800.0, 920.0, 0.1016, 0.2, 0.65, 6.5),
            Material("M08 150mm concrete block", MaterialCategory.STRUCTURAL_MATERIALS, 1.11, 1280.0, 920.0, 0.1524, 0.2, 0.65, 15.6),
            Material("M09 100mm concrete block", MaterialCategory.STRUCTURAL_MATERIALS, 1.11, 1280.0, 920.0, 0.1016, 0.2, 0.65, 10.4),
            Material("M10 100mm lightweight concrete", MaterialCategory.STRUCTURAL_MATERIALS, 0.53, 1280.0, 840.0, 0.1016, 0.15, 0.65, 7.8),
            Material("M11 100mm lightweight concrete", MaterialCategory.STRUCTURAL_MATERIALS, 0.53, 1280.0, 840.0, 0.1016, 0.15, 0.65, 7.8),
            Material("M12 150mm lightweight concrete", MaterialCategory.STRUCTURAL_MATERIALS, 0.53, 1280.0, 840.0, 0.1524, 0.15, 0.65, 11.7),
            Material("M13 200mm lightweight concrete", MaterialCategory.STRUCTURAL_MATERIALS, 0.53, 1280.0, 840.0, 0.2032, 0.15, 0.65, 15.6),
            Material("M14 100mm heavyweight concrete", MaterialCategory.STRUCTURAL_MATERIALS, 1.95, 2240.0, 900.0, 0.1016, 0.2, 0.65, 18.2),
            Material("M14a 100mm heavyweight concrete", MaterialCategory.STRUCTURAL_MATERIALS, 1.95, 2240.0, 900.0, 0.1016, 0.2, 0.65, 18.2),
            Material("M15 200mm heavyweight concrete", MaterialCategory.STRUCTURAL_MATERIALS, 1.95, 2240.0, 900.0, 0.2032, 0.2, 0.65, 36.4),
            Material("M16 300mm heavyweight concrete", MaterialCategory.STRUCTURAL_MATERIALS, 1.95, 2240.0, 900.0, 0.3048, 0.2, 0.65, 54.6),
            Material("M17 100mm stone", MaterialCategory.STRUCTURAL_MATERIALS, 2.10, 2240.0, 880.0, 0.1016, 0.2, 0.7, 22.8),
            Material("M18 150mm stone", MaterialCategory.STRUCTURAL_MATERIALS, 2.10, 2240.0, 880.0, 0.1524, 0.2, 0.7, 34.1),
            Material("M19 100mm limestone", MaterialCategory.STRUCTURAL_MATERIALS, 1.80, 2320.0, 880.0, 0.1016, 0.2, 0.6, 23.6),
            Material("M20 150mm limestone", MaterialCategory.STRUCTURAL_MATERIALS, 1.80, 2320.0, 880.0, 0.1524, 0.2, 0.6, 35.4),
            Material("M21 200mm limestone", MaterialCategory.STRUCTURAL_MATERIALS, 1.80, 2320.0, 880.0, 0.2032, 0.2, 0.6, 47.1),
            Material("M22 100mm granite", MaterialCategory.STRUCTURAL_MATERIALS, 2.80, 2640.0, 880.0, 0.1016, 0.2, 0.7, 26.8),
            Material("M23 150mm granite", MaterialCategory.STRUCTURAL_MATERIALS, 2.80, 2640.0, 880.0, 0.1524, 0.2, 0.7, 40.2),
            Material("M24 200mm granite", MaterialCategory.STRUCTURAL_MATERIALS, 2.80, 2640.0, 880.0, 0.2032, 0.2, 0.7, 53.6),
            Material("M25 100mm marble", MaterialCategory.STRUCTURAL_MATERIALS, 2.50, 2720.0, 880.0, 0.1016, 0.2, 0.6, 27.6),
            Material("M26 150mm marble", MaterialCategory.STRUCTURAL_MATERIALS, 2.50, 2720.0, 880.0, 0.1524, 0.2, 0.6, 41.4),
            Material("M27 200mm marble", MaterialCategory.STRUCTURAL_MATERIALS, 2.50, 2720.0, 880.0, 0.2032, 0.2, 0.6, 55.3),
        ]
        return {mat.name: mat for mat in materials}

    def initialize_glazing_materials(self) -> Dict[str, GlazingMaterial]:
        glazing_materials = [
            # ASHRAE-based glazing materials with Australian pricing
            GlazingMaterial("Single Clear 3mm", 5.8, 0.81, 25.0, 50.0),  # High U-value, high SHGC
            GlazingMaterial("Single Clear 6mm", 5.7, 0.78, 28.0, 60.0),  # Slightly lower SHGC
            GlazingMaterial("Single Tinted 6mm", 5.7, 0.55, 30.0, 70.0),  # Reduced SHGC for solar control
            GlazingMaterial("Double Clear 6mm/13mm Air", 2.7, 0.70, 40.0, 100.0),  # Improved insulation
            GlazingMaterial("Double Low-E 6mm/13mm Air", 1.8, 0.60, 45.0, 120.0),  # Low-E coating
            GlazingMaterial("Double Tinted 6mm/13mm Air", 2.7, 0.45, 42.0, 110.0),  # Tinted for solar control
            GlazingMaterial("Double Low-E 6mm/13mm Argon", 1.5, 0.55, 48.0, 130.0),  # Argon-filled, better U-value
            GlazingMaterial("Triple Clear 4mm/12mm Air", 1.8, 0.62, 55.0, 150.0),  # Triple glazing
            GlazingMaterial("Triple Low-E 4mm/12mm Argon", 0.9, 0.50, 60.0, 180.0),  # High-performance
            GlazingMaterial("Single Low-E Reflective 6mm", 5.6, 0.35, 35.0, 90.0),  # Reflective coating
            GlazingMaterial("Double Reflective 6mm/13mm Air", 2.5, 0.30, 50.0, 140.0),  # Low SHGC
            GlazingMaterial("Electrochromic 6mm/13mm Air", 2.0, 0.40, 70.0, 200.0),  # Dynamic glazing
        ]
        return {mat.name: mat for mat in glazing_materials}

    def initialize_door_materials(self) -> Dict[str, DoorMaterial]:
        door_materials = [
            # Door materials with ASHRAE-based properties and Australian pricing
            DoorMaterial("Solid Wood 45mm", 2.5, 0.50, 15.0, 200.0),  # Standard wooden door
            DoorMaterial("Insulated Wood 50mm", 1.8, 0.45, 18.0, 250.0),  # Better insulation
            DoorMaterial("Hollow Core Wood 40mm", 3.5, 0.50, 12.0, 150.0),  # Less insulation
            DoorMaterial("Steel Uninsulated 45mm", 5.0, 0.70, 20.0, 180.0),  # High U-value
            DoorMaterial("Steel Insulated 50mm", 2.0, 0.65, 25.0, 220.0),  # Foam-insulated
            DoorMaterial("Aluminum Uninsulated 45mm", 6.0, 0.75, 22.0, 200.0),  # Poor insulation
            DoorMaterial("Aluminum Insulated 50mm", 2.5, 0.70, 28.0, 240.0),  # Thermal break
            DoorMaterial("Glass Single 6mm", 5.7, 0.78, 28.0, 100.0),  # Same as single glazing
            DoorMaterial("Glass Double 6mm/13mm Air", 2.7, 0.70, 40.0, 150.0),  # Double-glazed door
            DoorMaterial("Fiberglass Insulated 50mm", 1.5, 0.60, 20.0, 230.0),  # High performance
            DoorMaterial("PVC Insulated 50mm", 1.7, 0.55, 18.0, 210.0),  # Durable, insulated
            DoorMaterial("Wood with Glass Insert", 3.0, 0.65, 16.0, 190.0),  # Mixed properties
        ]
        return {mat.name: mat for mat in door_materials}

    def initialize_constructions(self) -> Dict[str, Construction]:
        constructions = [
            Construction("Light Exterior Wall", "Wall", [
                {"material": self.library_materials["F08 Metal surface"], "thickness": 0.001},
                {"material": self.library_materials["I02 50mm insulation board"], "thickness": 0.0508},
                {"material": self.library_materials["F04 Wall air space resistance"], "thickness": 0.01},
                {"material": self.library_materials["G01a 19mm gypsum board"], "thickness": 0.0191}
            ]),
            Construction("Light Roof/Ceiling", "Roof", [
                {"material": self.library_materials["M11 100mm lightweight concrete"], "thickness": 0.1016},
                {"material": self.library_materials["F05 Ceiling air space resistance"], "thickness": 0.01},
                {"material": self.library_materials["F16 Acoustic tile"], "thickness": 0.0191}
            ]),
            Construction("Light Floor", "Floor", [
                {"material": self.library_materials["F16 Acoustic tile"], "thickness": 0.0191},
                {"material": self.library_materials["F05 Ceiling air space resistance"], "thickness": 0.01},
                {"material": self.library_materials["M11 100mm lightweight concrete"], "thickness": 0.1016}
            ]),
            Construction("Medium Exterior Wall", "Wall", [
                {"material": self.library_materials["M01 100mm brick"], "thickness": 0.1016},
                {"material": self.library_materials["I02 50mm insulation board"], "thickness": 0.0508},
                {"material": self.library_materials["F04 Wall air space resistance"], "thickness": 0.01},
                {"material": self.library_materials["G01a 19mm gypsum board"], "thickness": 0.0191}
            ]),
            Construction("Medium Roof/Ceiling", "Roof", [
                {"material": self.library_materials["M14a 100mm heavyweight concrete"], "thickness": 0.1016},
                {"material": self.library_materials["F05 Ceiling air space resistance"], "thickness": 0.01},
                {"material": self.library_materials["F16 Acoustic tile"], "thickness": 0.0191}
            ]),
            Construction("Medium Floor", "Floor", [
                {"material": self.library_materials["F16 Acoustic tile"], "thickness": 0.0191},
                {"material": self.library_materials["F05 Ceiling air space resistance"], "thickness": 0.01},
                {"material": self.library_materials["M14a 100mm heavyweight concrete"], "thickness": 0.1016}
            ]),
            Construction("Heavy Exterior Wall", "Wall", [
                {"material": self.library_materials["M01 100mm brick"], "thickness": 0.1016},
                {"material": self.library_materials["M15 200mm heavyweight concrete"], "thickness": 0.2032},
                {"material": self.library_materials["I02 50mm insulation board"], "thickness": 0.0508},
                {"material": self.library_materials["F04 Wall air space resistance"], "thickness": 0.01},
                {"material": self.library_materials["G01a 19mm gypsum board"], "thickness": 0.0191}
            ]),
            Construction("Heavy Roof/Ceiling", "Roof", [
                {"material": self.library_materials["M15 200mm heavyweight concrete"], "thickness": 0.2032},
                {"material": self.library_materials["F05 Ceiling air space resistance"], "thickness": 0.01},
                {"material": self.library_materials["F16 Acoustic tile"], "thickness": 0.0191}
            ]),
            Construction("Heavy Floor", "Floor", [
                {"material": self.library_materials["F16 Acoustic tile"], "thickness": 0.0191},
                {"material": self.library_materials["F05 Ceiling air space resistance"], "thickness": 0.01},
                {"material": self.library_materials["M15 200mm heavyweight concrete"], "thickness": 0.2032}
            ]),
        ]
        return {cons.name: cons for cons in constructions}

    def get_all_materials(self, project_materials: Optional[Dict[str, Material]] = None) -> List[Material]:
        materials = list(self.library_materials.values())
        if project_materials:
            materials.extend(list(project_materials.values()))
        return materials

    def get_all_glazing_materials(self, project_glazing_materials: Optional[Dict[str, GlazingMaterial]] = None) -> List[GlazingMaterial]:
        materials = list(self.library_glazing_materials.values())
        if project_glazing_materials:
            materials.extend(list(project_glazing_materials.values()))
        return materials

    def get_all_door_materials(self, project_door_materials: Optional[Dict[str, DoorMaterial]] = None) -> List[DoorMaterial]:
        materials = list(self.library_door_materials.values())
        if project_door_materials:
            materials.extend(list(project_door_materials.values()))
        return materials

    def add_project_material(self, material: Material, project_materials: Dict[str, Material]) -> Tuple[bool, str]:
        if len(project_materials) >= 20:
            return False, "Maximum 20 project materials allowed."
        if material.name in project_materials or material.name in self.library_materials:
            return False, f"Material name '{material.name}' already exists."
        project_materials[material.name] = material
        return True, f"Material '{material.name}' added to project materials."

    def add_project_glazing_material(self, material: GlazingMaterial, project_glazing_materials: Dict[str, GlazingMaterial]) -> Tuple[bool, str]:
        if len(project_glazing_materials) >= 20:
            return False, "Maximum 20 project glazing materials allowed."
        if material.name in project_glazing_materials or material.name in self.library_glazing_materials:
            return False, f"Glazing material name '{material.name}' already exists."
        project_glazing_materials[material.name] = material
        return True, f"Glazing material '{material.name}' added to project glazing materials."

    def add_project_door_material(self, material: DoorMaterial, project_door_materials: Dict[str, DoorMaterial]) -> Tuple[bool, str]:
        if len(project_door_materials) >= 20:
            return False, "Maximum 20 project door materials allowed."
        if material.name in project_door_materials or material.name in self.library_door_materials:
            return False, f"Door material name '{material.name}' already exists."
        project_door_materials[material.name] = material
        return True, f"Door material '{material.name}' added to project door materials."

    def edit_project_material(self, old_name: str, new_material: Material, project_materials: Dict[str, Material],
                             components: Dict[str, List]) -> Tuple[bool, str]:
        if old_name not in project_materials:
            return False, f"Material '{old_name}' not found in project materials."
        if new_material.name != old_name and (new_material.name in project_materials or new_material.name in self.library_materials):
            return False, f"Material name '{new_material.name}' already exists."
        for comp_list in components.values():
            for comp in comp_list:
                if comp.construction and any(layer["material"].name == old_name for layer in comp.layers):
                    comp.layers = [{"material": new_material if layer["material"].name == old_name else layer["material"],
                                    "thickness": layer["thickness"]} for layer in comp.layers]
                    comp.construction = Construction(
                        name=comp.construction.name,
                        component_type=comp.construction.component_type,
                        layers=comp.layers,
                        is_library=comp.construction.is_library
                    )
        project_materials.pop(old_name)
        project_materials[new_material.name] = new_material
        return True, f"Material '{old_name}' updated to '{new_material.name}'."

    def edit_project_glazing_material(self, old_name: str, new_material: GlazingMaterial,
                                     project_glazing_materials: Dict[str, GlazingMaterial],
                                     components: Dict[str, List]) -> Tuple[bool, str]:
        if old_name not in project_glazing_materials:
            return False, f"Glazing material '{old_name}' not found in project glazing materials."
        if new_material.name != old_name and (new_material.name in project_glazing_materials or new_material.name in self.library_glazing_materials):
            return False, f"Glazing material name '{new_material.name}' already exists."
        for comp_list in components.values():
            for comp in comp_list:
                if comp.glazing_material and comp.glazing_material.name == old_name:
                    comp.glazing_material = new_material
                    comp.u_value = new_material.u_value
                    comp.shgc = new_material.shgc
        project_glazing_materials.pop(old_name)
        project_glazing_materials[new_material.name] = new_material
        return True, f"Glazing material '{old_name}' updated to '{new_material.name}'."

    def edit_project_door_material(self, old_name: str, new_material: DoorMaterial,
                                  project_door_materials: Dict[str, DoorMaterial],
                                  components: Dict[str, List]) -> Tuple[bool, str]:
        if old_name not in project_door_materials:
            return False, f"Door material '{old_name}' not found in project door materials."
        if new_material.name != old_name and (new_material.name in project_door_materials or new_material.name in self.library_door_materials):
            return False, f"Door material name '{new_material.name}' already exists."
        for comp_list in components.values():
            for comp in comp_list:
                if comp.door_material and comp.door_material.name == old_name:
                    comp.door_material = new_material
                    comp.u_value = new_material.u_value
                    comp.solar_absorptivity = new_material.solar_absorption
        project_door_materials.pop(old_name)
        project_door_materials[new_material.name] = new_material
        return True, f"Door material '{old_name}' updated to '{new_material.name}'."

    def delete_project_material(self, name: str, project_materials: Dict[str, Material], components: Dict[str, List]) -> Tuple[bool, str]:
        if name not in project_materials:
            return False, f"Material '{name}' not found in project materials."
        for cons in self.library_constructions.values():
            if any(layer["material"].name == name for layer in cons.layers):
                return False, f"Cannot delete '{name}' as it is used in library construction '{cons.name}'."
        for comp_type, comp_list in components.items():
            for comp in comp_list:
                if 'layers' in comp and any(layer["material"].name == name for layer in comp["layers"]):
                    return False, f"Cannot delete '{name}' as it is used in component '{comp['name']}' ({comp_type})."
        del project_materials[name]
        return True, f"Material '{name}' deleted successfully."

    def delete_project_glazing_material(self, name: str, project_glazing_materials: Dict[str, GlazingMaterial],
                                       components: Dict[str, List]) -> Tuple[bool, str]:
        if name not in project_glazing_materials:
            return False, f"Glazing material '{name}' not found in project glazing materials."
        for comp_list in components.values():
            for comp in comp_list:
                if comp.glazing_material and comp.glazing_material.name == name:
                    return False, f"Cannot delete '{name}' as it is used in component '{comp.name}'."
        del project_glazing_materials[name]
        return True, f"Glazing material '{name}' deleted successfully."

    def delete_project_door_material(self, name: str, project_door_materials: Dict[str, DoorMaterial],
                                    components: Dict[str, List]) -> Tuple[bool, str]:
        if name not in project_door_materials:
            return False, f"Door material '{name}' not found in project door materials."
        for comp_list in components.values():
            for comp in comp_list:
                if comp.door_material and comp.door_material.name == name:
                    return False, f"Cannot delete '{name}' as it is used in component '{comp.name}'."
        del project_door_materials[name]
        return True, f"Door material '{name}' deleted successfully."

    def add_project_construction(self, construction: Construction, project_constructions: Dict[str, Construction]) -> Tuple[bool, str]:
        if len(project_constructions) >= 20:
            return False, "Maximum 20 project constructions allowed."
        if construction.name in project_constructions or construction.name in self.library_constructions:
            return False, f"Construction name '{construction.name}' already exists."
        project_constructions[construction.name] = construction
        return True, f"Construction '{construction.name}' added to project constructions."

    def edit_project_construction(self, old_name: str, new_construction: Construction,
                                 project_constructions: Dict[str, Construction],
                                 components: Dict[str, List]) -> Tuple[bool, str]:
        if old_name not in project_constructions:
            return False, f"Construction '{old_name}' not found in project constructions."
        if new_construction.name != old_name and (new_construction.name in project_constructions or new_construction.name in self.library_constructions):
            return False, f"Construction name '{new_construction.name}' already exists."
        for comp_list in components.values():
            for comp in comp_list:
                if comp.construction and comp.construction.name == old_name:
                    comp.construction = new_construction
                    comp.layers = new_construction.layers
                    comp.u_value = new_construction.u_value
        project_constructions.pop(old_name)
        project_constructions[new_construction.name] = new_construction
        return True, f"Construction '{old_name}' updated to '{new_construction.name}'."

    def delete_project_construction(self, name: str, project_constructions: Dict[str, Construction],
                                   components: Dict[str, List]) -> Tuple[bool, str]:
        if name not in project_constructions:
            return False, f"Construction '{name}' not found in project constructions."
        for comp_list in components.values():
            for comp in comp_list:
                if comp.construction and comp.construction.name == name:
                    return False, f"Construction '{name}' is used in component '{comp.name}'."
        project_constructions.pop(name)
        return True, f"Construction '{name}' deleted."

    def to_dataframe(self, data_type: str, project_materials: Optional[Dict[str, Material]] = None,
                     project_constructions: Optional[Dict[str, Construction]] = None,
                     project_glazing_materials: Optional[Dict[str, GlazingMaterial]] = None,
                     project_door_materials: Optional[Dict[str, DoorMaterial]] = None,
                     only_project: bool = False) -> pd.DataFrame:
        if data_type == "materials":
            data = []
            materials = project_materials.values() if only_project else self.get_all_materials(project_materials)
            for mat in materials:
                data.append({
                    "Name": mat.name,
                    "Category": mat.category.value,
                    "Conductivity (W/m·K)": mat.conductivity,
                    "Density (kg/m³)": mat.density,
                    "Specific Heat (J/kg·K)": mat.specific_heat,
                    "Default Thickness (m)": mat.default_thickness,
                    "Embodied Carbon (kgCO₂e/kg)": mat.embodied_carbon,
                    "Solar Absorption": mat.solar_absorption,
                    "Price (USD/m²)": mat.price,
                    "Source": "Project" if not mat.is_library else "Library"
                })
            return pd.DataFrame(data)
        elif data_type == "constructions":
            data = []
            constructions = project_constructions.values() if only_project else list(self.library_constructions.values())
            if not only_project and project_constructions:
                constructions.extend(list(project_constructions.values()))
            for cons in constructions:
                layers_str = "; ".join(f"{layer['material'].name} ({layer['thickness']}m)" for layer in cons.layers)
                data.append({
                    "Name": cons.name,
                    "Component Type": cons.component_type,
                    "U-Value (W/m²·K)": cons.u_value,
                    "Total Thickness (m)": cons.total_thickness,
                    "Embodied Carbon (kgCO₂e/m²)": cons.embodied_carbon,
                    "Solar Absorption": cons.solar_absorption,
                    "Price (USD/m²)": cons.price,
                    "Layers": layers_str,
                    "Source": "Project" if not cons.is_library else "Library"
                })
            return pd.DataFrame(data)
        elif data_type == "glazing_materials":
            data = []
            glazing_materials = project_glazing_materials.values() if only_project else self.get_all_glazing_materials(project_glazing_materials)
            for mat in glazing_materials:
                data.append({
                    "Name": mat.name,
                    "U-Value (W/m²·K)": mat.u_value,
                    "SHGC": mat.shgc,
                    "Embodied Carbon (kgCO₂e/m²)": mat.embodied_carbon,
                    "Price (USD/m²)": mat.price,
                    "Source": "Project" if not mat.is_library else "Library"
                })
            return pd.DataFrame(data)
        elif data_type == "door_materials":
            data = []
            door_materials = project_door_materials.values() if only_project else self.get_all_door_materials(project_door_materials)
            for mat in door_materials:
                data.append({
                    "Name": mat.name,
                    "U-Value (W/m²·K)": mat.u_value,
                    "Solar Absorption": mat.solar_absorption,
                    "Embodied Carbon (kgCO₂e/m²)": mat.embodied_carbon,
                    "Price (USD/m²)": mat.price,
                    "Source": "Project" if not mat.is_library else "Library"
                })
            return pd.DataFrame(data)
        return pd.DataFrame()