mabuseif commited on
Commit
0bec026
·
verified ·
1 Parent(s): fbf3827

Upload materials_library.py

Browse files
Files changed (1) hide show
  1. app/materials_library.py +645 -902
app/materials_library.py CHANGED
@@ -18,7 +18,7 @@ import uuid
18
  from typing import Dict, List, Any, Optional, Tuple, Union
19
 
20
  # Configure logging
21
- logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
22
  logger = logging.getLogger(__name__)
23
 
24
  # Define constants
@@ -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,127 +285,29 @@ 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 initialize_session_state():
357
- """Initialize all required session state variables."""
358
- # Ensure project_data exists
359
- if "project_data" not in st.session_state:
360
- st.session_state.project_data = {}
361
-
362
- # Ensure materials structure is fully initialized
363
- if "materials" not in st.session_state.project_data:
364
- st.session_state.project_data["materials"] = {
365
- "library": DEFAULT_MATERIALS.copy(),
366
- "project": {},
367
- "table": None
368
- }
369
- else:
370
- # Ensure all required keys exist in materials
371
- if "library" not in st.session_state.project_data["materials"]:
372
- st.session_state.project_data["materials"]["library"] = DEFAULT_MATERIALS.copy()
373
- if "project" not in st.session_state.project_data["materials"]:
374
- st.session_state.project_data["materials"]["project"] = {}
375
- if "table" not in st.session_state.project_data["materials"]:
376
- st.session_state.project_data["materials"]["table"] = None
377
-
378
- # Ensure fenestrations structure is fully initialized
379
- if "fenestrations" not in st.session_state.project_data:
380
- st.session_state.project_data["fenestrations"] = {
381
- "library": DEFAULT_FENESTRATIONS.copy(),
382
- "project": {},
383
- "table": None
384
- }
385
- else:
386
- # Ensure all required keys exist in fenestrations
387
- if "library" not in st.session_state.project_data["fenestrations"]:
388
- st.session_state.project_data["fenestrations"]["library"] = DEFAULT_FENESTRATIONS.copy()
389
- if "project" not in st.session_state.project_data["fenestrations"]:
390
- st.session_state.project_data["fenestrations"]["project"] = {}
391
- if "table" not in st.session_state.project_data["fenestrations"]:
392
- st.session_state.project_data["fenestrations"]["table"] = None
393
-
394
- # Initialize tables if they are None
395
- update_project_materials_table()
396
- update_project_fenestrations_table()
397
-
398
- # Initialize other session state variables
399
- if "material_editor" not in st.session_state:
400
- reset_material_editor()
401
- if "fenestration_editor" not in st.session_state:
402
- reset_fenestration_editor()
403
- if "active_tab" not in st.session_state:
404
- st.session_state.active_tab = "Materials"
405
- if "material_action" not in st.session_state:
406
- st.session_state.material_action = {"action": None, "id": None}
407
- if "fenestration_action" not in st.session_state:
408
- st.session_state.fenestration_action = {"action": None, "id": None}
409
- if "current_page" not in st.session_state:
410
- st.session_state.current_page = "Material Library"
411
-
412
- logger.debug("Session state initialized")
413
- logger.debug(f"Project materials: {list(st.session_state.project_data['materials']['project'].keys())}")
414
- logger.debug(f"Project fenestrations: {list(st.session_state.project_data['fenestrations']['project'].keys())}")
415
-
416
  def display_materials_page():
417
  """
418
  Display the material library page.
419
  This is the main function called by main.py when the Material Library page is selected.
420
  """
421
- initialize_session_state()
422
-
423
- # Apply custom CSS for button styling
424
- st.markdown("""
425
- <style>
426
- .stButton>button {
427
- width: 100%;
428
- font-size: 12px;
429
- padding: 5px;
430
- margin: 0;
431
- }
432
- </style>
433
- """, unsafe_allow_html=True)
434
-
435
  st.title("Material Library")
436
 
 
437
  with st.expander("Help & Information"):
438
  display_materials_help()
439
 
 
440
  tab1, tab2 = st.tabs(["Materials", "Fenestrations"])
441
 
 
442
  with tab1:
443
- st.session_state.active_tab = "Materials"
444
  display_materials_tab()
445
 
 
446
  with tab2:
447
- st.session_state.active_tab = "Fenestrations"
448
  display_fenestrations_tab()
449
 
 
450
  col1, col2 = st.columns(2)
451
 
452
  with col1:
@@ -459,974 +320,860 @@ def display_materials_page():
459
  st.session_state.current_page = "Construction"
460
  st.rerun()
461
 
462
- def handle_material_action(action: Dict[str, Any]):
463
- """Handle material-related actions."""
464
- if action["action"] == "preview":
465
- material = action["material"]
466
- name = action["name"]
467
- st.session_state.material_editor = {
468
- "name": name,
469
- "category": material["category"],
470
- "thermal_conductivity": material["thermal_conductivity"],
471
- "density": material["density"],
472
- "specific_heat": material["specific_heat"],
473
- "default_thickness": material["default_thickness"],
474
- "embodied_carbon": material["embodied_carbon"],
475
- "cost": material["cost"],
476
- "absorptivity": material["absorptivity"],
477
- "emissivity": material["emissivity"],
478
- "colour": material["colour"],
479
- "edit_mode": False,
480
- "original_name": name,
481
- "is_library": action["is_library"]
482
- }
483
- st.session_state.material_form_state = {
484
- "name": name,
485
- "category": material["category"],
486
- "thermal_conductivity": float(material["thermal_conductivity"]),
487
- "density": float(material["density"]),
488
- "specific_heat": float(material["specific_heat"]),
489
- "default_thickness": float(material["default_thickness"]),
490
- "embodied_carbon": float(material["embodied_carbon"]),
491
- "cost": float(material["cost"]),
492
- "absorptivity": float(material["absorptivity"]),
493
- "emissivity": float(material["emissivity"]),
494
- "colour": material["colour"]
495
- }
496
- logger.info(f"Previewed material '{name}'")
497
- st.rerun()
498
- elif action["action"] == "copy":
499
- name = action["name"]
500
- material = action["material"]
501
- new_name = f"{name}_Project"
502
- counter = 1
503
- library_materials = st.session_state.project_data["materials"]["library"]
504
- while new_name in st.session_state.project_data["materials"]["project"] or new_name in library_materials:
505
- new_name = f"{name}_Project_{counter}"
506
- counter += 1
507
- st.session_state.project_data["materials"]["project"][new_name] = material.copy()
508
- st.success(f"Material '{new_name}' copied to project.")
509
- logger.info(f"Copied library material '{name}' as '{new_name}' to project")
510
- # Update project materials table
511
- update_project_materials_table()
512
- st.rerun()
513
- elif action["action"] == "edit":
514
- material = action["material"]
515
- name = action["name"]
516
- st.session_state.material_editor = {
517
- "name": name,
518
- "category": material["category"],
519
- "thermal_conductivity": material["thermal_conductivity"],
520
- "density": material["density"],
521
- "specific_heat": material["specific_heat"],
522
- "default_thickness": material["default_thickness"],
523
- "embodied_carbon": material["embodied_carbon"],
524
- "cost": material["cost"],
525
- "absorptivity": material["absorptivity"],
526
- "emissivity": material["emissivity"],
527
- "colour": material["colour"],
528
- "edit_mode": True,
529
- "original_name": name,
530
- "is_library": False
531
- }
532
- st.session_state.material_form_state = {
533
- "name": name,
534
- "category": material["category"],
535
- "thermal_conductivity": float(material["thermal_conductivity"]),
536
- "density": float(material["density"]),
537
- "specific_heat": float(material["specific_heat"]),
538
- "default_thickness": float(material["default_thickness"]),
539
- "embodied_carbon": float(material["embodied_carbon"]),
540
- "cost": float(material["cost"]),
541
- "absorptivity": float(material["absorptivity"]),
542
- "emissivity": float(material["emissivity"]),
543
- "colour": material["colour"]
544
- }
545
- logger.info(f"Editing material '{name}'")
546
- st.rerun()
547
- elif action["action"] == "delete":
548
- name = action["name"]
549
- is_in_use = check_material_in_use(name)
550
- if is_in_use:
551
- st.error(f"Cannot delete material '{name}' because it is in use in constructions.")
552
- else:
553
- del st.session_state.project_data["materials"]["project"][name]
554
- st.success(f"Material '{name}' deleted from project.")
555
- logger.info(f"Deleted material '{name}' from project")
556
- # Update project materials table
557
- update_project_materials_table()
558
- st.rerun()
559
-
560
- def handle_fenestration_action(action: Dict[str, Any]):
561
- """Handle fenestration-related actions."""
562
- if action["action"] == "preview":
563
- fenestration = action["fenestration"]
564
- name = action["name"]
565
- st.session_state.fenestration_editor = {
566
- "name": name,
567
- "type": fenestration["type"],
568
- "u_value": fenestration["u_value"],
569
- "shgc": fenestration["shgc"],
570
- "visible_trans": fenestration["visible_transmittance"],
571
- "thickness": fenestration["thickness"],
572
- "embodied_carbon": fenestration["embodied_carbon"],
573
- "cost": fenestration["cost"],
574
- "edit_mode": False,
575
- "original_name": name,
576
- "is_library": action["is_library"]
577
- }
578
- st.session_state.fenestration_form_state = {
579
- "name": name,
580
- "type": fenestration["type"],
581
- "u_value": float(fenestration["u_value"]),
582
- "shgc": float(fenestration["shgc"]),
583
- "visible_trans": float(fenestration["visible_transmittance"]),
584
- "thickness": float(fenestration["thickness"]),
585
- "embodied_carbon": float(fenestration["embodied_carbon"]),
586
- "cost": float(fenestration["cost"])
587
- }
588
- logger.info(f"Previewed fenestration '{name}'")
589
- st.rerun()
590
- elif action["action"] == "copy":
591
- name = action["name"]
592
- fenestration = action["fenestration"]
593
- new_name = f"{name}_Project"
594
- counter = 1
595
- library_fenestrations = st.session_state.project_data["fenestrations"]["library"]
596
- while new_name in st.session_state.project_data["fenestrations"]["project"] or new_name in library_fenestrations:
597
- new_name = f"{name}_Project_{counter}"
598
- counter += 1
599
- st.session_state.project_data["fenestrations"]["project"][new_name] = fenestration.copy()
600
- st.success(f"Fenestration '{new_name}' copied to project.")
601
- logger.info(f"Copied library fenestration '{name}' as '{new_name}' to project")
602
- # Update project fenestrations table
603
- update_project_fenestrations_table()
604
- st.rerun()
605
- elif action["action"] == "edit":
606
- fenestration = action["fenestration"]
607
- name = action["name"]
608
- st.session_state.fenestration_editor = {
609
- "name": name,
610
- "type": fenestration["type"],
611
- "u_value": fenestration["u_value"],
612
- "shgc": fenestration["shgc"],
613
- "visible_trans": fenestration["visible_transmittance"],
614
- "thickness": fenestration["thickness"],
615
- "embodied_carbon": fenestration["embodied_carbon"],
616
- "cost": fenestration["cost"],
617
- "edit_mode": True,
618
- "original_name": name,
619
- "is_library": False
620
- }
621
- st.session_state.fenestration_form_state = {
622
- "name": name,
623
- "type": fenestration["type"],
624
- "u_value": float(fenestration["u_value"]),
625
- "shgc": float(fenestration["shgc"]),
626
- "visible_trans": float(fenestration["visible_transmittance"]),
627
- "thickness": float(fenestration["thickness"]),
628
- "embodied_carbon": float(fenestration["embodied_carbon"]),
629
- "cost": float(fenestration["cost"])
630
- }
631
- logger.info(f"Editing fenestration '{name}'")
632
- st.rerun()
633
- elif action["action"] == "delete":
634
- name = action["name"]
635
- is_in_use = check_fenestration_in_use(name)
636
- if is_in_use:
637
- st.error(f"Cannot delete fenestration '{name}' because it is in use in components.")
638
- else:
639
- del st.session_state.project_data["fenestrations"]["project"][name]
640
- st.success(f"Fenestration '{name}' deleted from project.")
641
- logger.info(f"Deleted fenestration '{name}' from project")
642
- # Update project fenestrations table
643
- update_project_fenestrations_table()
644
- st.rerun()
645
-
646
- def update_project_materials_table():
647
- """Update the project materials table in session state."""
648
- project_materials = st.session_state.project_data["materials"]["project"]
649
- data = []
650
- for name, props in project_materials.items():
651
- thermal_mass = calculate_thermal_mass(props["density"], props["specific_heat"], props["default_thickness"])
652
- thermal_mass_category = categorize_thermal_mass(thermal_mass)
653
- u_value = calculate_u_value(props["thermal_conductivity"], props["default_thickness"])
654
- data.append({
655
- "Name": name,
656
- "Category": props["category"],
657
- "Thermal Conductivity (W/m·K)": props["thermal_conductivity"],
658
- "Density (kg/m³)": props["density"],
659
- "Specific Heat (J/kg·K)": props["specific_heat"],
660
- "Thickness (m)": props["default_thickness"],
661
- "Embodied Carbon (kg CO₂e/m³)": props["embodied_carbon"],
662
- "Cost (USD/m³)": props["cost"],
663
- "Absorptivity": props["absorptivity"],
664
- "Emissivity": props["emissivity"],
665
- "Colour": props["colour"],
666
- "Thermal Mass": thermal_mass_category,
667
- "U-Value (W/m²·K)": u_value
668
- })
669
- st.session_state.project_data["materials"]["table"] = pd.DataFrame(data)
670
- logger.debug(f"Updated project materials table with {len(data)} entries")
671
-
672
- def update_project_fenestrations_table():
673
- """Update the project fenestrations table in session state."""
674
- project_fenestrations = st.session_state.project_data["fenestrations"]["project"]
675
- data = []
676
- for name, props in sorted(project_fenestrations.items()):
677
- data.append({
678
- "Name": name,
679
- "Type": props["type"],
680
- "U-Value (W/m²·K)": props["u_value"],
681
- "SHGC": props["shgc"],
682
- "Visible Transmittance": props["visible_transmittance"],
683
- "Thickness (m)": props["thickness"],
684
- "Embodied Carbon (kg/m²)": props["embodied_carbon"],
685
- "Cost (USD/m²)": props["cost"]
686
- })
687
- st.session_state.project_data["fenestrations"]["table"] = pd.DataFrame(data)
688
- logger.debug(f"Updated project fenestrations table with {len(data)} entries")
689
-
690
  def display_materials_tab():
691
  """Display the materials tab content."""
692
- logger.debug("Rendering materials tab")
693
- logger.debug(f"Project materials: {list(st.session_state.project_data['materials']['project'].keys())}")
694
 
695
- st.subheader("Materials")
696
- col1, col2 = st.columns([3, 2])
697
 
 
698
  with col1:
699
- filter_options = ["All"] + MATERIAL_CATEGORIES
700
- category = st.selectbox("Filter by Category", filter_options, key="material_filter")
701
-
702
  st.subheader("Library Materials")
703
- with st.container():
704
- library_materials = st.session_state.project_data["materials"]["library"]
705
- filtered_materials = []
706
- if category == "All":
707
- filtered_materials = list(library_materials.values())
708
- else:
709
- filtered_materials = [m for m in library_materials.values() if m["category"] == category]
710
-
711
- if filtered_materials:
712
- cols = st.columns([2, 1, 1, 1, 1])
713
- cols[0].write("**Name**")
714
- cols[1].write("**Thermal Mass**")
715
- cols[2].write("**U-Value (W/m²·K)**")
716
- cols[3].write("**Preview**")
717
- cols[4].write("**Copy**")
718
-
719
- for material in filtered_materials:
720
- cols = st.columns([2, 1, 1, 1, 1])
721
- name = [k for k, v in library_materials.items() if v == material][0]
722
- thermal_mass = calculate_thermal_mass(material["density"], material["specific_heat"], material["default_thickness"])
723
- thermal_mass_category = categorize_thermal_mass(thermal_mass)
724
- u_value = calculate_u_value(material["thermal_conductivity"], material["default_thickness"])
725
- cols[0].write(name)
726
- cols[1].write(thermal_mass_category)
727
- cols[2].write(f"{u_value:.3f}")
728
-
729
- preview_key = get_stable_button_key("lib_mat", name, "preview")
730
- copy_key = get_stable_button_key("lib_mat", name, "copy")
731
-
732
- cols[3].button("Preview", key=preview_key, on_click=handle_material_action, args=([{
733
- "action": "preview",
734
- "name": name,
735
- "material": material,
736
- "is_library": True
737
- }],))
738
-
739
- cols[4].button("Copy", key=copy_key, on_click=handle_material_action, args=([{
740
- "action": "copy",
741
- "name": name,
742
- "material": material
743
- }],))
744
-
745
- else:
746
- st.info("No materials found in the selected category.")
747
-
748
- project_category = st.selectbox("Filter Project by Category", filter_options, key="project_material_filter")
749
- st.subheader("Project Materials")
750
- with st.container():
751
- project_materials = st.session_state.project_data["materials"]["project"]
752
- filtered_project_materials = []
753
- if project_category == "All":
754
- filtered_project_materials = list(project_materials.values())
755
- else:
756
- filtered_project_materials = [m for m in project_materials.values() if m["category"] == project_category]
757
-
758
- if filtered_project_materials:
759
- cols = st.columns([2, 1, 1, 1, 1])
760
- cols[0].write("**Name**")
761
- cols[1].write("**Thermal Mass**")
762
- cols[2].write("**U-Value (W/m²·K)**")
763
- cols[3].write("**Edit**")
764
- cols[4].write("**Delete**")
765
-
766
- for material in filtered_project_materials:
767
- cols = st.columns([2, 1, 1, 1, 1])
768
- name = [k for k, v in project_materials.items() if v == material][0]
769
- thermal_mass = calculate_thermal_mass(material["density"], material["specific_heat"], material["default_thickness"])
770
- thermal_mass_category = categorize_thermal_mass(thermal_mass)
771
- u_value = calculate_u_value(material["thermal_conductivity"], material["default_thickness"])
772
- cols[0].write(name)
773
- cols[1].write(thermal_mass_category)
774
- cols[2].write(f"{u_value:.3f}")
775
-
776
- edit_key = get_stable_button_key("proj_mat", name, "edit")
777
- delete_key = get_stable_button_key("proj_mat", name, "delete")
778
-
779
- cols[3].button("Edit", key=edit_key, on_click=handle_material_action, args=([{
780
- "action": "edit",
781
- "name": name,
782
- "material": material
783
- }],))
784
-
785
- cols[4].button("Delete", key=delete_key, on_click=handle_material_action, args=([{
786
- "action": "delete",
787
- "name": name
788
- }],))
789
-
790
- else:
791
- st.info("No project materials in the selected category.")
792
-
793
- st.subheader("All Project Materials")
794
- if st.session_state.project_data["materials"]["table"] is not None:
795
- st.dataframe(st.session_state.project_data["materials"]["table"], use_container_width=True, hide_index=True)
796
- else:
797
- st.info("No project materials to display.")
798
 
 
799
  with col2:
800
- st.subheader("Material Editor/Creator")
801
- display_material_editor()
 
 
 
 
 
802
 
803
  def display_fenestrations_tab():
804
  """Display the fenestrations tab content."""
805
- logger.debug("Rendering fenestrations tab")
806
- logger.debug(f"Project fenestrations: {list(st.session_state.project_data['fenestrations']['project'].keys())}")
807
 
808
- st.subheader("Fenestrations")
809
- col1, col2 = st.columns([3, 2])
810
 
 
811
  with col1:
812
- filter_options = ["All"] + FENESTRATION_TYPES
813
- fen_type = st.selectbox("Filter by Type", filter_options, key="fenestration_filter")
814
-
815
  st.subheader("Library Fenestrations")
816
- with st.container():
817
- library_fenestrations = st.session_state.project_data["fenestrations"]["library"]
818
- filtered_fenestrations = []
819
- if fen_type == "All":
820
- filtered_fenestrations = list(library_fenestrations.values())
821
- else:
822
- filtered_fenestrations = [f for f in library_fenestrations.values() if f["type"] == fen_type]
823
-
824
- if filtered_fenestrations:
825
- cols = st.columns([2, 1, 1, 1, 1])
826
- cols[0].write("**Name**")
827
- cols[1].write("**Type**")
828
- cols[2].write("**U-Value (W/m²·K)**")
829
- cols[3].write("**Preview**")
830
- cols[4].write("**Copy**")
831
-
832
- for fenestration in filtered_fenestrations:
833
- cols = st.columns([2, 1, 1, 1, 1])
834
- name = [k for k, v in library_fenestrations.items() if v == fenestration][0]
835
- cols[0].write(name)
836
- cols[1].write(fenestration["type"])
837
- cols[2].write(f"{fenestration['u_value']:.2f}")
838
-
839
- preview_key = get_stable_button_key("lib_fen", name, "preview")
840
- copy_key = get_stable_button_key("lib_fen", name, "copy")
841
-
842
- cols[3].button("Preview", key=preview_key, on_click=handle_fenestration_action, args=([{
843
- "action": "preview",
844
- "name": name,
845
- "fenestration": fenestration,
846
- "is_library": True
847
- }],))
848
-
849
- cols[4].button("Copy", key=copy_key, on_click=handle_fenestration_action, args=([{
850
- "action": "copy",
851
- "name": name,
852
- "fenestration": fenestration
853
- }],))
854
-
855
- else:
856
- st.info("No fenestrations found in the selected type.")
857
-
858
- project_fen_type = st.selectbox("Filter Project by Type", filter_options, key="project_fenestration_filter")
859
- st.subheader("Project Fenestrations")
860
- with st.container():
861
- project_fenestrations = st.session_state.project_data["fenestrations"]["project"]
862
- filtered_project_fenestrations = []
863
- if project_fen_type == "All":
864
- filtered_project_fenestrations = list(project_fenestrations.values())
865
- else:
866
- filtered_project_fenestrations = [f for f in project_fenestrations.values() if f["type"] == project_fen_type]
867
-
868
- if filtered_project_fenestrations:
869
- cols = st.columns([2, 1, 1, 1, 1])
870
- cols[0].write("**Name**")
871
- cols[1].write("**Type**")
872
- cols[2].write("**U-Value (W/m²·K)**")
873
- cols[3].write("**Edit**")
874
- cols[4].write("**Delete**")
875
-
876
- for fenestration in filtered_project_fenestrations:
877
- cols = st.columns([2, 1, 1, 1, 1])
878
- name = [k for k, v in project_fenestrations.items() if v == fenestration][0]
879
- cols[0].write(name)
880
- cols[1].write(fenestration["type"])
881
- cols[2].write(f"{fenestration['u_value']:.2f}")
882
-
883
- edit_key = get_stable_button_key("proj_fen", name, "edit")
884
- delete_key = get_stable_button_key("proj_fen", name, "delete")
885
-
886
- cols[3].button("Edit", key=edit_key, on_click=handle_fenestration_action, args=([{
887
- "action": "edit",
888
- "name": name,
889
- "fenestration": fenestration
890
- }],))
891
-
892
- cols[4].button("Delete", key=delete_key, on_click=handle_fenestration_action, args=([{
893
- "action": "delete",
894
- "name": name
895
- }],))
896
-
897
- else:
898
- st.info("No project fenestrations in the selected type.")
899
-
900
- st.subheader("All Project Fenestrations")
901
- if st.session_state.project_data["fenestrations"]["table"] is not None:
902
- st.dataframe(st.session_state.project_data["fenestrations"]["table"], use_container_width=True, hide_index=True)
903
- else:
904
- st.info("No project fenestrations to display.")
905
 
 
906
  with col2:
907
- st.subheader("Fenestration Editor/Creator")
908
- display_fenestration_editor()
 
 
 
 
 
909
 
910
  def initialize_materials():
911
  """Initialize materials in session state if not present."""
912
  if "materials" not in st.session_state.project_data:
913
  st.session_state.project_data["materials"] = {
914
- "library": DEFAULT_MATERIALS.copy(),
915
- "project": {},
916
- "table": None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
917
  }
918
- logger.debug("Materials initialized")
919
 
920
  def initialize_fenestrations():
921
  """Initialize fenestrations in session state if not present."""
922
  if "fenestrations" not in st.session_state.project_data:
923
  st.session_state.project_data["fenestrations"] = {
924
- "library": DEFAULT_FENESTRATIONS.copy(),
925
- "project": {},
926
- "table": None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
927
  }
928
- logger.debug("Fenestrations initialized")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
929
 
930
  def display_material_editor():
931
  """Display the material editor form."""
932
- with st.form("material_editor_form", clear_on_submit=True):
933
- editor_state = st.session_state.material_editor
934
- form_state = st.session_state.material_form_state
935
- is_library = editor_state.get("is_library", False)
936
-
937
  name = st.text_input(
938
  "Material Name",
939
- value=form_state.get("name", editor_state["name"]),
940
- help="Enter a unique name for the material.",
941
- disabled=is_library
942
  )
943
 
 
944
  col1, col2 = st.columns(2)
945
 
946
  with col1:
947
- category_index = MATERIAL_CATEGORIES.index(form_state.get("category", editor_state.get("category", MATERIAL_CATEGORIES[0])))
948
  category = st.selectbox(
949
  "Category",
950
  MATERIAL_CATEGORIES,
951
- index=category_index,
952
- help="Select the material category.",
953
- disabled=is_library
954
  )
955
 
 
956
  thermal_conductivity = st.number_input(
957
  "Thermal Conductivity (W/m·K)",
958
  min_value=0.001,
959
  max_value=1000.0,
960
- value=float(form_state.get("thermal_conductivity", editor_state.get("thermal_conductivity", 0.5))),
961
  format="%.3f",
962
- help="Thermal conductivity in W/m·K. Lower values indicate better insulation.",
963
- disabled=is_library
964
  )
965
 
 
966
  density = st.number_input(
967
  "Density (kg/m³)",
968
  min_value=1.0,
969
  max_value=20000.0,
970
- value=float(form_state.get("density", editor_state.get("density", 1000.0))),
971
  format="%.1f",
972
- help="Material density in kg/m³.",
973
- disabled=is_library
974
  )
975
 
976
  with col2:
 
977
  specific_heat = st.number_input(
978
  "Specific Heat (J/kg·K)",
979
  min_value=100.0,
980
  max_value=10000.0,
981
- value=float(form_state.get("specific_heat", editor_state.get("specific_heat", 1000.0))),
982
  format="%.1f",
983
- help="Specific heat capacity in J/kg·K. Higher values indicate better thermal mass.",
984
- disabled=is_library
985
  )
986
 
987
- default_thickness = st.number_input(
988
- "Thickness (m)",
 
 
989
  min_value=0.001,
990
  max_value=0.5,
991
- value=float(form_state.get("default_thickness", editor_state.get("default_thickness", 0.05))),
992
  format="%.3f",
993
- help="Thickness for this material in meters.",
994
- disabled=is_library
995
  )
996
 
997
- absorptivity = st.number_input(
998
- "Absorptivity",
999
- min_value=0.0,
1000
- max_value=1.0,
1001
- value=float(form_state.get("absorptivity", editor_state.get("absorptivity", 0.60))),
1002
- format="%.2f",
1003
- help="Solar radiation absorbed (0-1).",
1004
- disabled=is_library
1005
- )
1006
-
1007
- col1, col2 = st.columns(2)
1008
-
1009
- with col1:
1010
- emissivity = st.number_input(
1011
- "Emissivity",
1012
- min_value=0.0,
1013
- max_value=1.0,
1014
- value=float(form_state.get("emissivity", editor_state.get("emissivity", 0.90))),
1015
- format="%.2f",
1016
- help="Ratio of radiation emitted (0-1).",
1017
- disabled=is_library
1018
- )
1019
-
1020
- with col2:
1021
- 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
1022
- colour = st.selectbox(
1023
- "Colour Category",
1024
- COLOUR_CATEGORIES,
1025
- index=colour_index,
1026
- help="Colour category affecting absorptivity.",
1027
- disabled=is_library
1028
  )
1029
 
 
1030
  st.subheader("Additional Properties")
1031
  col1, col2 = st.columns(2)
1032
 
1033
  with col1:
 
1034
  embodied_carbon = st.number_input(
1035
  "Embodied Carbon (kg CO₂e/m³)",
1036
  min_value=0.0,
1037
  max_value=10000.0,
1038
- value=float(form_state.get("embodied_carbon", editor_state.get("embodied_carbon", 100.0))),
1039
  format="%.1f",
1040
- help="Embodied carbon in kg CO₂e per cubic meter.",
1041
- disabled=is_library
1042
  )
1043
 
1044
  with col2:
 
1045
  cost = st.number_input(
1046
  "Cost (USD/m³)",
1047
  min_value=0.0,
1048
  max_value=10000.0,
1049
- value=float(form_state.get("cost", editor_state.get("cost", 100.0))),
1050
  format="%.1f",
1051
- help="Material cost in USD per cubic meter.",
1052
- disabled=is_library
1053
  )
1054
 
 
1055
  col1, col2 = st.columns(2)
1056
 
1057
  with col1:
1058
- submit_button = st.form_submit_button("Save Material", disabled=is_library)
1059
 
1060
  with col2:
1061
  clear_button = st.form_submit_button("Clear Form")
 
 
 
 
 
 
 
 
 
1062
 
1063
- if submit_button and not is_library:
1064
- st.session_state.material_form_state = {
1065
- "name": name,
 
 
 
 
1066
  "category": category,
1067
  "thermal_conductivity": thermal_conductivity,
1068
  "density": density,
1069
  "specific_heat": specific_heat,
 
1070
  "default_thickness": default_thickness,
1071
  "embodied_carbon": embodied_carbon,
1072
- "cost": cost,
1073
- "absorptivity": absorptivity,
1074
- "emissivity": emissivity,
1075
- "colour": colour
1076
  }
1077
- success, message = validate_material(
1078
- name, category, thermal_conductivity, density, specific_heat,
1079
- default_thickness, embodied_carbon, cost,
1080
- editor_state["edit_mode"], editor_state["original_name"],
1081
- absorptivity, emissivity, colour
1082
- )
1083
- if not success:
1084
- st.error(message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1085
  else:
1086
- material_data = {
1087
- "category": category,
1088
- "thermal_conductivity": thermal_conductivity,
1089
- "density": density,
1090
- "specific_heat": specific_heat,
1091
- "default_thickness": default_thickness,
1092
- "embodied_carbon": embodied_carbon,
1093
- "cost": cost,
1094
- "absorptivity": absorptivity,
1095
- "emissivity": emissivity,
1096
- "colour": colour
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1097
  }
1098
- if editor_state["edit_mode"]:
1099
- original_name = editor_state["original_name"]
1100
- if original_name != name:
1101
- del st.session_state.project_data["materials"]["project"][original_name]
1102
- st.session_state.project_data["materials"]["project"][name] = material_data
1103
- st.success(f"Material '{name}' updated successfully.")
1104
- logger.info(f"Updated material '{name}' in project")
1105
- else:
1106
- st.session_state.project_data["materials"]["project"][name] = material_data
1107
- st.success(f"Material '{name}' added to your project.")
1108
- logger.info(f"Added new material '{name}' to project")
1109
- update_project_materials_table()
1110
- reset_material_editor()
1111
- st.rerun()
1112
 
1113
- if clear_button:
1114
- reset_material_editor()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1115
 
1116
  def display_fenestration_editor():
1117
  """Display the fenestration editor form."""
1118
- with st.form("fenestration_editor_form", clear_on_submit=True):
1119
- editor_state = st.session_state.fenestration_editor
1120
- form_state = st.session_state.fenestration_form_state
1121
- is_library = editor_state.get("is_library", False)
1122
-
1123
  name = st.text_input(
1124
  "Fenestration Name",
1125
- value=form_state.get("name", editor_state["name"]),
1126
- help="Enter a unique name for the fenestration.",
1127
- disabled=is_library
1128
  )
1129
 
 
1130
  col1, col2 = st.columns(2)
1131
 
1132
  with col1:
1133
- 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
1134
  fenestration_type = st.selectbox(
1135
  "Type",
1136
  FENESTRATION_TYPES,
1137
- index=type_index,
1138
- help="Select the fenestration type.",
1139
- disabled=is_library
1140
  )
1141
 
 
1142
  u_value = st.number_input(
1143
  "U-Value (W/m²·K)",
1144
  min_value=0.1,
1145
  max_value=10.0,
1146
- value=float(form_state.get("u_value", editor_state.get("u_value", 2.8))),
1147
  format="%.2f",
1148
- help="U-value in W/m²·K.",
1149
- disabled=is_library
1150
  )
1151
 
1152
- with col2:
1153
  shgc = st.number_input(
1154
  "Solar Heat Gain Coefficient (SHGC)",
1155
  min_value=0.0,
1156
  max_value=1.0,
1157
- value=float(form_state.get("shgc", editor_state.get("shgc", 0.7))),
1158
  format="%.2f",
1159
- help="Solar Heat Gain Coefficient (0-1).",
1160
- disabled=is_library
1161
  )
1162
-
1163
- visible_trans = st.number_input(
 
 
1164
  "Visible Transmittance",
1165
  min_value=0.0,
1166
  max_value=1.0,
1167
- value=float(form_state.get("visible_trans", editor_state.get("visible_trans", 0.8))),
1168
  format="%.2f",
1169
- help="Visible light transmission (0-1).",
1170
- disabled=is_library
1171
  )
1172
 
 
1173
  thickness = st.number_input(
1174
  "Thickness (m)",
1175
  min_value=0.001,
1176
  max_value=0.1,
1177
- value=float(form_state.get("thickness", editor_state.get("thickness", 0.024))),
1178
  format="%.3f",
1179
- help="Thickness in meters.",
1180
- disabled=is_library
1181
  )
1182
 
 
1183
  st.subheader("Additional Properties")
1184
-
1185
  col1, col2 = st.columns(2)
1186
 
1187
  with col1:
 
1188
  embodied_carbon = st.number_input(
1189
  "Embodied Carbon (kg CO₂e/m²)",
1190
  min_value=0.0,
1191
  max_value=5000.0,
1192
- value=float(form_state.get("embodied_carbon", editor_state.get("embodied_carbon", 900.0))),
1193
  format="%.1f",
1194
- help="Embodied carbon in kg CO₂e/m².",
1195
- disabled=is_library
1196
  )
1197
 
1198
  with col2:
 
1199
  cost = st.number_input(
1200
  "Cost (USD/m²)",
1201
  min_value=0.0,
1202
  max_value=5000.0,
1203
- value=float(form_state.get("cost", editor_state.get("cost", 200.0))),
1204
  format="%.1f",
1205
- help="Fenestration cost in USD/m².",
1206
- disabled=is_library
1207
  )
1208
 
 
1209
  col1, col2 = st.columns(2)
1210
 
1211
  with col1:
1212
- submit_button = st.form_submit_button("Save Fenestration", disabled=is_library)
1213
 
1214
  with col2:
1215
  clear_button = st.form_submit_button("Clear Form")
 
 
 
 
 
 
 
 
 
1216
 
1217
- if submit_button and not is_library:
1218
- st.session_state.fenestration_form_state = {
1219
- "name": name,
 
 
 
 
1220
  "type": fenestration_type,
1221
  "u_value": u_value,
1222
  "shgc": shgc,
1223
- "visible_trans": visible_trans,
1224
  "thickness": thickness,
1225
  "embodied_carbon": embodied_carbon,
1226
  "cost": cost
1227
  }
1228
- success, message = validate_fenestration(
1229
- name, fenestration_type, u_value, shgc, visible_trans, thickness,
1230
- embodied_carbon, cost, editor_state["edit_mode"], editor_state["original_name"]
1231
- )
1232
- if not success:
1233
- st.error(message)
 
 
 
 
 
 
 
1234
  else:
1235
- fenestration_data = {
1236
- "type": fenestration_type,
1237
- "u_value": u_value,
1238
- "shgc": shgc,
1239
- "visible_transmittance": visible_trans,
1240
- "thickness": thickness,
1241
- "embodied_carbon": embodied_carbon,
1242
- "cost": cost
1243
- }
1244
- if editor_state["edit_mode"]:
1245
- original_name = editor_state["original_name"]
1246
- if original_name != name:
1247
- del st.session_state.project_data["fenestrations"]["project"][original_name]
1248
- st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1249
- st.success(f"Fenestration '{name}' updated successfully.")
1250
- logger.info(f"Updated fenestration '{name}' in project")
1251
- else:
1252
- st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1253
- st.success(f"Fenestration '{name}' added to your project.")
1254
- logger.info(f"Added new fenestration '{name}' to project")
1255
- update_project_fenestrations_table()
1256
- reset_fenestration_editor()
1257
- st.rerun()
1258
-
1259
- if clear_button:
1260
  reset_fenestration_editor()
 
 
 
 
 
1261
 
1262
  def validate_material(
1263
  name: str, category: str, thermal_conductivity: float, density: float,
1264
- specific_heat: float, default_thickness: float, embodied_carbon: float,
1265
- cost: float, edit_mode: bool, original_name: str,
1266
- absorptivity: float, emissivity: float, colour: str
1267
- ) -> Tuple[bool, str]:
1268
  """
1269
  Validate material inputs.
1270
- Returns (success, message) tuple.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1271
  """
 
 
 
1272
  if not name or name.strip() == "":
1273
- return False, "Material name is required."
1274
 
1275
- if (not edit_mode or name != original_name) and name in st.session_state.project_data["materials"]["project"]:
1276
- return False, f"Material name '{name}' already exists in project materials."
 
 
1277
 
 
1278
  if category not in MATERIAL_CATEGORIES:
1279
- return False, "Invalid material category."
1280
 
 
1281
  if thermal_conductivity <= 0:
1282
- return False, "Thermal conductivity must be greater than zero."
1283
 
 
1284
  if density <= 0:
1285
- return False, "Density must be greater than zero."
1286
 
 
1287
  if specific_heat <= 0:
1288
- return False, "Specific heat must be greater than zero."
 
 
 
 
 
 
1289
 
1290
- if default_thickness <= 0:
1291
- return False, "Thickness must be greater than zero."
 
1292
 
 
1293
  if embodied_carbon < 0:
1294
- return False, "Embodied carbon cannot be negative."
1295
 
 
1296
  if cost < 0:
1297
- return False, "Cost cannot be negative."
1298
-
1299
- if absorptivity < 0 or absorptivity > 1:
1300
- return False, "Absorptivity must be between 0 and 1."
1301
-
1302
- if emissivity < 0 or emissivity > 1:
1303
- return False, "Emissivity must be between 0 and 1."
1304
 
1305
- if colour not in COLOUR_CATEGORIES:
1306
- return False, "Invalid colour category."
1307
-
1308
- colour_ranges = {
1309
- "Light": (0.30, 0.50),
1310
- "Medium": (0.60, 0.70),
1311
- "Dark": (0.85, 0.95),
1312
- "Reflective": (0.10, 0.20)
1313
- }
1314
- min_abs, max_abs = colour_ranges[colour]
1315
- if not (min_abs <= absorptivity <= max_abs):
1316
- return False, f"Absorptivity for {colour} colour must be between {min_abs} and {max_abs}."
1317
-
1318
- return True, ""
1319
 
1320
  def validate_fenestration(
1321
  name: str, fenestration_type: str, u_value: float, shgc: float,
1322
- visible_trans: float, thickness: float, embodied_carbon: float,
1323
  cost: float, edit_mode: bool, original_name: str
1324
- ) -> Tuple[bool, str]:
1325
  """
1326
  Validate fenestration inputs.
1327
- Returns (success, message) tuple.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1328
  """
 
 
 
1329
  if not name or name.strip() == "":
1330
- return False, "Fenestration name is required."
1331
 
1332
- if (not edit_mode or name != original_name) and name in st.session_state.project_data["fenestrations"]["project"]:
1333
- return False, f"Fenestration name '{name}' already exists in project."
 
 
1334
 
 
1335
  if fenestration_type not in FENESTRATION_TYPES:
1336
- return False, "Invalid fenestration type."
1337
 
 
1338
  if u_value <= 0:
1339
- return False, "U-value must be greater than zero."
1340
 
 
1341
  if shgc < 0 or shgc > 1:
1342
- return False, "SHGC must be between 0 and 1."
1343
 
1344
- if visible_trans < 0 or visible_trans > 1:
1345
- return False, "Visible transmittance must be between 0 and 1."
 
1346
 
 
1347
  if thickness <= 0:
1348
- return False, "Thickness must be greater than zero."
1349
-
 
1350
  if embodied_carbon < 0:
1351
- return False, "Embodied carbon cannot be negative."
1352
 
 
1353
  if cost < 0:
1354
- return False, "Cost cannot be negative."
1355
 
1356
- return True, ""
1357
 
1358
  def reset_material_editor():
1359
- """Reset the material editor and form state."""
1360
- st.session_state.material_form_state = {
1361
- "name": "",
1362
- "category": MATERIAL_CATEGORIES[0],
1363
- "thermal_conductivity": 0.5,
1364
- "density": 1000.0,
1365
- "specific_heat": 1000.0,
1366
- "default_thickness": 0.05,
1367
- "embodied_carbon": 100.0,
1368
- "cost": 100.0,
1369
- "absorptivity": 0.60,
1370
- "emissivity": 0.90,
1371
- "colour": "Medium"
1372
- }
1373
  st.session_state.material_editor = {
1374
  "name": "",
1375
  "category": MATERIAL_CATEGORIES[0],
1376
  "thermal_conductivity": 0.5,
1377
  "density": 1000.0,
1378
  "specific_heat": 1000.0,
 
1379
  "default_thickness": 0.05,
1380
  "embodied_carbon": 100.0,
1381
  "cost": 100.0,
1382
- "absorptivity": 0.60,
1383
- "emissivity": 0.90,
1384
- "colour": "Medium",
1385
  "edit_mode": False,
1386
- "original_name": "",
1387
- "is_library": False
1388
  }
1389
- st.session_state.material_action = {"action": None, "id": None}
1390
- logger.debug("Material editor reset")
1391
 
1392
  def reset_fenestration_editor():
1393
- """Reset the fenestration editor and form state."""
1394
- st.session_state.fenestration_form_state = {
1395
- "name": "",
1396
- "type": FENESTRATION_TYPES[0],
1397
- "u_value": 2.8,
1398
- "shgc": 0.7,
1399
- "visible_trans": 0.8,
1400
- "thickness": 0.024,
1401
- "embodied_carbon": 900.0,
1402
- "cost": 200.0
1403
- }
1404
  st.session_state.fenestration_editor = {
1405
  "name": "",
1406
  "type": FENESTRATION_TYPES[0],
1407
  "u_value": 2.8,
1408
  "shgc": 0.7,
1409
- "visible_trans": 0.8,
1410
  "thickness": 0.024,
1411
  "embodied_carbon": 900.0,
1412
  "cost": 200.0,
1413
  "edit_mode": False,
1414
- "original_name": "",
1415
- "is_library": False
1416
  }
1417
- st.session_state.fenestration_action = {"action": None, "id": None}
1418
- logger.debug("Fenestration editor reset")
1419
 
1420
  def check_material_in_use(material_name: str) -> bool:
1421
  """
1422
  Check if a material is in use in any constructions.
 
 
 
 
 
 
1423
  """
 
 
1424
  return False
1425
 
1426
- def check_fenestration_in_use(name: str) -> bool:
1427
  """
1428
  Check if a fenestration is in use in any components.
 
 
 
 
 
 
1429
  """
 
 
1430
  return False
1431
 
1432
  def display_materials_help():
@@ -1438,35 +1185,31 @@ def display_materials_help():
1438
 
1439
  **Materials Tab:**
1440
 
1441
- - **Library Materials**: Pre-defined materials with standard thermal properties. Use 'Preview' to view details or 'Copy' to add to your project.
1442
- - **Project Materials**: Materials you've added to your project. Use 'Edit' to modify or 'Delete' to remove.
1443
- - **Material Editor/Creator**: Create new materials or edit existing project materials. Library materials can only be previewed.
1444
 
1445
  **Fenestrations Tab:**
1446
 
1447
- - **Library Fenestrations**: Pre-defined windows, doors, and skylights. Use 'Preview' to view details or 'Copy' to add to your project.
1448
- - **Project Fenestrations**: Fenestrations you've added to your project. Use 'Edit' to modify or 'Delete' to remove.
1449
- - **Fenestration Editor/Creator**: Create new fenestrations or edit existing project fenestrations. Library fenestrations can only be previewed.
1450
 
1451
  **Key Properties:**
1452
 
1453
- - **Thermal Conductivity (W/m·K)**: Rate of heat transfer through a material. Lower values indicate better insulation.
1454
- - **Density (kg/m³)**: Mass per unit volume.
1455
- - **Specific Heat (J/kg·K)**: Energy required to raise the temperature of 1 kg by 1 K. Higher values indicate better thermal mass.
1456
- - **Thermal Mass (J/m²·K)**: Areal heat capacity (density × specific heat × thickness).
1457
- - **U-Value (W/m²·K)**: Overall heat transfer coefficient, accounting for material and surfaces resistances. Lower values indicate better insulation.
1458
- - **SHGC**: Solar Heat Gain Coefficient (0-1). Fraction of incident solar radiation that enters through a fenestration.
1459
- - **Visible Transmittance**: Fraction of visible light that passes through a fenestration.
1460
- - **Embodied Carbon**: Carbon emissions associated with material production, measured in kg CO₂e per unit volume or area.
1461
- - **Cost**: Material cost in USD per unit volume or area.
1462
- - **Absorptivity**: Fraction of solar radiation absorbed (0-1). Depends on colour category.
1463
- - **Emissivity**: Ratio of radiation emitted by the material (0-1).
1464
- - **Colour**: Category affecting absorptivity (Light, Medium, Dark, Reflective).
1465
 
1466
  **Workflow:**
1467
 
1468
  1. Browse the library materials and fenestrations
1469
- 2. Copy items to your project or create custom ones
1470
  3. Edit properties as needed for your specific project
1471
  4. Continue to the Construction page to create assemblies using these materials
1472
- """)
 
18
  from typing import Dict, List, Any, Optional, Tuple, Union
19
 
20
  # Configure logging
21
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
22
  logger = logging.getLogger(__name__)
23
 
24
  # Define constants
 
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:
 
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": "",
385
+ "category": MATERIAL_CATEGORIES[0],
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():
398
  """Initialize fenestrations in session state if not present."""
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
+ """)