mabuseif commited on
Commit
dfe6cfa
·
verified ·
1 Parent(s): d5288fc

Update app/materials_library.py

Browse files
Files changed (1) hide show
  1. app/materials_library.py +419 -426
app/materials_library.py CHANGED
@@ -44,19 +44,22 @@ COLOUR_CATEGORIES = [
44
  "Reflective" # Absorptivity: 0.10–0.20 (e.g., polished metals)
45
  ]
46
 
47
- # Default library materials with absorptivity, emissivity, and colour
 
 
 
 
48
  DEFAULT_MATERIALS = {
49
  "Brick": {
50
  "category": "Structural Materials",
51
  "thermal_conductivity": 0.72,
52
  "density": 1920.0,
53
  "specific_heat": 840.0,
54
- "thickness_range": [0.05, 0.3],
55
  "default_thickness": 0.1,
56
- "embodied_carbon": 240.0, # kg CO2e/m³
57
- "cost": 180.0, # USD/m³
58
- "absorptivity": 0.65, # Medium (reddish-brown brick)
59
- "emissivity": 0.90, # Typical for ceramics
60
  "colour": "Medium"
61
  },
62
  "Concrete": {
@@ -64,12 +67,11 @@ DEFAULT_MATERIALS = {
64
  "thermal_conductivity": 1.4,
65
  "density": 2300.0,
66
  "specific_heat": 880.0,
67
- "thickness_range": [0.05, 0.5],
68
  "default_thickness": 0.15,
69
  "embodied_carbon": 320.0,
70
  "cost": 120.0,
71
- "absorptivity": 0.60, # Medium (grey concrete)
72
- "emissivity": 0.88, # Typical for concrete
73
  "colour": "Medium"
74
  },
75
  "Gypsum Board": {
@@ -77,12 +79,11 @@ DEFAULT_MATERIALS = {
77
  "thermal_conductivity": 0.25,
78
  "density": 900.0,
79
  "specific_heat": 1000.0,
80
- "thickness_range": [0.01, 0.05],
81
  "default_thickness": 0.0125,
82
  "embodied_carbon": 120.0,
83
  "cost": 90.0,
84
- "absorptivity": 0.40, # Light (white-painted surface)
85
- "emissivity": 0.90, # Typical for painted surfaces
86
  "colour": "Light"
87
  },
88
  "Mineral Wool": {
@@ -90,12 +91,11 @@ DEFAULT_MATERIALS = {
90
  "thermal_conductivity": 0.04,
91
  "density": 30.0,
92
  "specific_heat": 840.0,
93
- "thickness_range": [0.025, 0.3],
94
  "default_thickness": 0.1,
95
  "embodied_carbon": 45.0,
96
  "cost": 70.0,
97
- "absorptivity": 0.50, # Light (light-colored insulation)
98
- "emissivity": 0.95, # Typical for fibrous materials
99
  "colour": "Light"
100
  },
101
  "EPS Insulation": {
@@ -103,12 +103,11 @@ DEFAULT_MATERIALS = {
103
  "thermal_conductivity": 0.035,
104
  "density": 25.0,
105
  "specific_heat": 1400.0,
106
- "thickness_range": [0.025, 0.3],
107
  "default_thickness": 0.1,
108
  "embodied_carbon": 88.0,
109
  "cost": 65.0,
110
- "absorptivity": 0.40, # Light (white foam)
111
- "emissivity": 0.92, # Typical for polymers
112
  "colour": "Light"
113
  },
114
  "Wood (Pine)": {
@@ -116,12 +115,11 @@ DEFAULT_MATERIALS = {
116
  "thermal_conductivity": 0.14,
117
  "density": 550.0,
118
  "specific_heat": 1600.0,
119
- "thickness_range": [0.01, 0.3],
120
  "default_thickness": 0.05,
121
  "embodied_carbon": 110.0,
122
  "cost": 250.0,
123
- "absorptivity": 0.65, # Medium (natural wood)
124
- "emissivity": 0.90, # Typical for wood
125
  "colour": "Medium"
126
  },
127
  "Steel": {
@@ -129,12 +127,11 @@ DEFAULT_MATERIALS = {
129
  "thermal_conductivity": 50.0,
130
  "density": 7800.0,
131
  "specific_heat": 450.0,
132
- "thickness_range": [0.001, 0.05],
133
  "default_thickness": 0.005,
134
  "embodied_carbon": 1750.0,
135
  "cost": 800.0,
136
- "absorptivity": 0.15, # Reflective (polished steel)
137
- "emissivity": 0.20, # Typical for polished metals
138
  "colour": "Reflective"
139
  },
140
  "Aluminum": {
@@ -142,12 +139,11 @@ DEFAULT_MATERIALS = {
142
  "thermal_conductivity": 230.0,
143
  "density": 2700.0,
144
  "specific_heat": 880.0,
145
- "thickness_range": [0.001, 0.05],
146
  "default_thickness": 0.003,
147
  "embodied_carbon": 8500.0,
148
  "cost": 1200.0,
149
- "absorptivity": 0.10, # Reflective (polished aluminum)
150
- "emissivity": 0.05, # Typical for polished aluminum
151
  "colour": "Reflective"
152
  },
153
  "Glass Fiber Insulation": {
@@ -155,12 +151,11 @@ DEFAULT_MATERIALS = {
155
  "thermal_conductivity": 0.035,
156
  "density": 12.0,
157
  "specific_heat": 840.0,
158
- "thickness_range": [0.025, 0.3],
159
  "default_thickness": 0.1,
160
  "embodied_carbon": 28.0,
161
  "cost": 45.0,
162
- "absorptivity": 0.50, # Light (light-colored fibers)
163
- "emissivity": 0.95, # Typical for fibrous materials
164
  "colour": "Light"
165
  },
166
  "Ceramic Tile": {
@@ -168,12 +163,11 @@ DEFAULT_MATERIALS = {
168
  "thermal_conductivity": 1.3,
169
  "density": 2300.0,
170
  "specific_heat": 840.0,
171
- "thickness_range": [0.005, 0.025],
172
  "default_thickness": 0.01,
173
  "embodied_carbon": 650.0,
174
  "cost": 280.0,
175
- "absorptivity": 0.60, # Medium (neutral-colored tiles)
176
- "emissivity": 0.90, # Typical for ceramics
177
  "colour": "Medium"
178
  },
179
  "Carpet": {
@@ -181,12 +175,11 @@ DEFAULT_MATERIALS = {
181
  "thermal_conductivity": 0.06,
182
  "density": 200.0,
183
  "specific_heat": 1300.0,
184
- "thickness_range": [0.005, 0.02],
185
  "default_thickness": 0.01,
186
  "embodied_carbon": 40.0,
187
  "cost": 150.0,
188
- "absorptivity": 0.70, # Medium (typical carpet colors)
189
- "emissivity": 0.95, # Typical for fabrics
190
  "colour": "Medium"
191
  },
192
  "Plywood": {
@@ -194,12 +187,11 @@ DEFAULT_MATERIALS = {
194
  "thermal_conductivity": 0.13,
195
  "density": 560.0,
196
  "specific_heat": 1400.0,
197
- "thickness_range": [0.006, 0.025],
198
  "default_thickness": 0.012,
199
  "embodied_carbon": 350.0,
200
  "cost": 180.0,
201
- "absorptivity": 0.65, # Medium (natural wood)
202
- "emissivity": 0.90, # Typical for wood
203
  "colour": "Medium"
204
  },
205
  "Concrete Block": {
@@ -207,12 +199,11 @@ DEFAULT_MATERIALS = {
207
  "thermal_conductivity": 0.51,
208
  "density": 1400.0,
209
  "specific_heat": 1000.0,
210
- "thickness_range": [0.1, 0.3],
211
  "default_thickness": 0.2,
212
  "embodied_carbon": 260.0,
213
  "cost": 140.0,
214
- "absorptivity": 0.60, # Medium (grey blocks)
215
- "emissivity": 0.88, # Typical for concrete
216
  "colour": "Medium"
217
  },
218
  "Stone (Granite)": {
@@ -220,12 +211,11 @@ DEFAULT_MATERIALS = {
220
  "thermal_conductivity": 2.8,
221
  "density": 2600.0,
222
  "specific_heat": 790.0,
223
- "thickness_range": [0.01, 0.1],
224
  "default_thickness": 0.03,
225
  "embodied_carbon": 170.0,
226
  "cost": 450.0,
227
- "absorptivity": 0.85, # Dark (dark stone)
228
- "emissivity": 0.90, # Typical for stone
229
  "colour": "Dark"
230
  },
231
  "Polyurethane Insulation": {
@@ -233,17 +223,16 @@ DEFAULT_MATERIALS = {
233
  "thermal_conductivity": 0.025,
234
  "density": 30.0,
235
  "specific_heat": 1400.0,
236
- "thickness_range": [0.025, 0.2],
237
  "default_thickness": 0.075,
238
  "embodied_carbon": 102.0,
239
  "cost": 95.0,
240
- "absorptivity": 0.40, # Light (light-colored foam)
241
- "emissivity": 0.92, # Typical for polymers
242
  "colour": "Light"
243
  }
244
  }
245
 
246
- # Default library fenestrations with absorptivity and emissivity
247
  DEFAULT_FENESTRATIONS = {
248
  "Single Glazing": {
249
  "type": "Window",
@@ -251,11 +240,8 @@ DEFAULT_FENESTRATIONS = {
251
  "shgc": 0.86,
252
  "visible_transmittance": 0.9,
253
  "thickness": 0.006,
254
- "embodied_carbon": 800.0, # kg CO2e/m²
255
- "cost": 120.0, # USD/m²
256
- "absorptivity": 0.15, # Reflective (clear glass)
257
- "emissivity": 0.84, # Typical for glass
258
- "colour": "Reflective"
259
  },
260
  "Double Glazing (Air)": {
261
  "type": "Window",
@@ -264,10 +250,7 @@ DEFAULT_FENESTRATIONS = {
264
  "visible_transmittance": 0.78,
265
  "thickness": 0.024,
266
  "embodied_carbon": 950.0,
267
- "cost": 180.0,
268
- "absorptivity": 0.15, # Reflective
269
- "emissivity": 0.84,
270
- "colour": "Reflective"
271
  },
272
  "Double Glazing (Argon)": {
273
  "type": "Window",
@@ -276,10 +259,7 @@ DEFAULT_FENESTRATIONS = {
276
  "visible_transmittance": 0.76,
277
  "thickness": 0.024,
278
  "embodied_carbon": 980.0,
279
- "cost": 220.0,
280
- "absorptivity": 0.15, # Reflective
281
- "emissivity": 0.84,
282
- "colour": "Reflective"
283
  },
284
  "Triple Glazing": {
285
  "type": "Window",
@@ -288,10 +268,7 @@ DEFAULT_FENESTRATIONS = {
288
  "visible_transmittance": 0.68,
289
  "thickness": 0.036,
290
  "embodied_carbon": 1100.0,
291
- "cost": 320.0,
292
- "absorptivity": 0.15, # Reflective
293
- "emissivity": 0.84,
294
- "colour": "Reflective"
295
  },
296
  "Low-E Double Glazing": {
297
  "type": "Window",
@@ -300,10 +277,7 @@ DEFAULT_FENESTRATIONS = {
300
  "visible_transmittance": 0.74,
301
  "thickness": 0.024,
302
  "embodied_carbon": 1050.0,
303
- "cost": 250.0,
304
- "absorptivity": 0.10, # Reflective (low-E coating)
305
- "emissivity": 0.10, # Typical for low-E glass
306
- "colour": "Reflective"
307
  },
308
  "Wooden Door": {
309
  "type": "Door",
@@ -312,10 +286,7 @@ DEFAULT_FENESTRATIONS = {
312
  "visible_transmittance": 0.0,
313
  "thickness": 0.04,
314
  "embodied_carbon": 400.0,
315
- "cost": 280.0,
316
- "absorptivity": 0.65, # Medium (natural wood)
317
- "emissivity": 0.90,
318
- "colour": "Medium"
319
  },
320
  "Steel Door": {
321
  "type": "Door",
@@ -324,10 +295,7 @@ DEFAULT_FENESTRATIONS = {
324
  "visible_transmittance": 0.0,
325
  "thickness": 0.035,
326
  "embodied_carbon": 1200.0,
327
- "cost": 350.0,
328
- "absorptivity": 0.15, # Reflective (polished steel)
329
- "emissivity": 0.20,
330
- "colour": "Reflective"
331
  },
332
  "Glass Door": {
333
  "type": "Door",
@@ -336,10 +304,7 @@ DEFAULT_FENESTRATIONS = {
336
  "visible_transmittance": 0.7,
337
  "thickness": 0.01,
338
  "embodied_carbon": 900.0,
339
- "cost": 420.0,
340
- "absorptivity": 0.15, # Reflective
341
- "emissivity": 0.84,
342
- "colour": "Reflective"
343
  },
344
  "Skylight (Double Glazed)": {
345
  "type": "Skylight",
@@ -348,10 +313,7 @@ DEFAULT_FENESTRATIONS = {
348
  "visible_transmittance": 0.75,
349
  "thickness": 0.024,
350
  "embodied_carbon": 1050.0,
351
- "cost": 380.0,
352
- "absorptivity": 0.15, # Reflective
353
- "emissivity": 0.84,
354
- "colour": "Reflective"
355
  },
356
  "Skylight (Low-E)": {
357
  "type": "Skylight",
@@ -360,13 +322,20 @@ DEFAULT_FENESTRATIONS = {
360
  "visible_transmittance": 0.7,
361
  "thickness": 0.024,
362
  "embodied_carbon": 1150.0,
363
- "cost": 450.0,
364
- "absorptivity": 0.10, # Reflective (low-E coating)
365
- "emissivity": 0.10,
366
- "colour": "Reflective"
367
  }
368
  }
369
 
 
 
 
 
 
 
 
 
 
 
370
  def display_materials_page():
371
  """
372
  Display the material library page.
@@ -411,11 +380,10 @@ def display_materials_tab():
411
  col1, col2 = st.columns([3, 2])
412
 
413
  with col1:
414
- # Category filter
415
  filter_options = ["All"] + MATERIAL_CATEGORIES
416
  category = st.selectbox("Filter by Category", filter_options, key="material_filter")
417
 
418
- # Library Materials
419
  st.subheader("Library Materials")
420
  with st.container():
421
  library_materials = st.session_state.project_data["materials"]["library"]
@@ -428,7 +396,7 @@ def display_materials_tab():
428
  if filtered_materials:
429
  cols = st.columns([2, 1, 1, 1, 1])
430
  cols[0].write("**Name**")
431
- cols[1].write("**Thermal Mass (J/kg·K)**")
432
  cols[2].write("**U-Value (W/m²·K)**")
433
  cols[3].write("**Preview**")
434
  cols[4].write("**Copy**")
@@ -436,92 +404,141 @@ def display_materials_tab():
436
  for material in filtered_materials:
437
  cols = st.columns([2, 1, 1, 1, 1])
438
  name = [k for k, v in library_materials.items() if v == material][0]
439
- u_value = material["thermal_conductivity"] / material["default_thickness"]
 
440
  cols[0].write(name)
441
- cols[1].write(f"{material['specific_heat']:.1f}")
442
  cols[2].write(f"{u_value:.3f}")
443
- if cols[3].button("Preview", key=f"preview_lib_mat_{name}"):
444
- st.session_state.material_editor = {
445
- "name": name,
446
- "category": material["category"],
447
- "thermal_conductivity": material["thermal_conductivity"],
448
- "density": material["density"],
449
- "specific_heat": material["specific_heat"],
450
- "thickness_range": material["thickness_range"],
451
- "default_thickness": material["default_thickness"],
452
- "embodied_carbon": material["embodied_carbon"],
453
- "cost": material["cost"],
454
- "absorptivity": material["absorptivity"],
455
- "emissivity": material["emissivity"],
456
- "colour": material["colour"],
457
- "edit_mode": False,
458
- "original_name": name,
459
- "is_library": True
460
- }
461
- st.session_state.active_tab = "Materials"
462
- st.success(f"Previewing material '{name}'")
463
- if cols[4].button("Copy", key=f"copy_lib_mat_{name}"):
464
- new_name = f"{name}_Project"
465
- counter = 1
466
- while new_name in st.session_state.project_data["materials"]["project"] or new_name in library_materials:
467
- new_name = f"{name}_Project_{counter}"
468
- counter += 1
469
- st.session_state.project_data["materials"]["project"][new_name] = material.copy()
470
- st.success(f"Material '{new_name}' copied to project.")
471
- logger.info(f"Copied library material '{name}' as '{new_name}' to project")
472
- st.rerun()
 
 
 
 
 
473
  else:
474
  st.info("No materials found in the selected category.")
475
 
476
  # Project Materials
 
477
  st.subheader("Project Materials")
478
  with st.container():
479
  project_materials = st.session_state.project_data["materials"]["project"]
480
- if project_materials:
 
 
 
 
 
 
481
  cols = st.columns([2, 1, 1, 1, 1])
482
  cols[0].write("**Name**")
483
- cols[1].write("**Thermal Mass (J/kg·K)**")
484
  cols[2].write("**U-Value (W/m²·K)**")
485
  cols[3].write("**Edit**")
486
  cols[4].write("**Delete**")
487
 
488
- for name, material in project_materials.items():
489
  cols = st.columns([2, 1, 1, 1, 1])
490
- u_value = material["thermal_conductivity"] / material["default_thickness"]
 
 
491
  cols[0].write(name)
492
- cols[1].write(f"{material['specific_heat']:.1f}")
493
  cols[2].write(f"{u_value:.3f}")
494
- if cols[3].button("Edit", key=f"edit_proj_mat_{name}"):
495
- st.session_state.material_editor = {
496
- "name": name,
497
- "category": material["category"],
498
- "thermal_conductivity": material["thermal_conductivity"],
499
- "density": material["density"],
500
- "specific_heat": material["specific_heat"],
501
- "thickness_range": material["thickness_range"],
502
- "default_thickness": material["default_thickness"],
503
- "embodied_carbon": material["embodied_carbon"],
504
- "cost": material["cost"],
505
- "absorptivity": material["absorptivity"],
506
- "emissivity": material["emissivity"],
507
- "colour": material["colour"],
508
- "edit_mode": True,
509
- "original_name": name,
510
- "is_library": False
511
- }
512
- st.session_state.active_tab = "Materials"
513
- st.success(f"Editing material '{name}'")
514
- if cols[4].button("Delete", key=f"delete_proj_mat_{name}"):
515
- is_in_use = check_material_in_use(name)
516
- if is_in_use:
517
- st.error(f"Cannot delete material '{name}' because it is in use in constructions.")
518
- else:
519
- del st.session_state.project_data["materials"]["project"][name]
520
- st.success(f"Material '{name}' deleted from project.")
521
- logger.info(f"Deleted material '{name}' from project")
522
- st.rerun()
 
 
 
 
 
523
  else:
524
- st.info("No project materials added.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
 
526
  with col2:
527
  st.subheader("Material Editor/Creator")
@@ -564,43 +581,53 @@ def display_fenestrations_tab():
564
  cols[0].write(name)
565
  cols[1].write(fenestration["type"])
566
  cols[2].write(f"{fenestration['u_value']:.2f}")
567
- if cols[3].button("Preview", key=f"preview_lib_fen_{name}"):
568
- st.session_state.fenestration_editor = {
569
- "name": name,
570
- "type": fenestration["type"],
571
- "u_value": fenestration["u_value"],
572
- "shgc": fenestration["shgc"],
573
- "visible_transmittance": fenestration["visible_transmittance"],
574
- "thickness": fenestration["thickness"],
575
- "embodied_carbon": fenestration["embodied_carbon"],
576
- "cost": fenestration["cost"],
577
- "absorptivity": fenestration["absorptivity"],
578
- "emissivity": fenestration["emissivity"],
579
- "colour": fenestration["colour"],
580
- "edit_mode": False,
581
- "original_name": name,
582
- "is_library": True
583
- }
584
- st.session_state.active_tab = "Fenestrations"
585
- st.success(f"Previewing fenestration '{name}'")
586
- if cols[4].button("Copy", key=f"copy_lib_fen_{name}"):
587
- new_name = f"{name}_Project"
588
- counter = 1
589
- while new_name in st.session_state.project_data["fenestrations"]["project"] or new_name in library_fenestrations:
590
- new_name = f"{name}_Project_{counter}"
591
- counter += 1
592
- st.session_state.project_data["fenestrations"]["project"][new_name] = fenestration.copy()
593
- st.success(f"Fenestration '{new_name}' copied to project.")
594
- logger.info(f"Copied library fenestration '{name}' as '{new_name}' to project")
595
- st.rerun()
 
 
 
596
  else:
597
  st.info("No fenestrations found in the selected type.")
598
 
599
  # Project Fenestrations
 
600
  st.subheader("Project Fenestrations")
601
  with st.container():
602
  project_fenestrations = st.session_state.project_data["fenestrations"]["project"]
603
- if project_fenestrations:
 
 
 
 
 
 
604
  cols = st.columns([2, 1, 1, 1, 1])
605
  cols[0].write("**Name**")
606
  cols[1].write("**Type**")
@@ -608,41 +635,67 @@ def display_fenestrations_tab():
608
  cols[3].write("**Edit**")
609
  cols[4].write("**Delete**")
610
 
611
- for name, fenestration in project_fenestrations.items():
612
  cols = st.columns([2, 1, 1, 1, 1])
 
613
  cols[0].write(name)
614
  cols[1].write(fenestration["type"])
615
  cols[2].write(f"{fenestration['u_value']:.2f}")
616
- if cols[3].button("Edit", key=f"edit_proj_fen_{name}"):
617
- st.session_state.fenestration_editor = {
618
- "name": name,
619
- "type": fenestration["type"],
620
- "u_value": fenestration["u_value"],
621
- "shgc": fenestration["shgc"],
622
- "visible_transmittance": fenestration["visible_transmittance"],
623
- "thickness": fenestration["thickness"],
624
- "embodied_carbon": fenestration["embodied_carbon"],
625
- "cost": fenestration["cost"],
626
- "absorptivity": fenestration["absorptivity"],
627
- "emissivity": fenestration["emissivity"],
628
- "colour": fenestration["colour"],
629
- "edit_mode": True,
630
- "original_name": name,
631
- "is_library": False
632
- }
633
- st.session_state.active_tab = "Fenestrations"
634
- st.success(f"Editing fenestration '{name}'")
635
- if cols[4].button("Delete", key=f"delete_proj_fen_{name}"):
636
- is_in_use = check_fenestration_in_use(name)
637
- if is_in_use:
638
- st.error(f"Cannot delete fenestration '{name}' because it is in use in components.")
639
- else:
640
- del st.session_state.project_data["fenestrations"]["project"][name]
641
- st.success(f"Fenestration '{name}' deleted from project.")
642
- logger.info(f"Deleted fenestration '{name}' from project")
643
- st.rerun()
 
 
 
644
  else:
645
- st.info("No project fenestrations added.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
646
 
647
  with col2:
648
  st.subheader("Fenestration Editor/Creator")
@@ -653,7 +706,8 @@ def initialize_materials():
653
  if "materials" not in st.session_state.project_data:
654
  st.session_state.project_data["materials"] = {
655
  "library": {},
656
- "project": {}
 
657
  }
658
 
659
  # Initialize library materials if empty
@@ -668,7 +722,6 @@ def initialize_materials():
668
  "thermal_conductivity": 0.5,
669
  "density": 1000.0,
670
  "specific_heat": 1000.0,
671
- "thickness_range": [0.01, 0.2],
672
  "default_thickness": 0.05,
673
  "embodied_carbon": 100.0,
674
  "cost": 100.0,
@@ -679,13 +732,16 @@ def initialize_materials():
679
  "original_name": "",
680
  "is_library": False
681
  }
 
 
682
 
683
  def initialize_fenestrations():
684
  """Initialize fenestrations in session state if not present."""
685
  if "fenestrations" not in st.session_state.project_data:
686
  st.session_state.project_data["fenestrations"] = {
687
  "library": {},
688
- "project": {}
 
689
  }
690
 
691
  # Initialize library fenestrations if empty
@@ -703,13 +759,12 @@ def initialize_fenestrations():
703
  "thickness": 0.024,
704
  "embodied_carbon": 900.0,
705
  "cost": 200.0,
706
- "absorptivity": 0.15,
707
- "emissivity": 0.84,
708
- "colour": "Reflective",
709
  "edit_mode": False,
710
  "original_name": "",
711
  "is_library": False
712
  }
 
 
713
 
714
  def display_material_editor():
715
  """Display the material editor form."""
@@ -772,26 +827,50 @@ def display_material_editor():
772
  disabled=is_library
773
  )
774
 
775
- # Thickness range
776
- min_thickness, max_thickness = editor_state["thickness_range"]
777
- thickness_range = st.slider(
778
- "Thickness Range (m)",
779
  min_value=0.001,
780
  max_value=0.5,
781
- value=(float(min_thickness), float(max_thickness)),
782
  format="%.3f",
783
- help="Minimum and maximum thickness range for this material in meters.",
784
  disabled=is_library
785
  )
786
 
787
- # Default thickness
788
- default_thickness = st.number_input(
789
- "Default Thickness (m)",
790
- min_value=float(thickness_range[0]),
791
- max_value=float(thickness_range[1]),
792
- value=min(max(float(editor_state["default_thickness"]), float(thickness_range[0])), float(thickness_range[1])),
793
- format="%.3f",
794
- help="Default thickness for this material in meters.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
  disabled=is_library
796
  )
797
 
@@ -810,17 +889,6 @@ def display_material_editor():
810
  help="Embodied carbon in kg CO₂e per cubic meter.",
811
  disabled=is_library
812
  )
813
-
814
- # Absorptivity
815
- absorptivity = st.number_input(
816
- "Absorptivity",
817
- min_value=0.0,
818
- max_value=1.0,
819
- value=float(editor_state["absorptivity"]),
820
- format="%.2f",
821
- help="Solar radiation absorbed (0-1).",
822
- disabled=is_library
823
- )
824
 
825
  with col2:
826
  # Cost
@@ -833,26 +901,6 @@ def display_material_editor():
833
  help="Material cost in USD per cubic meter.",
834
  disabled=is_library
835
  )
836
-
837
- # Emissivity
838
- emissivity = st.number_input(
839
- "Emissivity",
840
- min_value=0.0,
841
- max_value=1.0,
842
- value=float(editor_state["emissivity"]),
843
- format="%.2f",
844
- help="Ratio of radiation emitted (0-1).",
845
- disabled=is_library
846
- )
847
-
848
- # Colour
849
- colour = st.selectbox(
850
- "Colour Category",
851
- COLOUR_CATEGORIES,
852
- index=COLOUR_CATEGORIES.index(editor_state["colour"]) if editor_state["colour"] in COLOUR_CATEGORIES else 0,
853
- help="Colour category affecting absorptivity.",
854
- disabled=is_library
855
- )
856
 
857
  # Form submission buttons
858
  col1, col2 = st.columns(2)
@@ -865,54 +913,59 @@ def display_material_editor():
865
 
866
  # Handle form submission
867
  if submit_button and not is_library:
868
- # Validate inputs
869
- validation_errors = validate_material(
870
- name, category, thermal_conductivity, density, specific_heat,
871
- thickness_range, default_thickness, embodied_carbon, cost,
872
- editor_state["edit_mode"], editor_state["original_name"],
873
- absorptivity, emissivity, colour
874
- )
875
-
876
- if validation_errors:
877
- for error in validation_errors:
878
- st.error(error)
879
- else:
880
- # Create material data
881
- material_data = {
882
- "category": category,
883
- "thermal_conductivity": thermal_conductivity,
884
- "density": density,
885
- "specific_heat": specific_heat,
886
- "thickness_range": list(thickness_range),
887
- "default_thickness": default_thickness,
888
- "embodied_carbon": embodied_carbon,
889
- "cost": cost,
890
- "absorptivity": absorptivity,
891
- "emissivity": emissivity,
892
- "colour": colour
893
- }
894
 
895
- # Handle edit mode
896
- if editor_state["edit_mode"]:
897
- original_name = editor_state["original_name"]
898
- if original_name != name:
899
- del st.session_state.project_data["materials"]["project"][original_name]
900
- st.session_state.project_data["materials"]["project"][name] = material_data
901
- st.success(f"Material '{name}' updated successfully.")
902
- logger.info(f"Updated material '{name}' in project")
903
  else:
904
- st.session_state.project_data["materials"]["project"][name] = material_data
905
- st.success(f"Material '{name}' added to your project.")
906
- logger.info(f"Added new material '{name}' to project")
907
-
908
- # Reset editor
909
- reset_material_editor()
910
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
911
 
912
  # Handle clear button
913
  if clear_button:
914
- reset_material_editor()
915
- st.rerun()
 
 
 
916
 
917
  def display_fenestration_editor():
918
  """Display the fenestration editor form."""
@@ -1001,17 +1054,6 @@ def display_fenestration_editor():
1001
  help="Embodied carbon in kg CO₂e per square meter.",
1002
  disabled=is_library
1003
  )
1004
-
1005
- # Absorptivity
1006
- absorptivity = st.number_input(
1007
- "Absorptivity",
1008
- min_value=0.0,
1009
- max_value=1.0,
1010
- value=float(editor_state["absorptivity"]),
1011
- format="%.2f",
1012
- help="Solar radiation absorbed (0-1).",
1013
- disabled=is_library
1014
- )
1015
 
1016
  with col2:
1017
  # Cost
@@ -1024,26 +1066,6 @@ def display_fenestration_editor():
1024
  help="Fenestration cost in USD per square meter.",
1025
  disabled=is_library
1026
  )
1027
-
1028
- # Emissivity
1029
- emissivity = st.number_input(
1030
- "Emissivity",
1031
- min_value=0.0,
1032
- max_value=1.0,
1033
- value=float(editor_state["emissivity"]),
1034
- format="%.2f",
1035
- help="Ratio of radiation emitted (0-1).",
1036
- disabled=is_library
1037
- )
1038
-
1039
- # Colour
1040
- colour = st.selectbox(
1041
- "Colour Category",
1042
- COLOUR_CATEGORIES,
1043
- index=COLOUR_CATEGORIES.index(editor_state["colour"]) if editor_state["colour"] in COLOUR_CATEGORIES else 0,
1044
- help="Colour category affecting absorptivity.",
1045
- disabled=is_library
1046
- )
1047
 
1048
  # Form submission buttons
1049
  col1, col2 = st.columns(2)
@@ -1056,57 +1078,59 @@ def display_fenestration_editor():
1056
 
1057
  # Handle form submission
1058
  if submit_button and not is_library:
1059
- # Validate inputs
1060
- validation_errors = validate_fenestration(
1061
- name, fenestration_type, u_value, shgc, visible_transmittance, thickness,
1062
- embodied_carbon, cost, editor_state["edit_mode"], editor_state["original_name"],
1063
- absorptivity, emissivity, colour
1064
- )
1065
-
1066
- if validation_errors:
1067
- for error in validation_errors:
1068
- st.error(error)
1069
- else:
1070
- # Create fenestration data
1071
- fenestration_data = {
1072
- "type": fenestration_type,
1073
- "u_value": u_value,
1074
- "shgc": shgc,
1075
- "visible_transmittance": visible_transmittance,
1076
- "thickness": thickness,
1077
- "embodied_carbon": embodied_carbon,
1078
- "cost": cost,
1079
- "absorptivity": absorptivity,
1080
- "emissivity": emissivity,
1081
- "colour": colour
1082
- }
1083
 
1084
- # Handle edit mode
1085
- if editor_state["edit_mode"]:
1086
- original_name = editor_state["original_name"]
1087
- if original_name != name:
1088
- del st.session_state.project_data["fenestrations"]["project"][original_name]
1089
- st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1090
- st.success(f"Fenestration '{name}' updated successfully.")
1091
- logger.info(f"Updated fenestration '{name}' in project")
1092
  else:
1093
- st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1094
- st.success(f"Fenestration '{name}' added to your project.")
1095
- logger.info(f"Added new fenestration '{name}' to project")
1096
-
1097
- # Reset editor
1098
- reset_fenestration_editor()
1099
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1100
 
1101
  # Handle clear button
1102
  if clear_button:
1103
- reset_fenestration_editor()
1104
- st.rerun()
 
 
 
1105
 
1106
  def validate_material(
1107
  name: str, category: str, thermal_conductivity: float, density: float,
1108
- specific_heat: float, thickness_range: Tuple[float, float], default_thickness: float,
1109
- embodied_carbon: float, cost: float, edit_mode: bool, original_name: str,
1110
  absorptivity: float, emissivity: float, colour: str
1111
  ) -> List[str]:
1112
  """
@@ -1139,15 +1163,9 @@ def validate_material(
1139
  if specific_heat <= 0:
1140
  errors.append("Specific heat must be greater than zero.")
1141
 
1142
- # Validate thickness range
1143
- if thickness_range[0] <= 0:
1144
- errors.append("Minimum thickness must be greater than zero.")
1145
- if thickness_range[0] >= thickness_range[1]:
1146
- errors.append("Maximum thickness must be greater than minimum thickness.")
1147
-
1148
  # Validate default thickness
1149
- if default_thickness < thickness_range[0] or default_thickness > thickness_range[1]:
1150
- errors.append("Default thickness must be within the thickness range.")
1151
 
1152
  # Validate embodied carbon
1153
  if embodied_carbon < 0:
@@ -1185,8 +1203,7 @@ def validate_material(
1185
  def validate_fenestration(
1186
  name: str, fenestration_type: str, u_value: float, shgc: float,
1187
  visible_transmittance: float, thickness: float, embodied_carbon: float,
1188
- cost: float, edit_mode: bool, original_name: str,
1189
- absorptivity: float, emissivity: float, colour: str
1190
  ) -> List[str]:
1191
  """
1192
  Validate fenestration inputs.
@@ -1230,29 +1247,6 @@ def validate_fenestration(
1230
  if cost < 0:
1231
  errors.append("Cost cannot be negative.")
1232
 
1233
- # Validate absorptivity
1234
- if absorptivity < 0 or absorptivity > 1:
1235
- errors.append("Absorptivity must be between 0 and 1.")
1236
-
1237
- # Validate emissivity
1238
- if emissivity < 0 or emissivity > 1:
1239
- errors.append("Emissivity must be between 0 and 1.")
1240
-
1241
- # Validate colour
1242
- if colour not in COLOUR_CATEGORIES:
1243
- errors.append("Please select a valid colour category.")
1244
-
1245
- # Validate absorptivity based on colour
1246
- colour_ranges = {
1247
- "Light": (0.30, 0.50),
1248
- "Medium": (0.60, 0.70),
1249
- "Dark": (0.85, 0.95),
1250
- "Reflective": (0.10, 0.20)
1251
- }
1252
- min_abs, max_abs = colour_ranges[colour]
1253
- if not (min_abs <= absorptivity <= max_abs):
1254
- errors.append(f"Absorptivity for {colour} colour must be between {min_abs} and {max_abs}.")
1255
-
1256
  return errors
1257
 
1258
  def reset_material_editor():
@@ -1263,7 +1257,6 @@ def reset_material_editor():
1263
  "thermal_conductivity": 0.5,
1264
  "density": 1000.0,
1265
  "specific_heat": 1000.0,
1266
- "thickness_range": [0.01, 0.2],
1267
  "default_thickness": 0.05,
1268
  "embodied_carbon": 100.0,
1269
  "cost": 100.0,
@@ -1274,6 +1267,7 @@ def reset_material_editor():
1274
  "original_name": "",
1275
  "is_library": False
1276
  }
 
1277
 
1278
  def reset_fenestration_editor():
1279
  """Reset the fenestration editor to default values."""
@@ -1286,13 +1280,11 @@ def reset_fenestration_editor():
1286
  "thickness": 0.024,
1287
  "embodied_carbon": 900.0,
1288
  "cost": 200.0,
1289
- "absorptivity": 0.15,
1290
- "emissivity": 0.84,
1291
- "colour": "Reflective",
1292
  "edit_mode": False,
1293
  "original_name": "",
1294
  "is_library": False
1295
  }
 
1296
 
1297
  def check_material_in_use(material_name: str) -> bool:
1298
  """
@@ -1330,7 +1322,8 @@ def display_materials_help():
1330
  * **Thermal Conductivity (W/m·K)**: Rate of heat transfer through a material. Lower values indicate better insulation.
1331
  * **Density (kg/m³)**: Mass per unit volume.
1332
  * **Specific Heat (J/kg·K)**: Energy required to raise the temperature of 1 kg by 1 K. Higher values indicate better thermal mass.
1333
- * **U-Value (W/m²·K)**: Overall heat transfer coefficient for fenestrations. Lower values indicate better insulation.
 
1334
  * **SHGC**: Solar Heat Gain Coefficient (0-1). Fraction of incident solar radiation that enters through a fenestration.
1335
  * **Visible Transmittance**: Fraction of visible light that passes through a fenestration.
1336
  * **Embodied Carbon**: Carbon emissions associated with material production, measured in kg CO₂e per unit volume or area.
 
44
  "Reflective" # Absorptivity: 0.10–0.20 (e.g., polished metals)
45
  ]
46
 
47
+ # Surface resistances
48
+ R_SI = 0.12 # Internal surface resistance (m²·K/W)
49
+ R_SE = 0.04 # External surface resistance (m²·K/W)
50
+
51
+ # Default library materials
52
  DEFAULT_MATERIALS = {
53
  "Brick": {
54
  "category": "Structural Materials",
55
  "thermal_conductivity": 0.72,
56
  "density": 1920.0,
57
  "specific_heat": 840.0,
 
58
  "default_thickness": 0.1,
59
+ "embodied_carbon": 240.0,
60
+ "cost": 180.0,
61
+ "absorptivity": 0.65,
62
+ "emissivity": 0.90,
63
  "colour": "Medium"
64
  },
65
  "Concrete": {
 
67
  "thermal_conductivity": 1.4,
68
  "density": 2300.0,
69
  "specific_heat": 880.0,
 
70
  "default_thickness": 0.15,
71
  "embodied_carbon": 320.0,
72
  "cost": 120.0,
73
+ "absorptivity": 0.60,
74
+ "emissivity": 0.88,
75
  "colour": "Medium"
76
  },
77
  "Gypsum Board": {
 
79
  "thermal_conductivity": 0.25,
80
  "density": 900.0,
81
  "specific_heat": 1000.0,
 
82
  "default_thickness": 0.0125,
83
  "embodied_carbon": 120.0,
84
  "cost": 90.0,
85
+ "absorptivity": 0.40,
86
+ "emissivity": 0.90,
87
  "colour": "Light"
88
  },
89
  "Mineral Wool": {
 
91
  "thermal_conductivity": 0.04,
92
  "density": 30.0,
93
  "specific_heat": 840.0,
 
94
  "default_thickness": 0.1,
95
  "embodied_carbon": 45.0,
96
  "cost": 70.0,
97
+ "absorptivity": 0.50,
98
+ "emissivity": 0.95,
99
  "colour": "Light"
100
  },
101
  "EPS Insulation": {
 
103
  "thermal_conductivity": 0.035,
104
  "density": 25.0,
105
  "specific_heat": 1400.0,
 
106
  "default_thickness": 0.1,
107
  "embodied_carbon": 88.0,
108
  "cost": 65.0,
109
+ "absorptivity": 0.40,
110
+ "emissivity": 0.92,
111
  "colour": "Light"
112
  },
113
  "Wood (Pine)": {
 
115
  "thermal_conductivity": 0.14,
116
  "density": 550.0,
117
  "specific_heat": 1600.0,
 
118
  "default_thickness": 0.05,
119
  "embodied_carbon": 110.0,
120
  "cost": 250.0,
121
+ "absorptivity": 0.65,
122
+ "emissivity": 0.90,
123
  "colour": "Medium"
124
  },
125
  "Steel": {
 
127
  "thermal_conductivity": 50.0,
128
  "density": 7800.0,
129
  "specific_heat": 450.0,
 
130
  "default_thickness": 0.005,
131
  "embodied_carbon": 1750.0,
132
  "cost": 800.0,
133
+ "absorptivity": 0.15,
134
+ "emissivity": 0.20,
135
  "colour": "Reflective"
136
  },
137
  "Aluminum": {
 
139
  "thermal_conductivity": 230.0,
140
  "density": 2700.0,
141
  "specific_heat": 880.0,
 
142
  "default_thickness": 0.003,
143
  "embodied_carbon": 8500.0,
144
  "cost": 1200.0,
145
+ "absorptivity": 0.10,
146
+ "emissivity": 0.05,
147
  "colour": "Reflective"
148
  },
149
  "Glass Fiber Insulation": {
 
151
  "thermal_conductivity": 0.035,
152
  "density": 12.0,
153
  "specific_heat": 840.0,
 
154
  "default_thickness": 0.1,
155
  "embodied_carbon": 28.0,
156
  "cost": 45.0,
157
+ "absorptivity": 0.50,
158
+ "emissivity": 0.95,
159
  "colour": "Light"
160
  },
161
  "Ceramic Tile": {
 
163
  "thermal_conductivity": 1.3,
164
  "density": 2300.0,
165
  "specific_heat": 840.0,
 
166
  "default_thickness": 0.01,
167
  "embodied_carbon": 650.0,
168
  "cost": 280.0,
169
+ "absorptivity": 0.60,
170
+ "emissivity": 0.90,
171
  "colour": "Medium"
172
  },
173
  "Carpet": {
 
175
  "thermal_conductivity": 0.06,
176
  "density": 200.0,
177
  "specific_heat": 1300.0,
 
178
  "default_thickness": 0.01,
179
  "embodied_carbon": 40.0,
180
  "cost": 150.0,
181
+ "absorptivity": 0.70,
182
+ "emissivity": 0.95,
183
  "colour": "Medium"
184
  },
185
  "Plywood": {
 
187
  "thermal_conductivity": 0.13,
188
  "density": 560.0,
189
  "specific_heat": 1400.0,
 
190
  "default_thickness": 0.012,
191
  "embodied_carbon": 350.0,
192
  "cost": 180.0,
193
+ "absorptivity": 0.65,
194
+ "emissivity": 0.90,
195
  "colour": "Medium"
196
  },
197
  "Concrete Block": {
 
199
  "thermal_conductivity": 0.51,
200
  "density": 1400.0,
201
  "specific_heat": 1000.0,
 
202
  "default_thickness": 0.2,
203
  "embodied_carbon": 260.0,
204
  "cost": 140.0,
205
+ "absorptivity": 0.60,
206
+ "emissivity": 0.88,
207
  "colour": "Medium"
208
  },
209
  "Stone (Granite)": {
 
211
  "thermal_conductivity": 2.8,
212
  "density": 2600.0,
213
  "specific_heat": 790.0,
 
214
  "default_thickness": 0.03,
215
  "embodied_carbon": 170.0,
216
  "cost": 450.0,
217
+ "absorptivity": 0.85,
218
+ "emissivity": 0.90,
219
  "colour": "Dark"
220
  },
221
  "Polyurethane Insulation": {
 
223
  "thermal_conductivity": 0.025,
224
  "density": 30.0,
225
  "specific_heat": 1400.0,
 
226
  "default_thickness": 0.075,
227
  "embodied_carbon": 102.0,
228
  "cost": 95.0,
229
+ "absorptivity": 0.40,
230
+ "emissivity": 0.92,
231
  "colour": "Light"
232
  }
233
  }
234
 
235
+ # Default library fenestrations
236
  DEFAULT_FENESTRATIONS = {
237
  "Single Glazing": {
238
  "type": "Window",
 
240
  "shgc": 0.86,
241
  "visible_transmittance": 0.9,
242
  "thickness": 0.006,
243
+ "embodied_carbon": 800.0,
244
+ "cost": 120.0
 
 
 
245
  },
246
  "Double Glazing (Air)": {
247
  "type": "Window",
 
250
  "visible_transmittance": 0.78,
251
  "thickness": 0.024,
252
  "embodied_carbon": 950.0,
253
+ "cost": 180.0
 
 
 
254
  },
255
  "Double Glazing (Argon)": {
256
  "type": "Window",
 
259
  "visible_transmittance": 0.76,
260
  "thickness": 0.024,
261
  "embodied_carbon": 980.0,
262
+ "cost": 220.0
 
 
 
263
  },
264
  "Triple Glazing": {
265
  "type": "Window",
 
268
  "visible_transmittance": 0.68,
269
  "thickness": 0.036,
270
  "embodied_carbon": 1100.0,
271
+ "cost": 320.0
 
 
 
272
  },
273
  "Low-E Double Glazing": {
274
  "type": "Window",
 
277
  "visible_transmittance": 0.74,
278
  "thickness": 0.024,
279
  "embodied_carbon": 1050.0,
280
+ "cost": 250.0
 
 
 
281
  },
282
  "Wooden Door": {
283
  "type": "Door",
 
286
  "visible_transmittance": 0.0,
287
  "thickness": 0.04,
288
  "embodied_carbon": 400.0,
289
+ "cost": 280.0
 
 
 
290
  },
291
  "Steel Door": {
292
  "type": "Door",
 
295
  "visible_transmittance": 0.0,
296
  "thickness": 0.035,
297
  "embodied_carbon": 1200.0,
298
+ "cost": 350.0
 
 
 
299
  },
300
  "Glass Door": {
301
  "type": "Door",
 
304
  "visible_transmittance": 0.7,
305
  "thickness": 0.01,
306
  "embodied_carbon": 900.0,
307
+ "cost": 420.0
 
 
 
308
  },
309
  "Skylight (Double Glazed)": {
310
  "type": "Skylight",
 
313
  "visible_transmittance": 0.75,
314
  "thickness": 0.024,
315
  "embodied_carbon": 1050.0,
316
+ "cost": 380.0
 
 
 
317
  },
318
  "Skylight (Low-E)": {
319
  "type": "Skylight",
 
322
  "visible_transmittance": 0.7,
323
  "thickness": 0.024,
324
  "embodied_carbon": 1150.0,
325
+ "cost": 450.0
 
 
 
326
  }
327
  }
328
 
329
+ def calculate_u_value(thermal_conductivity: float, thickness: float) -> float:
330
+ """Calculate U-value for a material."""
331
+ r_material = thickness / thermal_conductivity if thermal_conductivity > 0 else float('inf')
332
+ r_total = R_SI + r_material + R_SE
333
+ return 1 / r_total if r_total > 0 else 0.0
334
+
335
+ def calculate_thermal_mass(density: float, specific_heat: float, thickness: float) -> float:
336
+ """Calculate areal thermal mass for a material."""
337
+ return density * specific_heat * thickness
338
+
339
  def display_materials_page():
340
  """
341
  Display the material library page.
 
380
  col1, col2 = st.columns([3, 2])
381
 
382
  with col1:
383
+ # Library Materials
384
  filter_options = ["All"] + MATERIAL_CATEGORIES
385
  category = st.selectbox("Filter by Category", filter_options, key="material_filter")
386
 
 
387
  st.subheader("Library Materials")
388
  with st.container():
389
  library_materials = st.session_state.project_data["materials"]["library"]
 
396
  if filtered_materials:
397
  cols = st.columns([2, 1, 1, 1, 1])
398
  cols[0].write("**Name**")
399
+ cols[1].write("**Thermal Mass (J/m²·K)**")
400
  cols[2].write("**U-Value (W/m²·K)**")
401
  cols[3].write("**Preview**")
402
  cols[4].write("**Copy**")
 
404
  for material in filtered_materials:
405
  cols = st.columns([2, 1, 1, 1, 1])
406
  name = [k for k, v in library_materials.items() if v == material][0]
407
+ thermal_mass = calculate_thermal_mass(material["density"], material["specific_heat"], material["default_thickness"])
408
+ u_value = calculate_u_value(material["thermal_conductivity"], material["default_thickness"])
409
  cols[0].write(name)
410
+ cols[1].write(f"{thermal_mass:.1f}")
411
  cols[2].write(f"{u_value:.3f}")
412
+ action_id = f"preview_lib_mat_{name}_{uuid.uuid4()}"
413
+ if cols[3].button("Preview", key=action_id):
414
+ if st.session_state.get("material_action_id") != action_id:
415
+ st.session_state.material_action_id = action_id
416
+ st.session_state.material_editor = {
417
+ "name": name,
418
+ "category": material["category"],
419
+ "thermal_conductivity": material["thermal_conductivity"],
420
+ "density": material["density"],
421
+ "specific_heat": material["specific_heat"],
422
+ "default_thickness": material["default_thickness"],
423
+ "embodied_carbon": material["embodied_carbon"],
424
+ "cost": material["cost"],
425
+ "absorptivity": material["absorptivity"],
426
+ "emissivity": material["emissivity"],
427
+ "colour": material["colour"],
428
+ "edit_mode": False,
429
+ "original_name": name,
430
+ "is_library": True
431
+ }
432
+ st.session_state.active_tab = "Materials"
433
+ st.success(f"Previewing material '{name}'")
434
+ action_id = f"copy_lib_mat_{name}_{uuid.uuid4()}"
435
+ if cols[4].button("Copy", key=action_id):
436
+ if st.session_state.get("material_action_id") != action_id:
437
+ st.session_state.material_action_id = action_id
438
+ new_name = f"{name}_Project"
439
+ counter = 1
440
+ while new_name in st.session_state.project_data["materials"]["project"] or new_name in library_materials:
441
+ new_name = f"{name}_Project_{counter}"
442
+ counter += 1
443
+ st.session_state.project_data["materials"]["project"][new_name] = material.copy()
444
+ st.success(f"Material '{new_name}' copied to project.")
445
+ logger.info(f"Copied library material '{name}' as '{new_name}' to project")
446
+ st.rerun()
447
  else:
448
  st.info("No materials found in the selected category.")
449
 
450
  # Project Materials
451
+ project_category = st.selectbox("Filter Project by Category", filter_options, key="project_material_filter")
452
  st.subheader("Project Materials")
453
  with st.container():
454
  project_materials = st.session_state.project_data["materials"]["project"]
455
+ filtered_project_materials = []
456
+ if project_category == "All":
457
+ filtered_project_materials = list(project_materials.values())
458
+ else:
459
+ filtered_project_materials = [m for m in project_materials.values() if m["category"] == project_category]
460
+
461
+ if filtered_project_materials:
462
  cols = st.columns([2, 1, 1, 1, 1])
463
  cols[0].write("**Name**")
464
+ cols[1].write("**Thermal Mass (J/m²·K)**")
465
  cols[2].write("**U-Value (W/m²·K)**")
466
  cols[3].write("**Edit**")
467
  cols[4].write("**Delete**")
468
 
469
+ for material in filtered_project_materials:
470
  cols = st.columns([2, 1, 1, 1, 1])
471
+ name = [k for k, v in project_materials.items() if v == material][0]
472
+ thermal_mass = calculate_thermal_mass(material["density"], material["specific_heat"], material["default_thickness"])
473
+ u_value = calculate_u_value(material["thermal_conductivity"], material["default_thickness"])
474
  cols[0].write(name)
475
+ cols[1].write(f"{thermal_mass:.1f}")
476
  cols[2].write(f"{u_value:.3f}")
477
+ action_id = f"edit_proj_mat_{name}_{uuid.uuid4()}"
478
+ if cols[3].button("Edit", key=action_id):
479
+ if st.session_state.get("material_action_id") != action_id:
480
+ st.session_state.material_action_id = action_id
481
+ st.session_state.material_editor = {
482
+ "name": name,
483
+ "category": material["category"],
484
+ "thermal_conductivity": material["thermal_conductivity"],
485
+ "density": material["density"],
486
+ "specific_heat": material["specific_heat"],
487
+ "default_thickness": material["default_thickness"],
488
+ "embodied_carbon": material["embodied_carbon"],
489
+ "cost": material["cost"],
490
+ "absorptivity": material["absorptivity"],
491
+ "emissivity": material["emissivity"],
492
+ "colour": material["colour"],
493
+ "edit_mode": True,
494
+ "original_name": name,
495
+ "is_library": False
496
+ }
497
+ st.session_state.active_tab = "Materials"
498
+ st.success(f"Editing material '{name}'")
499
+ action_id = f"delete_proj_mat_{name}_{uuid.uuid4()}"
500
+ if cols[4].button("Delete", key=action_id):
501
+ if st.session_state.get("material_action_id") != action_id:
502
+ st.session_state.material_action_id = action_id
503
+ is_in_use = check_material_in_use(name)
504
+ if is_in_use:
505
+ st.error(f"Cannot delete material '{name}' because it is in use in constructions.")
506
+ else:
507
+ del st.session_state.project_data["materials"]["project"][name]
508
+ st.success(f"Material '{name}' deleted from project.")
509
+ logger.info(f"Deleted material '{name}' from project")
510
+ st.rerun()
511
  else:
512
+ st.info("No project materials in the selected category.")
513
+
514
+ # Comprehensive Materials Table
515
+ st.subheader("All Project Materials")
516
+ project_materials = st.session_state.project_data["materials"]["project"]
517
+ if project_materials:
518
+ data = []
519
+ for name, props in project_materials.items():
520
+ thermal_mass = calculate_thermal_mass(props["density"], props["specific_heat"], props["default_thickness"])
521
+ u_value = calculate_u_value(props["thermal_conductivity"], props["default_thickness"])
522
+ data.append({
523
+ "Name": name,
524
+ "Category": props["category"],
525
+ "Thermal Conductivity (W/m·K)": props["thermal_conductivity"],
526
+ "Density (kg/m³)": props["density"],
527
+ "Specific Heat (J/kg·K)": props["specific_heat"],
528
+ "Thickness (m)": props["default_thickness"],
529
+ "Embodied Carbon (kg CO₂e/m³)": props["embodied_carbon"],
530
+ "Cost (USD/m³)": props["cost"],
531
+ "Absorptivity": props["absorptivity"],
532
+ "Emissivity": props["emissivity"],
533
+ "Colour": props["colour"],
534
+ "Thermal Mass (J/m²·K)": thermal_mass,
535
+ "U-Value (W/m²·K)": u_value
536
+ })
537
+ df = pd.DataFrame(data)
538
+ st.dataframe(df, use_container_width=True, hide_index=True)
539
+ st.session_state.project_data["materials"]["table"] = df
540
+ else:
541
+ st.info("No project materials to display.")
542
 
543
  with col2:
544
  st.subheader("Material Editor/Creator")
 
581
  cols[0].write(name)
582
  cols[1].write(fenestration["type"])
583
  cols[2].write(f"{fenestration['u_value']:.2f}")
584
+ action_id = f"preview_lib_fen_{name}_{uuid.uuid4()}"
585
+ if cols[3].button("Preview", key=action_id):
586
+ if st.session_state.get("fenestration_action_id") != action_id:
587
+ st.session_state.fenestration_action_id = action_id
588
+ st.session_state.fenestration_editor = {
589
+ "name": name,
590
+ "type": fenestration["type"],
591
+ "u_value": fenestration["u_value"],
592
+ "shgc": fenestration["shgc"],
593
+ "visible_transmittance": fenestration["visible_transmittance"],
594
+ "thickness": fenestration["thickness"],
595
+ "embodied_carbon": fenestration["embodied_carbon"],
596
+ "cost": fenestration["cost"],
597
+ "edit_mode": False,
598
+ "original_name": name,
599
+ "is_library": True
600
+ }
601
+ st.session_state.active_tab = "Fenestrations"
602
+ st.success(f"Previewing fenestration '{name}'")
603
+ action_id = f"copy_lib_fen_{name}_{uuid.uuid4()}"
604
+ if cols[4].button("Copy", key=action_id):
605
+ if st.session_state.get("fenestration_action_id") != action_id:
606
+ st.session_state.fenestration_action_id = action_id
607
+ new_name = f"{name}_Project"
608
+ counter = 1
609
+ while new_name in st.session_state.project_data["fenestrations"]["project"] or new_name in library_fenestrations:
610
+ new_name = f"{name}_Project_{counter}"
611
+ counter += 1
612
+ st.session_state.project_data["fenestrations"]["project"][new_name] = fenestration.copy()
613
+ st.success(f"Fenestration '{new_name}' copied to project.")
614
+ logger.info(f"Copied library fenestration '{name}' as '{new_name}' to project")
615
+ st.rerun()
616
  else:
617
  st.info("No fenestrations found in the selected type.")
618
 
619
  # Project Fenestrations
620
+ project_fen_type = st.selectbox("Filter Project by Type", filter_options, key="project_fenestration_filter")
621
  st.subheader("Project Fenestrations")
622
  with st.container():
623
  project_fenestrations = st.session_state.project_data["fenestrations"]["project"]
624
+ filtered_project_fenestrations = []
625
+ if project_fen_type == "All":
626
+ filtered_project_fenestrations = list(project_fenestrations.values())
627
+ else:
628
+ filtered_project_fenestrations = [f for f in project_fenestrations.values() if f["type"] == project_fen_type]
629
+
630
+ if filtered_project_fenestrations:
631
  cols = st.columns([2, 1, 1, 1, 1])
632
  cols[0].write("**Name**")
633
  cols[1].write("**Type**")
 
635
  cols[3].write("**Edit**")
636
  cols[4].write("**Delete**")
637
 
638
+ for fenestration in filtered_project_fenestrations:
639
  cols = st.columns([2, 1, 1, 1, 1])
640
+ name = [k for k, v in project_fenestrations.items() if v == fenestration][0]
641
  cols[0].write(name)
642
  cols[1].write(fenestration["type"])
643
  cols[2].write(f"{fenestration['u_value']:.2f}")
644
+ action_id = f"edit_proj_fen_{name}_{uuid.uuid4()}"
645
+ if cols[3].button("Edit", key=action_id):
646
+ if st.session_state.get("fenestration_action_id") != action_id:
647
+ st.session_state.fenestration_action_id = action_id
648
+ st.session_state.fenestration_editor = {
649
+ "name": name,
650
+ "type": fenestration["type"],
651
+ "u_value": fenestration["u_value"],
652
+ "shgc": fenestration["shgc"],
653
+ "visible_transmittance": fenestration["visible_transmittance"],
654
+ "thickness": fenestration["thickness"],
655
+ "embodied_carbon": fenestration["embodied_carbon"],
656
+ "cost": fenestration["cost"],
657
+ "edit_mode": True,
658
+ "original_name": name,
659
+ "is_library": False
660
+ }
661
+ st.session_state.active_tab = "Fenestrations"
662
+ st.success(f"Editing fenestration '{name}'")
663
+ action_id = f"delete_proj_fen_{name}_{uuid.uuid4()}"
664
+ if cols[4].button("Delete", key=action_id):
665
+ if st.session_state.get("fenestration_action_id") != action_id:
666
+ st.session_state.fenestration_action_id = action_id
667
+ is_in_use = check_fenestration_in_use(name)
668
+ if is_in_use:
669
+ st.error(f"Cannot delete fenestration '{name}' because it is in use in components.")
670
+ else:
671
+ del st.session_state.project_data["fenestrations"]["project"][name]
672
+ st.success(f"Fenestration '{name}' deleted from project.")
673
+ logger.info(f"Deleted fenestration '{name}' from project")
674
+ st.rerun()
675
  else:
676
+ st.info("No project fenestrations in the selected type.")
677
+
678
+ # Comprehensive Fenestrations Table
679
+ st.subheader("All Project Fenestrations")
680
+ project_fenestrations = st.session_state.project_data["fenestrations"]["project"]
681
+ if project_fenestrations:
682
+ data = []
683
+ for name, props in project_fenestrations.items():
684
+ data.append({
685
+ "Name": name,
686
+ "Type": props["type"],
687
+ "U-Value (W/m²·K)": props["u_value"],
688
+ "SHGC": props["shgc"],
689
+ "Visible Transmittance": props["visible_transmittance"],
690
+ "Thickness (m)": props["thickness"],
691
+ "Embodied Carbon (kg CO₂e/m²)": props["embodied_carbon"],
692
+ "Cost (USD/m²)": props["cost"]
693
+ })
694
+ df = pd.DataFrame(data)
695
+ st.dataframe(df, use_container_width=True, hide_index=True)
696
+ st.session_state.project_data["fenestrations"]["table"] = df
697
+ else:
698
+ st.info("No project fenestrations to display.")
699
 
700
  with col2:
701
  st.subheader("Fenestration Editor/Creator")
 
706
  if "materials" not in st.session_state.project_data:
707
  st.session_state.project_data["materials"] = {
708
  "library": {},
709
+ "project": {},
710
+ "table": None
711
  }
712
 
713
  # Initialize library materials if empty
 
722
  "thermal_conductivity": 0.5,
723
  "density": 1000.0,
724
  "specific_heat": 1000.0,
 
725
  "default_thickness": 0.05,
726
  "embodied_carbon": 100.0,
727
  "cost": 100.0,
 
732
  "original_name": "",
733
  "is_library": False
734
  }
735
+ if "material_action_id" not in st.session_state:
736
+ st.session_state.material_action_id = None
737
 
738
  def initialize_fenestrations():
739
  """Initialize fenestrations in session state if not present."""
740
  if "fenestrations" not in st.session_state.project_data:
741
  st.session_state.project_data["fenestrations"] = {
742
  "library": {},
743
+ "project": {},
744
+ "table": None
745
  }
746
 
747
  # Initialize library fenestrations if empty
 
759
  "thickness": 0.024,
760
  "embodied_carbon": 900.0,
761
  "cost": 200.0,
 
 
 
762
  "edit_mode": False,
763
  "original_name": "",
764
  "is_library": False
765
  }
766
+ if "fenestration_action_id" not in st.session_state:
767
+ st.session_state.fenestration_action_id = None
768
 
769
  def display_material_editor():
770
  """Display the material editor form."""
 
827
  disabled=is_library
828
  )
829
 
830
+ # Default thickness
831
+ default_thickness = st.number_input(
832
+ "Thickness (m)",
 
833
  min_value=0.001,
834
  max_value=0.5,
835
+ value=float(editor_state["default_thickness"]),
836
  format="%.3f",
837
+ help="Thickness for this material in meters.",
838
  disabled=is_library
839
  )
840
 
841
+ # Absorptivity
842
+ absorptivity = st.number_input(
843
+ "Absorptivity",
844
+ min_value=0.0,
845
+ max_value=1.0,
846
+ value=float(editor_state["absorptivity"]),
847
+ format="%.2f",
848
+ help="Solar radiation absorbed (0-1).",
849
+ disabled=is_library
850
+ )
851
+
852
+ # Other properties
853
+ col1, col2 = st.columns(2)
854
+
855
+ with col1:
856
+ # Emissivity
857
+ emissivity = st.number_input(
858
+ "Emissivity",
859
+ min_value=0.0,
860
+ max_value=1.0,
861
+ value=float(editor_state["emissivity"]),
862
+ format="%.2f",
863
+ help="Ratio of radiation emitted (0-1).",
864
+ disabled=is_library
865
+ )
866
+
867
+ with col2:
868
+ # Colour
869
+ colour = st.selectbox(
870
+ "Colour Category",
871
+ COLOUR_CATEGORIES,
872
+ index=COLOUR_CATEGORIES.index(editor_state["colour"]) if editor_state["colour"] in COLOUR_CATEGORIES else 0,
873
+ help="Colour category affecting absorptivity.",
874
  disabled=is_library
875
  )
876
 
 
889
  help="Embodied carbon in kg CO₂e per cubic meter.",
890
  disabled=is_library
891
  )
 
 
 
 
 
 
 
 
 
 
 
892
 
893
  with col2:
894
  # Cost
 
901
  help="Material cost in USD per cubic meter.",
902
  disabled=is_library
903
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
904
 
905
  # Form submission buttons
906
  col1, col2 = st.columns(2)
 
913
 
914
  # Handle form submission
915
  if submit_button and not is_library:
916
+ action_id = str(uuid.uuid4())
917
+ if st.session_state.get("material_action_id") != action_id:
918
+ st.session_state.material_action_id = action_id
919
+ # Validate inputs
920
+ validation_errors = validate_material(
921
+ name, category, thermal_conductivity, density, specific_heat,
922
+ default_thickness, embodied_carbon, cost,
923
+ editor_state["edit_mode"], editor_state["original_name"],
924
+ absorptivity, emissivity, colour
925
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
926
 
927
+ if validation_errors:
928
+ for error in validation_errors:
929
+ st.error(error)
 
 
 
 
 
930
  else:
931
+ # Create material data
932
+ material_data = {
933
+ "category": category,
934
+ "thermal_conductivity": thermal_conductivity,
935
+ "density": density,
936
+ "specific_heat": specific_heat,
937
+ "default_thickness": default_thickness,
938
+ "embodied_carbon": embodied_carbon,
939
+ "cost": cost,
940
+ "absorptivity": absorptivity,
941
+ "emissivity": emissivity,
942
+ "colour": colour
943
+ }
944
+
945
+ # Handle edit mode
946
+ if editor_state["edit_mode"]:
947
+ original_name = editor_state["original_name"]
948
+ if original_name != name:
949
+ del st.session_state.project_data["materials"]["project"][original_name]
950
+ st.session_state.project_data["materials"]["project"][name] = material_data
951
+ st.success(f"Material '{name}' updated successfully.")
952
+ logger.info(f"Updated material '{name}' in project")
953
+ else:
954
+ st.session_state.project_data["materials"]["project"][name] = material_data
955
+ st.success(f"Material '{name}' added to your project.")
956
+ logger.info(f"Added new material '{name}' to project")
957
+
958
+ # Reset editor
959
+ reset_material_editor()
960
+ st.rerun()
961
 
962
  # Handle clear button
963
  if clear_button:
964
+ action_id = str(uuid.uuid4())
965
+ if st.session_state.get("material_action_id") != action_id:
966
+ st.session_state.material_action_id = action_id
967
+ reset_material_editor()
968
+ st.rerun()
969
 
970
  def display_fenestration_editor():
971
  """Display the fenestration editor form."""
 
1054
  help="Embodied carbon in kg CO₂e per square meter.",
1055
  disabled=is_library
1056
  )
 
 
 
 
 
 
 
 
 
 
 
1057
 
1058
  with col2:
1059
  # Cost
 
1066
  help="Fenestration cost in USD per square meter.",
1067
  disabled=is_library
1068
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1069
 
1070
  # Form submission buttons
1071
  col1, col2 = st.columns(2)
 
1078
 
1079
  # Handle form submission
1080
  if submit_button and not is_library:
1081
+ action_id = str(uuid.uuid4())
1082
+ if st.session_state.get("fenestration_action_id") != action_id:
1083
+ st.session_state.fenestration_action_id = action_id
1084
+ # Validate inputs
1085
+ validation_errors = validate_fenestration(
1086
+ name, fenestration_type, u_value, shgc, visible_transmittance, thickness,
1087
+ embodied_carbon, cost, editor_state["edit_mode"], editor_state["original_name"]
1088
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1089
 
1090
+ if validation_errors:
1091
+ for error in validation_errors:
1092
+ st.error(error)
 
 
 
 
 
1093
  else:
1094
+ # Create fenestration data
1095
+ fenestration_data = {
1096
+ "type": fenestration_type,
1097
+ "u_value": u_value,
1098
+ "shgc": shgc,
1099
+ "visible_transmittance": visible_transmittance,
1100
+ "thickness": thickness,
1101
+ "embodied_carbon": embodied_carbon,
1102
+ "cost": cost
1103
+ }
1104
+
1105
+ # Handle edit mode
1106
+ if editor_state["edit_mode"]:
1107
+ original_name = editor_state["original_name"]
1108
+ if original_name != name:
1109
+ del st.session_state.project_data["fenestrations"]["project"][original_name]
1110
+ st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1111
+ st.success(f"Fenestration '{name}' updated successfully.")
1112
+ logger.info(f"Updated fenestration '{name}' in project")
1113
+ else:
1114
+ st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1115
+ st.success(f"Fenestration '{name}' added to your project.")
1116
+ logger.info(f"Added new fenestration '{name}' to project")
1117
+
1118
+ # Reset editor
1119
+ reset_fenestration_editor()
1120
+ st.rerun()
1121
 
1122
  # Handle clear button
1123
  if clear_button:
1124
+ action_id = str(uuid.uuid4())
1125
+ if st.session_state.get("fenestration_action_id") != action_id:
1126
+ st.session_state.fenestration_action_id = action_id
1127
+ reset_fenestration_editor()
1128
+ st.rerun()
1129
 
1130
  def validate_material(
1131
  name: str, category: str, thermal_conductivity: float, density: float,
1132
+ specific_heat: float, default_thickness: float, embodied_carbon: float,
1133
+ cost: float, edit_mode: bool, original_name: str,
1134
  absorptivity: float, emissivity: float, colour: str
1135
  ) -> List[str]:
1136
  """
 
1163
  if specific_heat <= 0:
1164
  errors.append("Specific heat must be greater than zero.")
1165
 
 
 
 
 
 
 
1166
  # Validate default thickness
1167
+ if default_thickness <= 0:
1168
+ errors.append("Thickness must be greater than zero.")
1169
 
1170
  # Validate embodied carbon
1171
  if embodied_carbon < 0:
 
1203
  def validate_fenestration(
1204
  name: str, fenestration_type: str, u_value: float, shgc: float,
1205
  visible_transmittance: float, thickness: float, embodied_carbon: float,
1206
+ cost: float, edit_mode: bool, original_name: str
 
1207
  ) -> List[str]:
1208
  """
1209
  Validate fenestration inputs.
 
1247
  if cost < 0:
1248
  errors.append("Cost cannot be negative.")
1249
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1250
  return errors
1251
 
1252
  def reset_material_editor():
 
1257
  "thermal_conductivity": 0.5,
1258
  "density": 1000.0,
1259
  "specific_heat": 1000.0,
 
1260
  "default_thickness": 0.05,
1261
  "embodied_carbon": 100.0,
1262
  "cost": 100.0,
 
1267
  "original_name": "",
1268
  "is_library": False
1269
  }
1270
+ st.session_state.material_action_id = None
1271
 
1272
  def reset_fenestration_editor():
1273
  """Reset the fenestration editor to default values."""
 
1280
  "thickness": 0.024,
1281
  "embodied_carbon": 900.0,
1282
  "cost": 200.0,
 
 
 
1283
  "edit_mode": False,
1284
  "original_name": "",
1285
  "is_library": False
1286
  }
1287
+ st.session_state.fenestration_action_id = None
1288
 
1289
  def check_material_in_use(material_name: str) -> bool:
1290
  """
 
1322
  * **Thermal Conductivity (W/m·K)**: Rate of heat transfer through a material. Lower values indicate better insulation.
1323
  * **Density (kg/m³)**: Mass per unit volume.
1324
  * **Specific Heat (J/kg·K)**: Energy required to raise the temperature of 1 kg by 1 K. Higher values indicate better thermal mass.
1325
+ * **Thermal Mass (J/m²·K)**: Areal heat capacity (density × specific heat × thickness).
1326
+ * **U-Value (W/m²·K)**: Overall heat transfer coefficient, accounting for material and surface resistances. Lower values indicate better insulation.
1327
  * **SHGC**: Solar Heat Gain Coefficient (0-1). Fraction of incident solar radiation that enters through a fenestration.
1328
  * **Visible Transmittance**: Fraction of visible light that passes through a fenestration.
1329
  * **Embodied Carbon**: Carbon emissions associated with material production, measured in kg CO₂e per unit volume or area.