mabuseif commited on
Commit
2d9ca63
·
verified ·
1 Parent(s): 2883201

Upload materials_library.py

Browse files
Files changed (1) hide show
  1. app/materials_library.py +602 -839
app/materials_library.py CHANGED
@@ -37,17 +37,6 @@ FENESTRATION_TYPES = [
37
  "Custom"
38
  ]
39
 
40
- COLOUR_CATEGORIES = [
41
- "Light", # Absorptivity: 0.30–0.50 (e.g., white paints, aluminium coatings)
42
- "Medium", # Absorptivity: 0.60–0.70 (e.g., beige, tan, concrete)
43
- "Dark", # Absorptivity: 0.85–0.95 (e.g., black surfaces, dark roofs)
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": {
@@ -55,180 +44,150 @@ DEFAULT_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": {
66
  "category": "Structural Materials",
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": {
78
  "category": "Finishing Materials",
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": {
90
  "category": "Insulation",
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": {
102
  "category": "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)": {
114
  "category": "Structural Materials",
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": {
126
  "category": "Structural Materials",
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": {
138
  "category": "Structural Materials",
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": {
150
  "category": "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": {
162
  "category": "Finishing Materials",
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": {
174
  "category": "Finishing Materials",
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": {
186
  "category": "Sub-Structural Materials",
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": {
198
  "category": "Structural Materials",
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)": {
210
  "category": "Finishing Materials",
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": {
222
  "category": "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
 
@@ -240,8 +199,8 @@ DEFAULT_FENESTRATIONS = {
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",
@@ -326,521 +285,100 @@ DEFAULT_FENESTRATIONS = {
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 categorize_thermal_mass(thermal_mass: float) -> str:
340
- """Categorize thermal mass based on given ranges."""
341
- if thermal_mass < 30000:
342
- return "Low"
343
- elif 30000 <= thermal_mass <= 90000:
344
- return "Medium"
345
- else:
346
- return "High"
347
-
348
- def get_stable_button_key(prefix: str, name: str, action: str, unique_id: str = None) -> str:
349
- """Generate a stable button key based on prefix, name, action, and optional unique identifier."""
350
- import hashlib
351
- unique_id = unique_id or str(uuid.uuid4())[:8]
352
- key_string = f"{prefix}_{name}_{action}_{unique_id}"
353
- hash_key = hashlib.md5(key_string.encode()).hexdigest()[:8]
354
- return f"{prefix}_{action}_{name}_{hash_key}"
355
-
356
  def display_materials_page():
357
  """
358
  Display the material library page.
359
  This is the main function called by main.py when the Material Library page is selected.
360
  """
361
- # Initialize session state
362
- if "active_tab" not in st.session_state:
363
- st.session_state.active_tab = "Materials"
364
- if "material_action" not in st.session_state:
365
- st.session_state.material_action = {"action": None, "id": None}
366
- if "fenestration_action" not in st.session_state:
367
- st.session_state.fenestration_action = {"action": None, "id": None}
368
- if "project_data" not in st.session_state:
369
- st.session_state.project_data = {}
370
-
371
- # Apply custom CSS for button styling
372
- st.markdown("""
373
- <style>
374
- .stButton>button {
375
- width: 100%;
376
- font-size: 12px;
377
- padding: 5px;
378
- margin: 0;
379
- }
380
- </style>
381
- """, unsafe_allow_html=True)
382
-
383
  st.title("Material Library")
384
 
 
385
  with st.expander("Help & Information"):
386
  display_materials_help()
387
 
 
388
  tab1, tab2 = st.tabs(["Materials", "Fenestrations"])
389
 
 
390
  with tab1:
391
- st.session_state.active_tab = "Materials"
392
  display_materials_tab()
393
 
 
394
  with tab2:
395
- st.session_state.active_tab = "Fenestrations"
396
  display_fenestrations_tab()
397
 
 
398
  col1, col2 = st.columns(2)
399
 
400
  with col1:
401
  if st.button("Back to Climate Data", key="back_to_climate"):
402
  st.session_state.current_page = "Climate Data"
 
403
 
404
  with col2:
405
  if st.button("Continue to Construction", key="continue_to_construction"):
406
  st.session_state.current_page = "Construction"
407
-
408
- def handle_material_action(action: Dict[str, Any]):
409
- """Handle material-related actions."""
410
- if action["action"] == "preview":
411
- material = action["material"]
412
- name = action["name"]
413
- st.session_state.material_editor = {
414
- "name": name,
415
- "category": material["category"],
416
- "thermal_conductivity": material["thermal_conductivity"],
417
- "density": material["density"],
418
- "specific_heat": material["specific_heat"],
419
- "default_thickness": material["default_thickness"],
420
- "embodied_carbon": material["embodied_carbon"],
421
- "cost": material["cost"],
422
- "absorptivity": material["absorptivity"],
423
- "emissivity": material["emissivity"],
424
- "colour": material["colour"],
425
- "edit_mode": False,
426
- "original_name": name,
427
- "is_library": action["is_library"]
428
- }
429
- st.session_state.material_form_state = {
430
- "name": name,
431
- "category": material["category"],
432
- "thermal_conductivity": float(material["thermal_conductivity"]),
433
- "density": float(material["density"]),
434
- "specific_heat": float(material["specific_heat"]),
435
- "default_thickness": float(material["default_thickness"]),
436
- "embodied_carbon": float(material["embodied_carbon"]),
437
- "cost": float(material["cost"]),
438
- "absorptivity": float(material["absorptivity"]),
439
- "emissivity": float(material["emissivity"]),
440
- "colour": material["colour"]
441
- }
442
- logger.info(f"Previewed material '{name}'")
443
- elif action["action"] == "copy":
444
- name = action["name"]
445
- material = action["material"]
446
- new_name = f"{name}_Project"
447
- counter = 1
448
- library_materials = st.session_state.project_data["materials"]["library"]
449
- while new_name in st.session_state.project_data["materials"]["project"] or new_name in library_materials:
450
- new_name = f"{name}_Project_{counter}"
451
- counter += 1
452
- st.session_state.project_data["materials"]["project"][new_name] = material.copy()
453
- st.success(f"Material '{new_name}' copied to project.")
454
- logger.info(f"Copied library material '{name}' as '{new_name}' to project")
455
- elif action["action"] == "edit":
456
- material = action["material"]
457
- name = action["name"]
458
- st.session_state.material_editor = {
459
- "name": name,
460
- "category": material["category"],
461
- "thermal_conductivity": material["thermal_conductivity"],
462
- "density": material["density"],
463
- "specific_heat": material["specific_heat"],
464
- "default_thickness": material["default_thickness"],
465
- "embodied_carbon": material["embodied_carbon"],
466
- "cost": material["cost"],
467
- "absorptivity": material["absorptivity"],
468
- "emissivity": material["emissivity"],
469
- "colour": material["colour"],
470
- "edit_mode": True,
471
- "original_name": name,
472
- "is_library": False
473
- }
474
- st.session_state.material_form_state = {
475
- "name": name,
476
- "category": material["category"],
477
- "thermal_conductivity": float(material["thermal_conductivity"]),
478
- "density": float(material["density"]),
479
- "specific_heat": float(material["specific_heat"]),
480
- "default_thickness": float(material["default_thickness"]),
481
- "embodied_carbon": float(material["embodied_carbon"]),
482
- "cost": float(material["cost"]),
483
- "absorptivity": float(material["absorptivity"]),
484
- "emissivity": float(material["emissivity"]),
485
- "colour": material["colour"]
486
- }
487
- logger.info(f"Editing material '{name}'")
488
- elif action["action"] == "delete":
489
- name = action["name"]
490
- is_in_use = check_material_in_use(name)
491
- if is_in_use:
492
- st.error(f"Cannot delete material '{name}' because it is in use in constructions.")
493
- else:
494
- del st.session_state.project_data["materials"]["project"][name]
495
- st.success(f"Material '{name}' deleted from project.")
496
- logger.info(f"Deleted material '{name}' from project")
497
-
498
- def handle_fenestration_action(action: Dict[str, Any]):
499
- """Handle fenestration-related actions."""
500
- if action["action"] == "preview":
501
- fenestration = action["fenestration"]
502
- name = action["name"]
503
- st.session_state.fenestration_editor = {
504
- "name": name,
505
- "type": fenestration["type"],
506
- "u_value": fenestration["u_value"],
507
- "shgc": fenestration["shgc"],
508
- "visible_trans": fenestration["visible_transmittance"],
509
- "thickness": fenestration["thickness"],
510
- "embodied_carbon": fenestration["embodied_carbon"],
511
- "cost": fenestration["cost"],
512
- "edit_mode": False,
513
- "original_name": name,
514
- "is_library": action["is_library"]
515
- }
516
- st.session_state.fenestration_form_state = {
517
- "name": name,
518
- "type": fenestration["type"],
519
- "u_value": float(fenestration["u_value"]),
520
- "shgc": float(fenestration["shgc"]),
521
- "visible_trans": float(fenestration["visible_transmittance"]),
522
- "thickness": float(fenestration["thickness"]),
523
- "embodied_carbon": float(fenestration["embodied_carbon"]),
524
- "cost": float(fenestration["cost"])
525
- }
526
- logger.info(f"Previewed fenestration '{name}'")
527
- elif action["action"] == "copy":
528
- name = action["name"]
529
- fenestration = action["fenestration"]
530
- new_name = f"{name}_Project"
531
- counter = 1
532
- library_fenestrations = st.session_state.project_data["fenestrations"]["library"]
533
- while new_name in st.session_state.project_data["fenestrations"]["project"] or new_name in library_fenestrations:
534
- new_name = f"{name}_Project_{counter}"
535
- counter += 1
536
- st.session_state.project_data["fenestrations"]["project"][new_name] = fenestration.copy()
537
- st.success(f"Fenestration '{new_name}' copied to project.")
538
- logger.info(f"Copied library fenestration '{name}' as '{new_name}' to project")
539
- elif action["action"] == "edit":
540
- fenestration = action["fenestration"]
541
- name = action["name"]
542
- st.session_state.fenestration_editor = {
543
- "name": name,
544
- "type": fenestration["type"],
545
- "u_value": fenestration["u_value"],
546
- "shgc": fenestration["shgc"],
547
- "visible_trans": fenestration["visible_transmittance"],
548
- "thickness": fenestration["thickness"],
549
- "embodied_carbon": fenestration["embodied_carbon"],
550
- "cost": fenestration["cost"],
551
- "edit_mode": True,
552
- "original_name": name,
553
- "is_library": False
554
- }
555
- st.session_state.fenestration_form_state = {
556
- "name": name,
557
- "type": fenestration["type"],
558
- "u_value": float(fenestration["u_value"]),
559
- "shgc": float(fenestration["shgc"]),
560
- "visible_trans": float(fenestration["visible_transmittance"]),
561
- "thickness": float(fenestration["thickness"]),
562
- "embodied_carbon": float(fenestration["embodied_carbon"]),
563
- "cost": float(fenestration["cost"])
564
- }
565
- logger.info(f"Editing fenestration '{name}'")
566
- elif action["action"] == "delete":
567
- name = action["name"]
568
- is_in_use = check_fenestration_in_use(name)
569
- if is_in_use:
570
- st.error(f"Cannot delete fenestration '{name}' because it is in use in components.")
571
- else:
572
- del st.session_state.project_data["fenestrations"]["project"][name]
573
- st.success(f"Fenestration '{name}' deleted from project.")
574
- logger.info(f"Deleted fenestration '{name}' from project")
575
 
576
  def display_materials_tab():
577
  """Display the materials tab content."""
 
578
  initialize_materials()
579
 
580
- st.subheader("Materials")
581
- col1, col2 = st.columns([3, 2])
582
 
 
583
  with col1:
584
- filter_options = ["All"] + MATERIAL_CATEGORIES
585
- category = st.selectbox("Filter by Category", filter_options, key="material_filter")
586
-
587
  st.subheader("Library Materials")
588
- with st.container():
589
- library_materials = st.session_state.project_data["materials"]["library"]
590
- filtered_materials = []
591
- if category == "All":
592
- filtered_materials = list(library_materials.values())
593
- else:
594
- filtered_materials = [m for m in library_materials.values() if m["category"] == category]
595
-
596
- if filtered_materials:
597
- cols = st.columns([2, 1, 1, 1, 1])
598
- cols[0].write("**Name**")
599
- cols[1].write("**Thermal Mass**")
600
- cols[2].write("**U-Value (W/m²·K)**")
601
- cols[3].write("**Preview**")
602
- cols[4].write("**Copy**")
603
-
604
- for material in filtered_materials:
605
- cols = st.columns([2, 1, 1, 1, 1])
606
- name = [k for k, v in library_materials.items() if v == material][0]
607
- thermal_mass = calculate_thermal_mass(material["density"], material["specific_heat"], material["default_thickness"])
608
- thermal_mass_category = categorize_thermal_mass(thermal_mass)
609
- u_value = calculate_u_value(material["thermal_conductivity"], material["default_thickness"])
610
- cols[0].write(name)
611
- cols[1].write(thermal_mass_category)
612
- cols[2].write(f"{u_value:.3f}")
613
-
614
- preview_key = get_stable_button_key("lib_mat", name, "preview")
615
- copy_key = get_stable_button_key("lib_mat", name, "copy")
616
-
617
- cols[3].button("Preview", key=preview_key, on_click=handle_material_action, args=([{
618
- "action": "preview",
619
- "name": name,
620
- "material": material,
621
- "is_library": True
622
- }],))
623
-
624
- cols[4].button("Copy", key=copy_key, on_click=handle_material_action, args=([{
625
- "action": "copy",
626
- "name": name,
627
- "material": material
628
- }],))
629
-
630
- else:
631
- st.info("No materials found in the selected category.")
632
-
633
- project_category = st.selectbox("Filter Project by Category", filter_options, key="project_material_filter")
634
- st.subheader("Project Materials")
635
- with st.container():
636
- project_materials = st.session_state.project_data["materials"]["project"]
637
- filtered_project_materials = []
638
- if project_category == "All":
639
- filtered_project_materials = list(project_materials.values())
640
- else:
641
- filtered_project_materials = [m for m in project_materials.values() if m["category"] == project_category]
642
-
643
- if filtered_project_materials:
644
- cols = st.columns([2, 1, 1, 1, 1])
645
- cols[0].write("**Name**")
646
- cols[1].write("**Thermal Mass**")
647
- cols[2].write("**U-Value (W/m²·K)**")
648
- cols[3].write("**Edit**")
649
- cols[4].write("**Delete**")
650
-
651
- for material in filtered_project_materials:
652
- cols = st.columns([2, 1, 1, 1, 1])
653
- name = [k for k, v in project_materials.items() if v == material][0]
654
- thermal_mass = calculate_thermal_mass(material["density"], material["specific_heat"], material["default_thickness"])
655
- thermal_mass_category = categorize_thermal_mass(thermal_mass)
656
- u_value = calculate_u_value(material["thermal_conductivity"], material["default_thickness"])
657
- cols[0].write(name)
658
- cols[1].write(thermal_mass_category)
659
- cols[2].write(f"{u_value:.3f}")
660
-
661
- edit_key = get_stable_button_key("proj_mat", name, "edit")
662
- delete_key = get_stable_button_key("proj_mat", name, "delete")
663
-
664
- cols[3].button("Edit", key=edit_key, on_click=handle_material_action, args=([{
665
- "action": "edit",
666
- "name": name,
667
- "material": material
668
- }],))
669
-
670
- cols[4].button("Delete", key=delete_key, on_click=handle_material_action, args=([{
671
- "action": "delete",
672
- "name": name
673
- }],))
674
-
675
- else:
676
- st.info("No project materials in the selected category.")
677
-
678
- st.subheader("All Project Materials")
679
- project_materials = st.session_state.project_data["materials"]["project"]
680
- if project_materials:
681
- data = []
682
- for name, props in project_materials.items():
683
- thermal_mass = calculate_thermal_mass(props["density"], props["specific_heat"], props["default_thickness"])
684
- thermal_mass_category = categorize_thermal_mass(thermal_mass)
685
- u_value = calculate_u_value(props["thermal_conductivity"], props["default_thickness"])
686
- data.append({
687
- "Name": name,
688
- "Category": props["category"],
689
- "Thermal Conductivity (W/m·K)": props["thermal_conductivity"],
690
- "Density (kg/m³)": props["density"],
691
- "Specific Heat (J/kg·K)": props["specific_heat"],
692
- "Thickness (m)": props["default_thickness"],
693
- "Embodied Carbon (kg CO₂e/m³)": props["embodied_carbon"],
694
- "Cost (USD/m³)": props["cost"],
695
- "Absorptivity": props["absorptivity"],
696
- "Emissivity": props["emissivity"],
697
- "Colour": props["colour"],
698
- "Thermal Mass": thermal_mass_category,
699
- "U-Value (W/m²·K)": u_value
700
- })
701
- df = pd.DataFrame(data)
702
- st.dataframe(df, use_container_width=True, hide_index=True)
703
- st.session_state.project_data["materials"]["table"] = df
704
- else:
705
- st.info("No project materials to display.")
706
 
 
707
  with col2:
708
- st.subheader("Material Editor/Creator")
709
- display_material_editor()
 
 
 
 
 
710
 
711
  def display_fenestrations_tab():
712
  """Display the fenestrations tab content."""
 
713
  initialize_fenestrations()
714
 
715
- st.subheader("Fenestrations")
716
- col1, col2 = st.columns([3, 2])
717
 
 
718
  with col1:
719
- filter_options = ["All"] + FENESTRATION_TYPES
720
- fen_type = st.selectbox("Filter by Type", filter_options, key="fenestration_filter")
721
-
722
  st.subheader("Library Fenestrations")
723
- with st.container():
724
- library_fenestrations = st.session_state.project_data["fenestrations"]["library"]
725
- filtered_fenestrations = []
726
- if fen_type == "All":
727
- filtered_fenestrations = list(library_fenestrations.values())
728
- else:
729
- filtered_fenestrations = [f for f in library_fenestrations.values() if f["type"] == fen_type]
730
-
731
- if filtered_fenestrations:
732
- cols = st.columns([2, 1, 1, 1, 1])
733
- cols[0].write("**Name**")
734
- cols[1].write("**Type**")
735
- cols[2].write("**U-Value (W/m²·K)**")
736
- cols[3].write("**Preview**")
737
- cols[4].write("**Copy**")
738
-
739
- for fenestration in filtered_fenestrations:
740
- cols = st.columns([2, 1, 1, 1, 1])
741
- name = [k for k, v in library_fenestrations.items() if v == fenestration][0]
742
- cols[0].write(name)
743
- cols[1].write(fenestration["type"])
744
- cols[2].write(f"{fenestration['u_value']:.2f}")
745
-
746
- preview_key = get_stable_button_key("lib_fen", name, "preview")
747
- copy_key = get_stable_button_key("lib_fen", name, "copy")
748
-
749
- cols[3].button("Preview", key=preview_key, on_click=handle_fenestration_action, args=([{
750
- "action": "preview",
751
- "name": name,
752
- "fenestration": fenestration,
753
- "is_library": True
754
- }],))
755
-
756
- cols[4].button("Copy", key=copy_key, on_click=handle_fenestration_action, args=([{
757
- "action": "copy",
758
- "name": name,
759
- "fenestration": fenestration
760
- }],))
761
-
762
- else:
763
- st.info("No fenestrations found in the selected type.")
764
-
765
- project_fen_type = st.selectbox("Filter Project by Type", filter_options, key="project_fenestration_filter")
766
- st.subheader("Project Fenestrations")
767
- with st.container():
768
- project_fenestrations = st.session_state.project_data["fenestrations"]["project"]
769
- filtered_project_fenestrations = []
770
- if project_fen_type == "All":
771
- filtered_project_fenestrations = list(project_fenestrations.values())
772
- else:
773
- filtered_project_fenestrations = [f for f in project_fenestrations.values() if f["type"] == project_fen_type]
774
-
775
- if filtered_project_fenestrations:
776
- cols = st.columns([2, 1, 1, 1, 1])
777
- cols[0].write("**Name**")
778
- cols[1].write("**Type**")
779
- cols[2].write("**U-Value (W/m²·K)**")
780
- cols[3].write("**Edit**")
781
- cols[4].write("**Delete**")
782
-
783
- for fenestration in filtered_project_fenestrations:
784
- cols = st.columns([2, 1, 1, 1, 1])
785
- name = [k for k, v in project_fenestrations.items() if v == fenestration][0]
786
- cols[0].write(name)
787
- cols[1].write(fenestration["type"])
788
- cols[2].write(f"{fenestration['u_value']:.2f}")
789
-
790
- edit_key = get_stable_button_key("proj_fen", name, "edit")
791
- delete_key = get_stable_button_key("proj_fen", name, "delete")
792
-
793
- cols[3].button("Edit", key=edit_key, on_click=handle_fenestration_action, args=([{
794
- "action": "edit",
795
- "name": name,
796
- "fenestration": fenestration
797
- }],))
798
-
799
- cols[4].button("Delete", key=delete_key, on_click=handle_fenestration_action, args=([{
800
- "action": "delete",
801
- "name": name
802
- }],))
803
-
804
- else:
805
- st.info("No project fenestrations in the selected type.")
806
-
807
- st.subheader("All Project Fenestrations")
808
- project_fenestrations = st.session_state.project_data["fenestrations"]["project"]
809
- if project_fenestrations:
810
- data = []
811
- for name, props in sorted(project_fenestrations.items()):
812
- data.append({
813
- "Name": name,
814
- "Type": props["type"],
815
- "U-Value (W/m²·K)": props["u_value"],
816
- "SHGC": props["shgc"],
817
- "Visible Transmittance": props["visible_transmittance"],
818
- "Thickness (m)": props["thickness"],
819
- "Embodied Carbon (kg/m²)": props["embodied_carbon"],
820
- "Cost (USD/m²)": props["cost"]
821
- })
822
- df = pd.DataFrame(data)
823
- st.dataframe(df, use_container_width=True, hide_index=True)
824
- st.session_state.project_data["fenestrations"]["table"] = df
825
- else:
826
- st.info("No project fenestrations to display.")
827
 
 
828
  with col2:
829
- st.subheader("Fenestration Editor/Creator")
830
- display_fenestration_editor()
 
 
 
 
 
831
 
832
  def initialize_materials():
833
  """Initialize materials in session state if not present."""
834
  if "materials" not in st.session_state.project_data:
835
  st.session_state.project_data["materials"] = {
836
  "library": {},
837
- "project": {},
838
- "table": None
839
  }
840
 
 
841
  if not st.session_state.project_data["materials"]["library"]:
842
  st.session_state.project_data["materials"]["library"] = DEFAULT_MATERIALS.copy()
843
 
 
844
  if "material_editor" not in st.session_state:
845
  st.session_state.material_editor = {
846
  "name": "",
@@ -848,30 +386,12 @@ def initialize_materials():
848
  "thermal_conductivity": 0.5,
849
  "density": 1000.0,
850
  "specific_heat": 1000.0,
 
851
  "default_thickness": 0.05,
852
  "embodied_carbon": 100.0,
853
  "cost": 100.0,
854
- "absorptivity": 0.60,
855
- "emissivity": 0.90,
856
- "colour": "Medium",
857
  "edit_mode": False,
858
- "original_name": "",
859
- "is_library": False
860
- }
861
-
862
- if "material_form_state" not in st.session_state:
863
- st.session_state.material_form_state = {
864
- "name": "",
865
- "category": MATERIAL_CATEGORIES[0],
866
- "thermal_conductivity": 0.5,
867
- "density": 1000.0,
868
- "specific_heat": 1000.0,
869
- "default_thickness": 0.05,
870
- "embodied_carbon": 100.0,
871
- "cost": 100.0,
872
- "absorptivity": 0.60,
873
- "emissivity": 0.90,
874
- "colour": "Medium"
875
  }
876
 
877
  def initialize_fenestrations():
@@ -879,534 +399,781 @@ def initialize_fenestrations():
879
  if "fenestrations" not in st.session_state.project_data:
880
  st.session_state.project_data["fenestrations"] = {
881
  "library": {},
882
- "project": {},
883
- "table": None
884
  }
885
 
 
886
  if not st.session_state.project_data["fenestrations"]["library"]:
887
  st.session_state.project_data["fenestrations"]["library"] = DEFAULT_FENESTRATIONS.copy()
888
 
 
889
  if "fenestration_editor" not in st.session_state:
890
  st.session_state.fenestration_editor = {
891
  "name": "",
892
  "type": FENESTRATION_TYPES[0],
893
  "u_value": 2.8,
894
  "shgc": 0.7,
895
- "visible_trans": 0.8,
896
  "thickness": 0.024,
897
  "embodied_carbon": 900.0,
898
  "cost": 200.0,
899
  "edit_mode": False,
900
- "original_name": "",
901
- "is_library": False
902
  }
 
 
 
 
 
 
 
 
 
903
 
904
- if "fenestration_form_state" not in st.session_state:
905
- st.session_state.fenestration_form_state = {
906
- "name": "",
907
- "type": FENESTRATION_TYPES[0],
908
- "u_value": 2.8,
909
- "shgc": 0.7,
910
- "visible_trans": 0.8,
911
- "thickness": 0.024,
912
- "embodied_carbon": 900.0,
913
- "cost": 200.0
914
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
915
 
916
  def display_material_editor():
917
  """Display the material editor form."""
918
- with st.form("material_editor_form", clear_on_submit=True):
919
- editor_state = st.session_state.material_editor
920
- form_state = st.session_state.material_form_state
921
- is_library = editor_state.get("is_library", False)
922
-
923
  name = st.text_input(
924
  "Material Name",
925
- value=form_state.get("name", editor_state["name"]),
926
- help="Enter a unique name for the material.",
927
- disabled=is_library
928
  )
929
 
 
930
  col1, col2 = st.columns(2)
931
 
932
  with col1:
933
- category_index = MATERIAL_CATEGORIES.index(form_state.get("category", editor_state.get("category", MATERIAL_CATEGORIES[0])))
934
  category = st.selectbox(
935
  "Category",
936
  MATERIAL_CATEGORIES,
937
- index=category_index,
938
- help="Select the material category.",
939
- disabled=is_library
940
  )
941
 
 
942
  thermal_conductivity = st.number_input(
943
  "Thermal Conductivity (W/m·K)",
944
  min_value=0.001,
945
  max_value=1000.0,
946
- value=float(form_state.get("thermal_conductivity", editor_state.get("thermal_conductivity", 0.5))),
947
  format="%.3f",
948
- help="Thermal conductivity in W/m·K. Lower values indicate better insulation.",
949
- disabled=is_library
950
  )
951
 
 
952
  density = st.number_input(
953
  "Density (kg/m³)",
954
  min_value=1.0,
955
  max_value=20000.0,
956
- value=float(form_state.get("density", editor_state.get("density", 1000.0))),
957
  format="%.1f",
958
- help="Material density in kg/m³.",
959
- disabled=is_library
960
  )
961
 
962
  with col2:
 
963
  specific_heat = st.number_input(
964
  "Specific Heat (J/kg·K)",
965
  min_value=100.0,
966
  max_value=10000.0,
967
- value=float(form_state.get("specific_heat", editor_state.get("specific_heat", 1000.0))),
968
  format="%.1f",
969
- help="Specific heat capacity in J/kg·K. Higher values indicate better thermal mass.",
970
- disabled=is_library
971
  )
972
 
973
- default_thickness = st.number_input(
974
- "Thickness (m)",
 
 
975
  min_value=0.001,
976
  max_value=0.5,
977
- value=float(form_state.get("default_thickness", editor_state.get("default_thickness", 0.05))),
978
  format="%.3f",
979
- help="Thickness for this material in meters.",
980
- disabled=is_library
981
  )
982
 
983
- absorptivity = st.number_input(
984
- "Absorptivity",
985
- min_value=0.0,
986
- max_value=1.0,
987
- value=float(form_state.get("absorptivity", editor_state.get("absorptivity", 0.60))),
988
- format="%.2f",
989
- help="Solar radiation absorbed (0-1).",
990
- disabled=is_library
991
- )
992
-
993
- col1, col2 = st.columns(2)
994
-
995
- with col1:
996
- emissivity = st.number_input(
997
- "Emissivity",
998
- min_value=0.0,
999
- max_value=1.0,
1000
- value=float(form_state.get("emissivity", editor_state.get("emissivity", 0.90))),
1001
- format="%.2f",
1002
- help="Ratio of radiation emitted (0-1).",
1003
- disabled=is_library
1004
- )
1005
-
1006
- with col2:
1007
- colour_index = COLOUR_CATEGORIES.index(form_state.get("colour", editor_state.get("colour", "Medium"))) if form_state.get("colour", editor_state.get("colour", "Medium")) in COLOUR_CATEGORIES else 0
1008
- colour = st.selectbox(
1009
- "Colour Category",
1010
- COLOUR_CATEGORIES,
1011
- index=colour_index,
1012
- help="Colour category affecting absorptivity.",
1013
- disabled=is_library
1014
  )
1015
 
 
1016
  st.subheader("Additional Properties")
1017
  col1, col2 = st.columns(2)
1018
 
1019
  with col1:
 
1020
  embodied_carbon = st.number_input(
1021
  "Embodied Carbon (kg CO₂e/m³)",
1022
  min_value=0.0,
1023
  max_value=10000.0,
1024
- value=float(form_state.get("embodied_carbon", editor_state.get("embodied_carbon", 100.0))),
1025
  format="%.1f",
1026
- help="Embodied carbon in kg CO₂e per cubic meter.",
1027
- disabled=is_library
1028
  )
1029
 
1030
  with col2:
 
1031
  cost = st.number_input(
1032
  "Cost (USD/m³)",
1033
  min_value=0.0,
1034
  max_value=10000.0,
1035
- value=float(form_state.get("cost", editor_state.get("cost", 100.0))),
1036
  format="%.1f",
1037
- help="Material cost in USD per cubic meter.",
1038
- disabled=is_library
1039
  )
1040
 
 
1041
  col1, col2 = st.columns(2)
1042
 
1043
  with col1:
1044
- submit_button = st.form_submit_button("Save Material", disabled=is_library)
1045
 
1046
  with col2:
1047
  clear_button = st.form_submit_button("Clear Form")
 
 
 
 
 
 
 
 
 
1048
 
1049
- if submit_button and not is_library:
1050
- st.session_state.material_form_state = {
1051
- "name": name,
 
 
 
 
1052
  "category": category,
1053
  "thermal_conductivity": thermal_conductivity,
1054
  "density": density,
1055
  "specific_heat": specific_heat,
 
1056
  "default_thickness": default_thickness,
1057
  "embodied_carbon": embodied_carbon,
1058
- "cost": cost,
1059
- "absorptivity": absorptivity,
1060
- "emissivity": emissivity,
1061
- "colour": colour
1062
  }
1063
- success, message = validate_material(
1064
- name, category, thermal_conductivity, density, specific_heat,
1065
- default_thickness, embodied_carbon, cost,
1066
- editor_state["edit_mode"], editor_state["original_name"],
1067
- absorptivity, emissivity, colour
1068
- )
1069
- if not success:
1070
- st.error(message)
 
 
 
 
 
1071
  else:
1072
- material_data = {
1073
- "category": category,
1074
- "thermal_conductivity": thermal_conductivity,
1075
- "density": density,
1076
- "specific_heat": specific_heat,
1077
- "default_thickness": default_thickness,
1078
- "embodied_carbon": embodied_carbon,
1079
- "cost": cost,
1080
- "absorptivity": absorptivity,
1081
- "emissivity": emissivity,
1082
- "colour": colour
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1083
  }
1084
- if editor_state["edit_mode"]:
1085
- original_name = editor_state["original_name"]
1086
- if original_name != name:
1087
- del st.session_state.project_data["materials"]["project"][original_name]
1088
- st.session_state.project_data["materials"]["project"][name] = material_data
1089
- st.success(f"Material '{name}' updated successfully.")
1090
- logger.info(f"Updated material '{name}' in project")
1091
- else:
1092
- st.session_state.project_data["materials"]["project"][name] = material_data
1093
- st.success(f"Material '{name}' added to your project.")
1094
- logger.info(f"Added new material '{name}' to project")
1095
- reset_material_editor()
1096
 
1097
- if clear_button:
1098
- reset_material_editor()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1099
 
1100
  def display_fenestration_editor():
1101
  """Display the fenestration editor form."""
1102
- with st.form("fenestration_editor_form", clear_on_submit=True):
1103
- editor_state = st.session_state.fenestration_editor
1104
- form_state = st.session_state.fenestration_form_state
1105
- is_library = editor_state.get("is_library", False)
1106
-
1107
  name = st.text_input(
1108
  "Fenestration Name",
1109
- value=form_state.get("name", editor_state["name"]),
1110
- help="Enter a unique name for the fenestration.",
1111
- disabled=is_library
1112
  )
1113
 
 
1114
  col1, col2 = st.columns(2)
1115
 
1116
  with col1:
1117
- type_index = FENESTRATION_TYPES.index(form_state.get("type", editor_state["type"])) if form_state.get("type", editor_state["type"]) in FENESTRATION_TYPES else 0
1118
  fenestration_type = st.selectbox(
1119
  "Type",
1120
  FENESTRATION_TYPES,
1121
- index=type_index,
1122
- help="Select the fenestration type.",
1123
- disabled=is_library
1124
  )
1125
 
 
1126
  u_value = st.number_input(
1127
  "U-Value (W/m²·K)",
1128
  min_value=0.1,
1129
  max_value=10.0,
1130
- value=float(form_state.get("u_value", editor_state.get("u_value", 2.8))),
1131
  format="%.2f",
1132
- help="U-value in W/m²·K.",
1133
- disabled=is_library
1134
  )
1135
 
1136
- with col2:
1137
  shgc = st.number_input(
1138
  "Solar Heat Gain Coefficient (SHGC)",
1139
  min_value=0.0,
1140
  max_value=1.0,
1141
- value=float(form_state.get("shgc", editor_state.get("shgc", 0.7))),
1142
  format="%.2f",
1143
- help="Solar Heat Gain Coefficient (0-1).",
1144
- disabled=is_library
1145
  )
1146
-
1147
- visible_trans = st.number_input(
 
 
1148
  "Visible Transmittance",
1149
  min_value=0.0,
1150
  max_value=1.0,
1151
- value=float(form_state.get("visible_trans", editor_state.get("visible_trans", 0.8))),
1152
  format="%.2f",
1153
- help="Visible light transmission (0-1).",
1154
- disabled=is_library
1155
  )
1156
 
 
1157
  thickness = st.number_input(
1158
  "Thickness (m)",
1159
  min_value=0.001,
1160
  max_value=0.1,
1161
- value=float(form_state.get("thickness", editor_state.get("thickness", 0.024))),
1162
  format="%.3f",
1163
- help="Thickness in meters.",
1164
- disabled=is_library
1165
  )
1166
 
 
1167
  st.subheader("Additional Properties")
1168
-
1169
  col1, col2 = st.columns(2)
1170
 
1171
  with col1:
 
1172
  embodied_carbon = st.number_input(
1173
  "Embodied Carbon (kg CO₂e/m²)",
1174
  min_value=0.0,
1175
  max_value=5000.0,
1176
- value=float(form_state.get("embodied_carbon", editor_state.get("embodied_carbon", 900.0))),
1177
  format="%.1f",
1178
- help="Embodied carbon in kg CO₂e/m².",
1179
- disabled=is_library
1180
  )
1181
 
1182
  with col2:
 
1183
  cost = st.number_input(
1184
  "Cost (USD/m²)",
1185
  min_value=0.0,
1186
  max_value=5000.0,
1187
- value=float(form_state.get("cost", editor_state.get("cost", 200.0))),
1188
  format="%.1f",
1189
- help="Fenestration cost in USD/m².",
1190
- disabled=is_library
1191
  )
1192
 
 
1193
  col1, col2 = st.columns(2)
1194
 
1195
  with col1:
1196
- submit_button = st.form_submit_button("Save Fenestration", disabled=is_library)
1197
 
1198
  with col2:
1199
  clear_button = st.form_submit_button("Clear Form")
 
 
 
 
 
 
 
 
 
1200
 
1201
- if submit_button and not is_library:
1202
- st.session_state.fenestration_form_state = {
1203
- "name": name,
 
 
 
 
1204
  "type": fenestration_type,
1205
  "u_value": u_value,
1206
  "shgc": shgc,
1207
- "visible_trans": visible_trans,
1208
  "thickness": thickness,
1209
  "embodied_carbon": embodied_carbon,
1210
  "cost": cost
1211
  }
1212
- success, message = validate_fenestration(
1213
- name, fenestration_type, u_value, shgc, visible_trans, thickness,
1214
- embodied_carbon, cost, editor_state["edit_mode"], editor_state["original_name"]
1215
- )
1216
- if not success:
1217
- st.error(message)
 
 
 
 
 
 
 
1218
  else:
1219
- fenestration_data = {
1220
- "type": fenestration_type,
1221
- "u_value": u_value,
1222
- "shgc": shgc,
1223
- "visible_transmittance": visible_trans,
1224
- "thickness": thickness,
1225
- "embodied_carbon": embodied_carbon,
1226
- "cost": cost
1227
- }
1228
- if editor_state["edit_mode"]:
1229
- original_name = editor_state["original_name"]
1230
- if original_name != name:
1231
- del st.session_state.project_data["fenestrations"]["project"][original_name]
1232
- st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1233
- st.success(f"Fenestration '{name}' updated successfully.")
1234
- logger.info(f"Updated fenestration '{name}' in project")
1235
- else:
1236
- st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1237
- st.success(f"Fenestration '{name}' added to your project.")
1238
- logger.info(f"Added new fenestration '{name}' to project")
1239
- reset_fenestration_editor()
1240
-
1241
- if clear_button:
1242
  reset_fenestration_editor()
 
 
 
 
 
1243
 
1244
  def validate_material(
1245
  name: str, category: str, thermal_conductivity: float, density: float,
1246
- specific_heat: float, default_thickness: float, embodied_carbon: float,
1247
- cost: float, edit_mode: bool, original_name: str,
1248
- absorptivity: float, emissivity: float, colour: str
1249
- ) -> Tuple[bool, str]:
1250
  """
1251
  Validate material inputs.
1252
- Returns (success, message) tuple.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1253
  """
 
 
 
1254
  if not name or name.strip() == "":
1255
- return False, "Material name is required."
1256
 
1257
- if (not edit_mode or name != original_name) and name in st.session_state.project_data["materials"]["project"]:
1258
- return False, f"Material name '{name}' already exists in project materials."
 
 
1259
 
 
1260
  if category not in MATERIAL_CATEGORIES:
1261
- return False, "Invalid material category."
1262
 
 
1263
  if thermal_conductivity <= 0:
1264
- return False, "Thermal conductivity must be greater than zero."
1265
 
 
1266
  if density <= 0:
1267
- return False, "Density must be greater than zero."
1268
 
 
1269
  if specific_heat <= 0:
1270
- return False, "Specific heat must be greater than zero."
1271
 
1272
- if default_thickness <= 0:
1273
- return False, "Thickness must be greater than zero."
 
 
 
1274
 
 
 
 
 
 
1275
  if embodied_carbon < 0:
1276
- return False, "Embodied carbon cannot be negative."
1277
 
 
1278
  if cost < 0:
1279
- return False, "Cost cannot be negative."
1280
-
1281
- if absorptivity < 0 or absorptivity > 1:
1282
- return False, "Absorptivity must be between 0 and 1."
1283
-
1284
- if emissivity < 0 or emissivity > 1:
1285
- return False, "Emissivity must be between 0 and 1."
1286
 
1287
- if colour not in COLOUR_CATEGORIES:
1288
- return False, "Invalid colour category."
1289
-
1290
- colour_ranges = {
1291
- "Light": (0.30, 0.50),
1292
- "Medium": (0.60, 0.70),
1293
- "Dark": (0.85, 0.95),
1294
- "Reflective": (0.10, 0.20)
1295
- }
1296
- min_abs, max_abs = colour_ranges[colour]
1297
- if not (min_abs <= absorptivity <= max_abs):
1298
- return False, f"Absorptivity for {colour} colour must be between {min_abs} and {max_abs}."
1299
-
1300
- return True, ""
1301
 
1302
  def validate_fenestration(
1303
  name: str, fenestration_type: str, u_value: float, shgc: float,
1304
- visible_trans: float, thickness: float, embodied_carbon: float,
1305
  cost: float, edit_mode: bool, original_name: str
1306
- ) -> Tuple[bool, str]:
1307
  """
1308
  Validate fenestration inputs.
1309
- Returns (success, message) tuple.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1310
  """
 
 
 
1311
  if not name or name.strip() == "":
1312
- return False, "Fenestration name is required."
1313
 
1314
- if (not edit_mode or name != original_name) and name in st.session_state.project_data["fenestrations"]["project"]:
1315
- return False, f"Fenestration name '{name}' already exists in project."
 
 
1316
 
 
1317
  if fenestration_type not in FENESTRATION_TYPES:
1318
- return False, "Invalid fenestration type."
1319
 
 
1320
  if u_value <= 0:
1321
- return False, "U-value must be greater than zero."
1322
 
 
1323
  if shgc < 0 or shgc > 1:
1324
- return False, "SHGC must be between 0 and 1."
1325
 
1326
- if visible_trans < 0 or visible_trans > 1:
1327
- return False, "Visible transmittance must be between 0 and 1."
 
1328
 
 
1329
  if thickness <= 0:
1330
- return False, "Thickness must be greater than zero."
1331
-
 
1332
  if embodied_carbon < 0:
1333
- return False, "Embodied carbon cannot be negative."
1334
 
 
1335
  if cost < 0:
1336
- return False, "Cost cannot be negative."
1337
 
1338
- return True, ""
1339
 
1340
  def reset_material_editor():
1341
- """Reset the material editor and form state."""
1342
- st.session_state.material_form_state = {
1343
- "name": "",
1344
- "category": MATERIAL_CATEGORIES[0],
1345
- "thermal_conductivity": 0.5,
1346
- "density": 1000.0,
1347
- "specific_heat": 1000.0,
1348
- "default_thickness": 0.05,
1349
- "embodied_carbon": 100.0,
1350
- "cost": 100.0,
1351
- "absorptivity": 0.60,
1352
- "emissivity": 0.90,
1353
- "colour": "Medium"
1354
- }
1355
  st.session_state.material_editor = {
1356
  "name": "",
1357
  "category": MATERIAL_CATEGORIES[0],
1358
  "thermal_conductivity": 0.5,
1359
  "density": 1000.0,
1360
  "specific_heat": 1000.0,
 
1361
  "default_thickness": 0.05,
1362
  "embodied_carbon": 100.0,
1363
  "cost": 100.0,
1364
- "absorptivity": 0.60,
1365
- "emissivity": 0.90,
1366
- "colour": "Medium",
1367
  "edit_mode": False,
1368
- "original_name": "",
1369
- "is_library": False
1370
  }
1371
- st.session_state.material_action = {"action": None, "id": None}
1372
 
1373
  def reset_fenestration_editor():
1374
- """Reset the fenestration editor and form state."""
1375
- st.session_state.fenestration_form_state = {
1376
- "name": "",
1377
- "type": FENESTRATION_TYPES[0],
1378
- "u_value": 2.8,
1379
- "shgc": 0.7,
1380
- "visible_trans": 0.8,
1381
- "thickness": 0.024,
1382
- "embodied_carbon": 900.0,
1383
- "cost": 200.0
1384
- }
1385
  st.session_state.fenestration_editor = {
1386
  "name": "",
1387
  "type": FENESTRATION_TYPES[0],
1388
  "u_value": 2.8,
1389
  "shgc": 0.7,
1390
- "visible_trans": 0.8,
1391
  "thickness": 0.024,
1392
  "embodied_carbon": 900.0,
1393
  "cost": 200.0,
1394
  "edit_mode": False,
1395
- "original_name": "",
1396
- "is_library": False
1397
  }
1398
- st.session_state.fenestration_action = {"action": None, "id": None}
1399
 
1400
  def check_material_in_use(material_name: str) -> bool:
1401
  """
1402
  Check if a material is in use in any constructions.
 
 
 
 
 
 
1403
  """
 
 
1404
  return False
1405
 
1406
- def check_fenestration_in_use(name: str) -> bool:
1407
  """
1408
  Check if a fenestration is in use in any components.
 
 
 
 
 
 
1409
  """
 
 
1410
  return False
1411
 
1412
  def display_materials_help():
@@ -1418,35 +1185,31 @@ def display_materials_help():
1418
 
1419
  **Materials Tab:**
1420
 
1421
- - **Library Materials**: Pre-defined materials with standard thermal properties. Use 'Preview' to view details or 'Copy' to add to your project.
1422
- - **Project Materials**: Materials you've added to your project. Use 'Edit' to modify or 'Delete' to remove.
1423
- - **Material Editor/Creator**: Create new materials or edit existing project materials. Library materials can only be previewed.
1424
 
1425
  **Fenestrations Tab:**
1426
 
1427
- - **Library Fenestrations**: Pre-defined windows, doors, and skylights. Use 'Preview' to view details or 'Copy' to add to your project.
1428
- - **Project Fenestrations**: Fenestrations you've added to your project. Use 'Edit' to modify or 'Delete' to remove.
1429
- - **Fenestration Editor/Creator**: Create new fenestrations or edit existing project fenestrations. Library fenestrations can only be previewed.
1430
 
1431
  **Key Properties:**
1432
 
1433
  * **Thermal Conductivity (W/m·K)**: Rate of heat transfer through a material. Lower values indicate better insulation.
1434
  * **Density (kg/m³)**: Mass per unit volume.
1435
  * **Specific Heat (J/kg·K)**: Energy required to raise the temperature of 1 kg by 1 K. Higher values indicate better thermal mass.
1436
- * **Thermal Mass (J/m²·K)**: Areal heat capacity (density × specific heat × thickness).
1437
- * **U-Value (W/m²·K)**: Overall heat transfer coefficient, accounting for material{eq surfaces resistances. Lower values indicate better insulation.
1438
  * **SHGC**: Solar Heat Gain Coefficient (0-1). Fraction of incident solar radiation that enters through a fenestration.
1439
  * **Visible Transmittance**: Fraction of visible light that passes through a fenestration.
1440
  * **Embodied Carbon**: Carbon emissions associated with material production, measured in kg CO₂e per unit volume or area.
1441
  * **Cost**: Material cost in USD per unit volume or area.
1442
- * **Absorptivity**: Fraction of solar radiation absorbed (0-1). Depends on colour category.
1443
- * **Emissivity**: Ratio of radiation emitted by the material (0-1).
1444
- * **Colour**: Category affecting absorptivity (Light, Medium, Dark, Reflective).
1445
 
1446
  **Workflow:**
1447
 
1448
  1. Browse the library materials and fenestrations
1449
- 2. Copy items to your project or create custom ones
1450
  3. Edit properties as needed for your specific project
1451
  4. Continue to the Construction page to create assemblies using these materials
1452
- """)
 
37
  "Custom"
38
  ]
39
 
 
 
 
 
 
 
 
 
 
 
 
40
  # Default library materials
41
  DEFAULT_MATERIALS = {
42
  "Brick": {
 
44
  "thermal_conductivity": 0.72,
45
  "density": 1920.0,
46
  "specific_heat": 840.0,
47
+ "thickness_range": [0.05, 0.3],
48
  "default_thickness": 0.1,
49
+ "embodied_carbon": 240.0, # kg CO2e/m³
50
+ "cost": 180.0 # USD/m³
 
 
 
51
  },
52
  "Concrete": {
53
  "category": "Structural Materials",
54
  "thermal_conductivity": 1.4,
55
  "density": 2300.0,
56
  "specific_heat": 880.0,
57
+ "thickness_range": [0.05, 0.5],
58
  "default_thickness": 0.15,
59
  "embodied_carbon": 320.0,
60
+ "cost": 120.0
 
 
 
61
  },
62
  "Gypsum Board": {
63
  "category": "Finishing Materials",
64
  "thermal_conductivity": 0.25,
65
  "density": 900.0,
66
  "specific_heat": 1000.0,
67
+ "thickness_range": [0.01, 0.05],
68
  "default_thickness": 0.0125,
69
  "embodied_carbon": 120.0,
70
+ "cost": 90.0
 
 
 
71
  },
72
  "Mineral Wool": {
73
  "category": "Insulation",
74
  "thermal_conductivity": 0.04,
75
  "density": 30.0,
76
  "specific_heat": 840.0,
77
+ "thickness_range": [0.025, 0.3],
78
  "default_thickness": 0.1,
79
  "embodied_carbon": 45.0,
80
+ "cost": 70.0
 
 
 
81
  },
82
  "EPS Insulation": {
83
  "category": "Insulation",
84
  "thermal_conductivity": 0.035,
85
  "density": 25.0,
86
  "specific_heat": 1400.0,
87
+ "thickness_range": [0.025, 0.3],
88
  "default_thickness": 0.1,
89
  "embodied_carbon": 88.0,
90
+ "cost": 65.0
 
 
 
91
  },
92
  "Wood (Pine)": {
93
  "category": "Structural Materials",
94
  "thermal_conductivity": 0.14,
95
  "density": 550.0,
96
  "specific_heat": 1600.0,
97
+ "thickness_range": [0.01, 0.3],
98
  "default_thickness": 0.05,
99
  "embodied_carbon": 110.0,
100
+ "cost": 250.0
 
 
 
101
  },
102
  "Steel": {
103
  "category": "Structural Materials",
104
  "thermal_conductivity": 50.0,
105
  "density": 7800.0,
106
  "specific_heat": 450.0,
107
+ "thickness_range": [0.001, 0.05],
108
  "default_thickness": 0.005,
109
  "embodied_carbon": 1750.0,
110
+ "cost": 800.0
 
 
 
111
  },
112
  "Aluminum": {
113
  "category": "Structural Materials",
114
  "thermal_conductivity": 230.0,
115
  "density": 2700.0,
116
  "specific_heat": 880.0,
117
+ "thickness_range": [0.001, 0.05],
118
  "default_thickness": 0.003,
119
  "embodied_carbon": 8500.0,
120
+ "cost": 1200.0
 
 
 
121
  },
122
  "Glass Fiber Insulation": {
123
  "category": "Insulation",
124
  "thermal_conductivity": 0.035,
125
  "density": 12.0,
126
  "specific_heat": 840.0,
127
+ "thickness_range": [0.025, 0.3],
128
  "default_thickness": 0.1,
129
  "embodied_carbon": 28.0,
130
+ "cost": 45.0
 
 
 
131
  },
132
  "Ceramic Tile": {
133
  "category": "Finishing Materials",
134
  "thermal_conductivity": 1.3,
135
  "density": 2300.0,
136
  "specific_heat": 840.0,
137
+ "thickness_range": [0.005, 0.025],
138
  "default_thickness": 0.01,
139
  "embodied_carbon": 650.0,
140
+ "cost": 280.0
 
 
 
141
  },
142
  "Carpet": {
143
  "category": "Finishing Materials",
144
  "thermal_conductivity": 0.06,
145
  "density": 200.0,
146
  "specific_heat": 1300.0,
147
+ "thickness_range": [0.005, 0.02],
148
  "default_thickness": 0.01,
149
  "embodied_carbon": 40.0,
150
+ "cost": 150.0
 
 
 
151
  },
152
  "Plywood": {
153
  "category": "Sub-Structural Materials",
154
  "thermal_conductivity": 0.13,
155
  "density": 560.0,
156
  "specific_heat": 1400.0,
157
+ "thickness_range": [0.006, 0.025],
158
  "default_thickness": 0.012,
159
  "embodied_carbon": 350.0,
160
+ "cost": 180.0
 
 
 
161
  },
162
  "Concrete Block": {
163
  "category": "Structural Materials",
164
  "thermal_conductivity": 0.51,
165
  "density": 1400.0,
166
  "specific_heat": 1000.0,
167
+ "thickness_range": [0.1, 0.3],
168
  "default_thickness": 0.2,
169
  "embodied_carbon": 260.0,
170
+ "cost": 140.0
 
 
 
171
  },
172
  "Stone (Granite)": {
173
  "category": "Finishing Materials",
174
  "thermal_conductivity": 2.8,
175
  "density": 2600.0,
176
  "specific_heat": 790.0,
177
+ "thickness_range": [0.01, 0.1],
178
  "default_thickness": 0.03,
179
  "embodied_carbon": 170.0,
180
+ "cost": 450.0
 
 
 
181
  },
182
  "Polyurethane Insulation": {
183
  "category": "Insulation",
184
  "thermal_conductivity": 0.025,
185
  "density": 30.0,
186
  "specific_heat": 1400.0,
187
+ "thickness_range": [0.025, 0.2],
188
  "default_thickness": 0.075,
189
  "embodied_carbon": 102.0,
190
+ "cost": 95.0
 
 
 
191
  }
192
  }
193
 
 
199
  "shgc": 0.86,
200
  "visible_transmittance": 0.9,
201
  "thickness": 0.006,
202
+ "embodied_carbon": 800.0, # kg CO2e/m²
203
+ "cost": 120.0 # USD/m²
204
  },
205
  "Double Glazing (Air)": {
206
  "type": "Window",
 
285
  }
286
  }
287
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  def display_materials_page():
289
  """
290
  Display the material library page.
291
  This is the main function called by main.py when the Material Library page is selected.
292
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  st.title("Material Library")
294
 
295
+ # Display help information in an expandable section
296
  with st.expander("Help & Information"):
297
  display_materials_help()
298
 
299
+ # Create tabs for materials and fenestrations
300
  tab1, tab2 = st.tabs(["Materials", "Fenestrations"])
301
 
302
+ # Materials tab
303
  with tab1:
 
304
  display_materials_tab()
305
 
306
+ # Fenestrations tab
307
  with tab2:
 
308
  display_fenestrations_tab()
309
 
310
+ # Navigation buttons
311
  col1, col2 = st.columns(2)
312
 
313
  with col1:
314
  if st.button("Back to Climate Data", key="back_to_climate"):
315
  st.session_state.current_page = "Climate Data"
316
+ st.rerun()
317
 
318
  with col2:
319
  if st.button("Continue to Construction", key="continue_to_construction"):
320
  st.session_state.current_page = "Construction"
321
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
 
323
  def display_materials_tab():
324
  """Display the materials tab content."""
325
+ # Initialize materials in session state if not present
326
  initialize_materials()
327
 
328
+ # Create columns for library and project materials
329
+ col1, col2 = st.columns(2)
330
 
331
+ # Library Materials
332
  with col1:
 
 
 
333
  st.subheader("Library Materials")
334
+ display_library_materials()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
+ # Project Materials
337
  with col2:
338
+ st.subheader("Project Materials")
339
+ display_project_materials()
340
+
341
+ # Material Editor
342
+ st.markdown("---")
343
+ st.subheader("Material Editor")
344
+ display_material_editor()
345
 
346
  def display_fenestrations_tab():
347
  """Display the fenestrations tab content."""
348
+ # Initialize fenestrations in session state if not present
349
  initialize_fenestrations()
350
 
351
+ # Create columns for library and project fenestrations
352
+ col1, col2 = st.columns(2)
353
 
354
+ # Library Fenestrations
355
  with col1:
 
 
 
356
  st.subheader("Library Fenestrations")
357
+ display_library_fenestrations()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
 
359
+ # Project Fenestrations
360
  with col2:
361
+ st.subheader("Project Fenestrations")
362
+ display_project_fenestrations()
363
+
364
+ # Fenestration Editor
365
+ st.markdown("---")
366
+ st.subheader("Fenestration Editor")
367
+ display_fenestration_editor()
368
 
369
  def initialize_materials():
370
  """Initialize materials in session state if not present."""
371
  if "materials" not in st.session_state.project_data:
372
  st.session_state.project_data["materials"] = {
373
  "library": {},
374
+ "project": {}
 
375
  }
376
 
377
+ # Initialize library materials if empty
378
  if not st.session_state.project_data["materials"]["library"]:
379
  st.session_state.project_data["materials"]["library"] = DEFAULT_MATERIALS.copy()
380
 
381
+ # Initialize material editor state
382
  if "material_editor" not in st.session_state:
383
  st.session_state.material_editor = {
384
  "name": "",
 
386
  "thermal_conductivity": 0.5,
387
  "density": 1000.0,
388
  "specific_heat": 1000.0,
389
+ "thickness_range": [0.01, 0.2],
390
  "default_thickness": 0.05,
391
  "embodied_carbon": 100.0,
392
  "cost": 100.0,
 
 
 
393
  "edit_mode": False,
394
+ "original_name": ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
  }
396
 
397
  def initialize_fenestrations():
 
399
  if "fenestrations" not in st.session_state.project_data:
400
  st.session_state.project_data["fenestrations"] = {
401
  "library": {},
402
+ "project": {}
 
403
  }
404
 
405
+ # Initialize library fenestrations if empty
406
  if not st.session_state.project_data["fenestrations"]["library"]:
407
  st.session_state.project_data["fenestrations"]["library"] = DEFAULT_FENESTRATIONS.copy()
408
 
409
+ # Initialize fenestration editor state
410
  if "fenestration_editor" not in st.session_state:
411
  st.session_state.fenestration_editor = {
412
  "name": "",
413
  "type": FENESTRATION_TYPES[0],
414
  "u_value": 2.8,
415
  "shgc": 0.7,
416
+ "visible_transmittance": 0.8,
417
  "thickness": 0.024,
418
  "embodied_carbon": 900.0,
419
  "cost": 200.0,
420
  "edit_mode": False,
421
+ "original_name": ""
 
422
  }
423
+
424
+ def display_library_materials():
425
+ """Display the library materials section."""
426
+ # Filter options
427
+ category_filter = st.selectbox(
428
+ "Filter by Category",
429
+ ["All"] + MATERIAL_CATEGORIES,
430
+ key="library_material_category_filter"
431
+ )
432
 
433
+ # Get library materials
434
+ library_materials = st.session_state.project_data["materials"]["library"]
435
+
436
+ # Apply filter
437
+ if category_filter != "All":
438
+ filtered_materials = {
439
+ name: props for name, props in library_materials.items()
440
+ if props["category"] == category_filter
 
 
441
  }
442
+ else:
443
+ filtered_materials = library_materials
444
+
445
+ # Display materials in a table
446
+ if filtered_materials:
447
+ # Create a DataFrame for display
448
+ data = []
449
+ for name, props in filtered_materials.items():
450
+ data.append({
451
+ "Name": name,
452
+ "Category": props["category"],
453
+ "Thermal Conductivity (W/m·K)": props["thermal_conductivity"],
454
+ "Density (kg/m³)": props["density"],
455
+ "Specific Heat (J/kg·K)": props["specific_heat"]
456
+ })
457
+
458
+ df = pd.DataFrame(data)
459
+ st.dataframe(df, use_container_width=True, hide_index=True)
460
+
461
+ # Add to project button
462
+ selected_material = st.selectbox(
463
+ "Select Material to Add to Project",
464
+ list(filtered_materials.keys()),
465
+ key="library_material_selector"
466
+ )
467
+
468
+ if st.button("Add to Project", key="add_library_material_to_project"):
469
+ # Check if material already exists in project
470
+ if selected_material in st.session_state.project_data["materials"]["project"]:
471
+ st.warning(f"Material '{selected_material}' already exists in your project.")
472
+ else:
473
+ # Add to project materials
474
+ st.session_state.project_data["materials"]["project"][selected_material] = \
475
+ st.session_state.project_data["materials"]["library"][selected_material].copy()
476
+ st.success(f"Material '{selected_material}' added to your project.")
477
+ logger.info(f"Added library material '{selected_material}' to project")
478
+ else:
479
+ st.info("No materials found in the selected category.")
480
+
481
+ def display_project_materials():
482
+ """Display the project materials section."""
483
+ # Get project materials
484
+ project_materials = st.session_state.project_data["materials"]["project"]
485
+
486
+ if project_materials:
487
+ # Create a DataFrame for display
488
+ data = []
489
+ for name, props in project_materials.items():
490
+ data.append({
491
+ "Name": name,
492
+ "Category": props["category"],
493
+ "Thermal Conductivity (W/m·K)": props["thermal_conductivity"],
494
+ "Density (kg/m³)": props["density"],
495
+ "Specific Heat (J/kg·K)": props["specific_heat"]
496
+ })
497
+
498
+ df = pd.DataFrame(data)
499
+ st.dataframe(df, use_container_width=True, hide_index=True)
500
+
501
+ # Edit and delete options
502
+ col1, col2 = st.columns(2)
503
+
504
+ with col1:
505
+ selected_material = st.selectbox(
506
+ "Select Material to Edit",
507
+ list(project_materials.keys()),
508
+ key="project_material_edit_selector"
509
+ )
510
+
511
+ if st.button("Edit Material", key="edit_project_material"):
512
+ # Load material data into editor
513
+ material_data = project_materials[selected_material]
514
+ st.session_state.material_editor = {
515
+ "name": selected_material,
516
+ "category": material_data["category"],
517
+ "thermal_conductivity": material_data["thermal_conductivity"],
518
+ "density": material_data["density"],
519
+ "specific_heat": material_data["specific_heat"],
520
+ "thickness_range": material_data["thickness_range"],
521
+ "default_thickness": material_data["default_thickness"],
522
+ "embodied_carbon": material_data["embodied_carbon"],
523
+ "cost": material_data["cost"],
524
+ "edit_mode": True,
525
+ "original_name": selected_material
526
+ }
527
+ st.success(f"Material '{selected_material}' loaded for editing.")
528
+
529
+ with col2:
530
+ selected_material_delete = st.selectbox(
531
+ "Select Material to Delete",
532
+ list(project_materials.keys()),
533
+ key="project_material_delete_selector"
534
+ )
535
+
536
+ if st.button("Delete Material", key="delete_project_material"):
537
+ # Check if material is in use
538
+ is_in_use = check_material_in_use(selected_material_delete)
539
+
540
+ if is_in_use:
541
+ st.error(f"Cannot delete material '{selected_material_delete}' because it is in use in constructions.")
542
+ else:
543
+ # Delete material
544
+ del st.session_state.project_data["materials"]["project"][selected_material_delete]
545
+ st.success(f"Material '{selected_material_delete}' deleted from your project.")
546
+ logger.info(f"Deleted material '{selected_material_delete}' from project")
547
+ else:
548
+ st.info("No materials in your project. Add materials from the library or create custom materials.")
549
 
550
  def display_material_editor():
551
  """Display the material editor form."""
552
+ with st.form("material_editor_form"):
553
+ # Material name
 
 
 
554
  name = st.text_input(
555
  "Material Name",
556
+ value=st.session_state.material_editor["name"],
557
+ help="Enter a unique name for the material."
 
558
  )
559
 
560
+ # Create two columns for layout
561
  col1, col2 = st.columns(2)
562
 
563
  with col1:
564
+ # Category
565
  category = st.selectbox(
566
  "Category",
567
  MATERIAL_CATEGORIES,
568
+ index=MATERIAL_CATEGORIES.index(st.session_state.material_editor["category"]) if st.session_state.material_editor["category"] in MATERIAL_CATEGORIES else 0,
569
+ help="Select the material category."
 
570
  )
571
 
572
+ # Thermal conductivity
573
  thermal_conductivity = st.number_input(
574
  "Thermal Conductivity (W/m·K)",
575
  min_value=0.001,
576
  max_value=1000.0,
577
+ value=float(st.session_state.material_editor["thermal_conductivity"]),
578
  format="%.3f",
579
+ help="Thermal conductivity in W/m·K. Lower values indicate better insulation."
 
580
  )
581
 
582
+ # Density
583
  density = st.number_input(
584
  "Density (kg/m³)",
585
  min_value=1.0,
586
  max_value=20000.0,
587
+ value=float(st.session_state.material_editor["density"]),
588
  format="%.1f",
589
+ help="Material density in kg/m³."
 
590
  )
591
 
592
  with col2:
593
+ # Specific heat
594
  specific_heat = st.number_input(
595
  "Specific Heat (J/kg·K)",
596
  min_value=100.0,
597
  max_value=10000.0,
598
+ value=float(st.session_state.material_editor["specific_heat"]),
599
  format="%.1f",
600
+ help="Specific heat capacity in J/kg·K. Higher values indicate better thermal mass."
 
601
  )
602
 
603
+ # Thickness range
604
+ min_thickness, max_thickness = st.session_state.material_editor["thickness_range"]
605
+ thickness_range = st.slider(
606
+ "Thickness Range (m)",
607
  min_value=0.001,
608
  max_value=0.5,
609
+ value=(float(min_thickness), float(max_thickness)),
610
  format="%.3f",
611
+ help="Minimum and maximum thickness range for this material in meters."
 
612
  )
613
 
614
+ # Default thickness
615
+ default_thickness = st.number_input(
616
+ "Default Thickness (m)",
617
+ min_value=float(thickness_range[0]),
618
+ max_value=float(thickness_range[1]),
619
+ value=min(max(float(st.session_state.material_editor["default_thickness"]), float(thickness_range[0])), float(thickness_range[1])),
620
+ format="%.3f",
621
+ help="Default thickness for this material in meters."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
622
  )
623
 
624
+ # Additional properties for embodied carbon and cost
625
  st.subheader("Additional Properties")
626
  col1, col2 = st.columns(2)
627
 
628
  with col1:
629
+ # Embodied carbon
630
  embodied_carbon = st.number_input(
631
  "Embodied Carbon (kg CO₂e/m³)",
632
  min_value=0.0,
633
  max_value=10000.0,
634
+ value=float(st.session_state.material_editor["embodied_carbon"]),
635
  format="%.1f",
636
+ help="Embodied carbon in kg CO₂e per cubic meter."
 
637
  )
638
 
639
  with col2:
640
+ # Cost
641
  cost = st.number_input(
642
  "Cost (USD/m³)",
643
  min_value=0.0,
644
  max_value=10000.0,
645
+ value=float(st.session_state.material_editor["cost"]),
646
  format="%.1f",
647
+ help="Material cost in USD per cubic meter."
 
648
  )
649
 
650
+ # Form submission buttons
651
  col1, col2 = st.columns(2)
652
 
653
  with col1:
654
+ submit_button = st.form_submit_button("Save Material")
655
 
656
  with col2:
657
  clear_button = st.form_submit_button("Clear Form")
658
+
659
+ # Handle form submission
660
+ if submit_button:
661
+ # Validate inputs
662
+ validation_errors = validate_material(
663
+ name, category, thermal_conductivity, density, specific_heat,
664
+ thickness_range, default_thickness, embodied_carbon, cost,
665
+ st.session_state.material_editor["edit_mode"], st.session_state.material_editor["original_name"]
666
+ )
667
 
668
+ if validation_errors:
669
+ # Display validation errors
670
+ for error in validation_errors:
671
+ st.error(error)
672
+ else:
673
+ # Create material data
674
+ material_data = {
675
  "category": category,
676
  "thermal_conductivity": thermal_conductivity,
677
  "density": density,
678
  "specific_heat": specific_heat,
679
+ "thickness_range": list(thickness_range),
680
  "default_thickness": default_thickness,
681
  "embodied_carbon": embodied_carbon,
682
+ "cost": cost
 
 
 
683
  }
684
+
685
+ # Handle edit mode
686
+ if st.session_state.material_editor["edit_mode"]:
687
+ original_name = st.session_state.material_editor["original_name"]
688
+
689
+ # If name changed, delete old entry and create new one
690
+ if original_name != name:
691
+ del st.session_state.project_data["materials"]["project"][original_name]
692
+
693
+ # Update material
694
+ st.session_state.project_data["materials"]["project"][name] = material_data
695
+ st.success(f"Material '{name}' updated successfully.")
696
+ logger.info(f"Updated material '{name}' in project")
697
  else:
698
+ # Add new material
699
+ st.session_state.project_data["materials"]["project"][name] = material_data
700
+ st.success(f"Material '{name}' added to your project.")
701
+ logger.info(f"Added new material '{name}' to project")
702
+
703
+ # Reset editor
704
+ reset_material_editor()
705
+
706
+ # Handle clear button
707
+ if clear_button:
708
+ reset_material_editor()
709
+ st.rerun()
710
+
711
+ def display_library_fenestrations():
712
+ """Display the library fenestrations section."""
713
+ # Filter options
714
+ type_filter = st.selectbox(
715
+ "Filter by Type",
716
+ ["All"] + FENESTRATION_TYPES,
717
+ key="library_fenestration_type_filter"
718
+ )
719
+
720
+ # Get library fenestrations
721
+ library_fenestrations = st.session_state.project_data["fenestrations"]["library"]
722
+
723
+ # Apply filter
724
+ if type_filter != "All":
725
+ filtered_fenestrations = {
726
+ name: props for name, props in library_fenestrations.items()
727
+ if props["type"] == type_filter
728
+ }
729
+ else:
730
+ filtered_fenestrations = library_fenestrations
731
+
732
+ # Display fenestrations in a table
733
+ if filtered_fenestrations:
734
+ # Create a DataFrame for display
735
+ data = []
736
+ for name, props in filtered_fenestrations.items():
737
+ data.append({
738
+ "Name": name,
739
+ "Type": props["type"],
740
+ "U-Value (W/m²·K)": props["u_value"],
741
+ "SHGC": props["shgc"],
742
+ "Visible Transmittance": props["visible_transmittance"]
743
+ })
744
+
745
+ df = pd.DataFrame(data)
746
+ st.dataframe(df, use_container_width=True, hide_index=True)
747
+
748
+ # Add to project button
749
+ selected_fenestration = st.selectbox(
750
+ "Select Fenestration to Add to Project",
751
+ list(filtered_fenestrations.keys()),
752
+ key="library_fenestration_selector"
753
+ )
754
+
755
+ if st.button("Add to Project", key="add_library_fenestration_to_project"):
756
+ # Check if fenestration already exists in project
757
+ if selected_fenestration in st.session_state.project_data["fenestrations"]["project"]:
758
+ st.warning(f"Fenestration '{selected_fenestration}' already exists in your project.")
759
+ else:
760
+ # Add to project fenestrations
761
+ st.session_state.project_data["fenestrations"]["project"][selected_fenestration] = \
762
+ st.session_state.project_data["fenestrations"]["library"][selected_fenestration].copy()
763
+ st.success(f"Fenestration '{selected_fenestration}' added to your project.")
764
+ logger.info(f"Added library fenestration '{selected_fenestration}' to project")
765
+ else:
766
+ st.info("No fenestrations found in the selected type.")
767
+
768
+ def display_project_fenestrations():
769
+ """Display the project fenestrations section."""
770
+ # Get project fenestrations
771
+ project_fenestrations = st.session_state.project_data["fenestrations"]["project"]
772
+
773
+ if project_fenestrations:
774
+ # Create a DataFrame for display
775
+ data = []
776
+ for name, props in project_fenestrations.items():
777
+ data.append({
778
+ "Name": name,
779
+ "Type": props["type"],
780
+ "U-Value (W/m²·K)": props["u_value"],
781
+ "SHGC": props["shgc"],
782
+ "Visible Transmittance": props["visible_transmittance"]
783
+ })
784
+
785
+ df = pd.DataFrame(data)
786
+ st.dataframe(df, use_container_width=True, hide_index=True)
787
+
788
+ # Edit and delete options
789
+ col1, col2 = st.columns(2)
790
+
791
+ with col1:
792
+ selected_fenestration = st.selectbox(
793
+ "Select Fenestration to Edit",
794
+ list(project_fenestrations.keys()),
795
+ key="project_fenestration_edit_selector"
796
+ )
797
+
798
+ if st.button("Edit Fenestration", key="edit_project_fenestration"):
799
+ # Load fenestration data into editor
800
+ fenestration_data = project_fenestrations[selected_fenestration]
801
+ st.session_state.fenestration_editor = {
802
+ "name": selected_fenestration,
803
+ "type": fenestration_data["type"],
804
+ "u_value": fenestration_data["u_value"],
805
+ "shgc": fenestration_data["shgc"],
806
+ "visible_transmittance": fenestration_data["visible_transmittance"],
807
+ "thickness": fenestration_data["thickness"],
808
+ "embodied_carbon": fenestration_data["embodied_carbon"],
809
+ "cost": fenestration_data["cost"],
810
+ "edit_mode": True,
811
+ "original_name": selected_fenestration
812
  }
813
+ st.success(f"Fenestration '{selected_fenestration}' loaded for editing.")
 
 
 
 
 
 
 
 
 
 
 
814
 
815
+ with col2:
816
+ selected_fenestration_delete = st.selectbox(
817
+ "Select Fenestration to Delete",
818
+ list(project_fenestrations.keys()),
819
+ key="project_fenestration_delete_selector"
820
+ )
821
+
822
+ if st.button("Delete Fenestration", key="delete_project_fenestration"):
823
+ # Check if fenestration is in use
824
+ is_in_use = check_fenestration_in_use(selected_fenestration_delete)
825
+
826
+ if is_in_use:
827
+ st.error(f"Cannot delete fenestration '{selected_fenestration_delete}' because it is in use in components.")
828
+ else:
829
+ # Delete fenestration
830
+ del st.session_state.project_data["fenestrations"]["project"][selected_fenestration_delete]
831
+ st.success(f"Fenestration '{selected_fenestration_delete}' deleted from your project.")
832
+ logger.info(f"Deleted fenestration '{selected_fenestration_delete}' from project")
833
+ else:
834
+ st.info("No fenestrations in your project. Add fenestrations from the library or create custom fenestrations.")
835
 
836
  def display_fenestration_editor():
837
  """Display the fenestration editor form."""
838
+ with st.form("fenestration_editor_form"):
839
+ # Fenestration name
 
 
 
840
  name = st.text_input(
841
  "Fenestration Name",
842
+ value=st.session_state.fenestration_editor["name"],
843
+ help="Enter a unique name for the fenestration."
 
844
  )
845
 
846
+ # Create two columns for layout
847
  col1, col2 = st.columns(2)
848
 
849
  with col1:
850
+ # Type
851
  fenestration_type = st.selectbox(
852
  "Type",
853
  FENESTRATION_TYPES,
854
+ index=FENESTRATION_TYPES.index(st.session_state.fenestration_editor["type"]) if st.session_state.fenestration_editor["type"] in FENESTRATION_TYPES else 0,
855
+ help="Select the fenestration type."
 
856
  )
857
 
858
+ # U-value
859
  u_value = st.number_input(
860
  "U-Value (W/m²·K)",
861
  min_value=0.1,
862
  max_value=10.0,
863
+ value=float(st.session_state.fenestration_editor["u_value"]),
864
  format="%.2f",
865
+ help="U-value in W/m²·K. Lower values indicate better insulation."
 
866
  )
867
 
868
+ # SHGC
869
  shgc = st.number_input(
870
  "Solar Heat Gain Coefficient (SHGC)",
871
  min_value=0.0,
872
  max_value=1.0,
873
+ value=float(st.session_state.fenestration_editor["shgc"]),
874
  format="%.2f",
875
+ help="Solar Heat Gain Coefficient (0-1). Lower values indicate less solar heat transmission."
 
876
  )
877
+
878
+ with col2:
879
+ # Visible transmittance
880
+ visible_transmittance = st.number_input(
881
  "Visible Transmittance",
882
  min_value=0.0,
883
  max_value=1.0,
884
+ value=float(st.session_state.fenestration_editor["visible_transmittance"]),
885
  format="%.2f",
886
+ help="Visible Transmittance (0-1). Higher values indicate more visible light transmission."
 
887
  )
888
 
889
+ # Thickness
890
  thickness = st.number_input(
891
  "Thickness (m)",
892
  min_value=0.001,
893
  max_value=0.1,
894
+ value=float(st.session_state.fenestration_editor["thickness"]),
895
  format="%.3f",
896
+ help="Thickness in meters."
 
897
  )
898
 
899
+ # Additional properties for embodied carbon and cost
900
  st.subheader("Additional Properties")
 
901
  col1, col2 = st.columns(2)
902
 
903
  with col1:
904
+ # Embodied carbon
905
  embodied_carbon = st.number_input(
906
  "Embodied Carbon (kg CO₂e/m²)",
907
  min_value=0.0,
908
  max_value=5000.0,
909
+ value=float(st.session_state.fenestration_editor["embodied_carbon"]),
910
  format="%.1f",
911
+ help="Embodied carbon in kg CO₂e per square meter."
 
912
  )
913
 
914
  with col2:
915
+ # Cost
916
  cost = st.number_input(
917
  "Cost (USD/m²)",
918
  min_value=0.0,
919
  max_value=5000.0,
920
+ value=float(st.session_state.fenestration_editor["cost"]),
921
  format="%.1f",
922
+ help="Fenestration cost in USD per square meter."
 
923
  )
924
 
925
+ # Form submission buttons
926
  col1, col2 = st.columns(2)
927
 
928
  with col1:
929
+ submit_button = st.form_submit_button("Save Fenestration")
930
 
931
  with col2:
932
  clear_button = st.form_submit_button("Clear Form")
933
+
934
+ # Handle form submission
935
+ if submit_button:
936
+ # Validate inputs
937
+ validation_errors = validate_fenestration(
938
+ name, fenestration_type, u_value, shgc, visible_transmittance, thickness,
939
+ embodied_carbon, cost, st.session_state.fenestration_editor["edit_mode"],
940
+ st.session_state.fenestration_editor["original_name"]
941
+ )
942
 
943
+ if validation_errors:
944
+ # Display validation errors
945
+ for error in validation_errors:
946
+ st.error(error)
947
+ else:
948
+ # Create fenestration data
949
+ fenestration_data = {
950
  "type": fenestration_type,
951
  "u_value": u_value,
952
  "shgc": shgc,
953
+ "visible_transmittance": visible_transmittance,
954
  "thickness": thickness,
955
  "embodied_carbon": embodied_carbon,
956
  "cost": cost
957
  }
958
+
959
+ # Handle edit mode
960
+ if st.session_state.fenestration_editor["edit_mode"]:
961
+ original_name = st.session_state.fenestration_editor["original_name"]
962
+
963
+ # If name changed, delete old entry and create new one
964
+ if original_name != name:
965
+ del st.session_state.project_data["fenestrations"]["project"][original_name]
966
+
967
+ # Update fenestration
968
+ st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
969
+ st.success(f"Fenestration '{name}' updated successfully.")
970
+ logger.info(f"Updated fenestration '{name}' in project")
971
  else:
972
+ # Add new fenestration
973
+ st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
974
+ st.success(f"Fenestration '{name}' added to your project.")
975
+ logger.info(f"Added new fenestration '{name}' to project")
976
+
977
+ # Reset editor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
978
  reset_fenestration_editor()
979
+
980
+ # Handle clear button
981
+ if clear_button:
982
+ reset_fenestration_editor()
983
+ st.rerun()
984
 
985
  def validate_material(
986
  name: str, category: str, thermal_conductivity: float, density: float,
987
+ specific_heat: float, thickness_range: Tuple[float, float], default_thickness: float,
988
+ embodied_carbon: float, cost: float, edit_mode: bool, original_name: str
989
+ ) -> List[str]:
 
990
  """
991
  Validate material inputs.
992
+
993
+ Args:
994
+ name: Material name
995
+ category: Material category
996
+ thermal_conductivity: Thermal conductivity in W/m·K
997
+ density: Density in kg/m³
998
+ specific_heat: Specific heat in J/kg·K
999
+ thickness_range: Tuple of (min_thickness, max_thickness) in meters
1000
+ default_thickness: Default thickness in meters
1001
+ embodied_carbon: Embodied carbon in kg CO₂e/m³
1002
+ cost: Cost in USD/m³
1003
+ edit_mode: Whether in edit mode
1004
+ original_name: Original name if in edit mode
1005
+
1006
+ Returns:
1007
+ List of validation error messages, empty if all inputs are valid
1008
  """
1009
+ errors = []
1010
+
1011
+ # Validate name
1012
  if not name or name.strip() == "":
1013
+ errors.append("Material name is required.")
1014
 
1015
+ # Check for name uniqueness if not in edit mode or if name changed
1016
+ if not edit_mode or (edit_mode and name != original_name):
1017
+ if name in st.session_state.project_data["materials"]["project"]:
1018
+ errors.append(f"Material name '{name}' already exists in your project.")
1019
 
1020
+ # Validate category
1021
  if category not in MATERIAL_CATEGORIES:
1022
+ errors.append("Please select a valid material category.")
1023
 
1024
+ # Validate thermal conductivity
1025
  if thermal_conductivity <= 0:
1026
+ errors.append("Thermal conductivity must be greater than zero.")
1027
 
1028
+ # Validate density
1029
  if density <= 0:
1030
+ errors.append("Density must be greater than zero.")
1031
 
1032
+ # Validate specific heat
1033
  if specific_heat <= 0:
1034
+ errors.append("Specific heat must be greater than zero.")
1035
 
1036
+ # Validate thickness range
1037
+ if thickness_range[0] <= 0:
1038
+ errors.append("Minimum thickness must be greater than zero.")
1039
+ if thickness_range[0] >= thickness_range[1]:
1040
+ errors.append("Maximum thickness must be greater than minimum thickness.")
1041
 
1042
+ # Validate default thickness
1043
+ if default_thickness < thickness_range[0] or default_thickness > thickness_range[1]:
1044
+ errors.append("Default thickness must be within the thickness range.")
1045
+
1046
+ # Validate embodied carbon
1047
  if embodied_carbon < 0:
1048
+ errors.append("Embodied carbon cannot be negative.")
1049
 
1050
+ # Validate cost
1051
  if cost < 0:
1052
+ errors.append("Cost cannot be negative.")
 
 
 
 
 
 
1053
 
1054
+ return errors
 
 
 
 
 
 
 
 
 
 
 
 
 
1055
 
1056
  def validate_fenestration(
1057
  name: str, fenestration_type: str, u_value: float, shgc: float,
1058
+ visible_transmittance: float, thickness: float, embodied_carbon: float,
1059
  cost: float, edit_mode: bool, original_name: str
1060
+ ) -> List[str]:
1061
  """
1062
  Validate fenestration inputs.
1063
+
1064
+ Args:
1065
+ name: Fenestration name
1066
+ fenestration_type: Fenestration type
1067
+ u_value: U-value in W/m²·K
1068
+ shgc: Solar Heat Gain Coefficient (0-1)
1069
+ visible_transmittance: Visible Transmittance (0-1)
1070
+ thickness: Thickness in meters
1071
+ embodied_carbon: Embodied carbon in kg CO₂e/m²
1072
+ cost: Cost in USD/m²
1073
+ edit_mode: Whether in edit mode
1074
+ original_name: Original name if in edit mode
1075
+
1076
+ Returns:
1077
+ List of validation error messages, empty if all inputs are valid
1078
  """
1079
+ errors = []
1080
+
1081
+ # Validate name
1082
  if not name or name.strip() == "":
1083
+ errors.append("Fenestration name is required.")
1084
 
1085
+ # Check for name uniqueness if not in edit mode or if name changed
1086
+ if not edit_mode or (edit_mode and name != original_name):
1087
+ if name in st.session_state.project_data["fenestrations"]["project"]:
1088
+ errors.append(f"Fenestration name '{name}' already exists in your project.")
1089
 
1090
+ # Validate type
1091
  if fenestration_type not in FENESTRATION_TYPES:
1092
+ errors.append("Please select a valid fenestration type.")
1093
 
1094
+ # Validate u_value
1095
  if u_value <= 0:
1096
+ errors.append("U-value must be greater than zero.")
1097
 
1098
+ # Validate shgc
1099
  if shgc < 0 or shgc > 1:
1100
+ errors.append("SHGC must be between 0 and 1.")
1101
 
1102
+ # Validate visible transmittance
1103
+ if visible_transmittance < 0 or visible_transmittance > 1:
1104
+ errors.append("Visible transmittance must be between 0 and 1.")
1105
 
1106
+ # Validate thickness
1107
  if thickness <= 0:
1108
+ errors.append("Thickness must be greater than zero.")
1109
+
1110
+ # Validate embodied carbon
1111
  if embodied_carbon < 0:
1112
+ errors.append("Embodied carbon cannot be negative.")
1113
 
1114
+ # Validate cost
1115
  if cost < 0:
1116
+ errors.append("Cost cannot be negative.")
1117
 
1118
+ return errors
1119
 
1120
  def reset_material_editor():
1121
+ """Reset the material editor to default values."""
 
 
 
 
 
 
 
 
 
 
 
 
 
1122
  st.session_state.material_editor = {
1123
  "name": "",
1124
  "category": MATERIAL_CATEGORIES[0],
1125
  "thermal_conductivity": 0.5,
1126
  "density": 1000.0,
1127
  "specific_heat": 1000.0,
1128
+ "thickness_range": [0.01, 0.2],
1129
  "default_thickness": 0.05,
1130
  "embodied_carbon": 100.0,
1131
  "cost": 100.0,
 
 
 
1132
  "edit_mode": False,
1133
+ "original_name": ""
 
1134
  }
 
1135
 
1136
  def reset_fenestration_editor():
1137
+ """Reset the fenestration editor to default values."""
 
 
 
 
 
 
 
 
 
 
1138
  st.session_state.fenestration_editor = {
1139
  "name": "",
1140
  "type": FENESTRATION_TYPES[0],
1141
  "u_value": 2.8,
1142
  "shgc": 0.7,
1143
+ "visible_transmittance": 0.8,
1144
  "thickness": 0.024,
1145
  "embodied_carbon": 900.0,
1146
  "cost": 200.0,
1147
  "edit_mode": False,
1148
+ "original_name": ""
 
1149
  }
 
1150
 
1151
  def check_material_in_use(material_name: str) -> bool:
1152
  """
1153
  Check if a material is in use in any constructions.
1154
+
1155
+ Args:
1156
+ material_name: Name of the material to check
1157
+
1158
+ Returns:
1159
+ True if the material is in use, False otherwise
1160
  """
1161
+ # This is a placeholder function that will be implemented when constructions are added
1162
+ # For now, we'll assume materials are not in use
1163
  return False
1164
 
1165
+ def check_fenestration_in_use(fenestration_name: str) -> bool:
1166
  """
1167
  Check if a fenestration is in use in any components.
1168
+
1169
+ Args:
1170
+ fenestration_name: Name of the fenestration to check
1171
+
1172
+ Returns:
1173
+ True if the fenestration is in use, False otherwise
1174
  """
1175
+ # This is a placeholder function that will be implemented when components are added
1176
+ # For now, we'll assume fenestrations are not in use
1177
  return False
1178
 
1179
  def display_materials_help():
 
1185
 
1186
  **Materials Tab:**
1187
 
1188
+ * **Library Materials**: Pre-defined materials with standard thermal properties.
1189
+ * **Project Materials**: Materials you've added to your project from the library or created custom.
1190
+ * **Material Editor**: Create new materials or edit existing ones in your project.
1191
 
1192
  **Fenestrations Tab:**
1193
 
1194
+ * **Library Fenestrations**: Pre-defined windows, doors, and skylights with standard thermal properties.
1195
+ * **Project Fenestrations**: Fenestrations you've added to your project from the library or created custom.
1196
+ * **Fenestration Editor**: Create new fenestrations or edit existing ones in your project.
1197
 
1198
  **Key Properties:**
1199
 
1200
  * **Thermal Conductivity (W/m·K)**: Rate of heat transfer through a material. Lower values indicate better insulation.
1201
  * **Density (kg/m³)**: Mass per unit volume.
1202
  * **Specific Heat (J/kg·K)**: Energy required to raise the temperature of 1 kg by 1 K. Higher values indicate better thermal mass.
1203
+ * **U-Value (W/m²·K)**: Overall heat transfer coefficient for fenestrations. Lower values indicate better insulation.
 
1204
  * **SHGC**: Solar Heat Gain Coefficient (0-1). Fraction of incident solar radiation that enters through a fenestration.
1205
  * **Visible Transmittance**: Fraction of visible light that passes through a fenestration.
1206
  * **Embodied Carbon**: Carbon emissions associated with material production, measured in kg CO₂e per unit volume or area.
1207
  * **Cost**: Material cost in USD per unit volume or area.
 
 
 
1208
 
1209
  **Workflow:**
1210
 
1211
  1. Browse the library materials and fenestrations
1212
+ 2. Add items to your project or create custom ones
1213
  3. Edit properties as needed for your specific project
1214
  4. Continue to the Construction page to create assemblies using these materials
1215
+ """)