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

Update app/materials_library.py

Browse files
Files changed (1) hide show
  1. app/materials_library.py +559 -426
app/materials_library.py CHANGED
@@ -37,7 +37,14 @@ FENESTRATION_TYPES = [
37
  "Custom"
38
  ]
39
 
40
- # Default library materials
 
 
 
 
 
 
 
41
  DEFAULT_MATERIALS = {
42
  "Brick": {
43
  "category": "Structural Materials",
@@ -47,7 +54,10 @@ DEFAULT_MATERIALS = {
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",
@@ -57,7 +67,10 @@ DEFAULT_MATERIALS = {
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",
@@ -67,7 +80,10 @@ DEFAULT_MATERIALS = {
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",
@@ -77,7 +93,10 @@ DEFAULT_MATERIALS = {
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",
@@ -87,7 +106,10 @@ DEFAULT_MATERIALS = {
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",
@@ -97,7 +119,10 @@ DEFAULT_MATERIALS = {
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",
@@ -107,7 +132,10 @@ DEFAULT_MATERIALS = {
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",
@@ -117,7 +145,10 @@ DEFAULT_MATERIALS = {
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",
@@ -127,7 +158,10 @@ DEFAULT_MATERIALS = {
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",
@@ -137,7 +171,10 @@ DEFAULT_MATERIALS = {
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",
@@ -147,7 +184,10 @@ DEFAULT_MATERIALS = {
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",
@@ -157,7 +197,10 @@ DEFAULT_MATERIALS = {
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",
@@ -167,7 +210,10 @@ DEFAULT_MATERIALS = {
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",
@@ -177,7 +223,10 @@ DEFAULT_MATERIALS = {
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",
@@ -187,11 +236,14 @@ DEFAULT_MATERIALS = {
187
  "thickness_range": [0.025, 0.2],
188
  "default_thickness": 0.075,
189
  "embodied_carbon": 102.0,
190
- "cost": 95.0
 
 
 
191
  }
192
  }
193
 
194
- # Default library fenestrations
195
  DEFAULT_FENESTRATIONS = {
196
  "Single Glazing": {
197
  "type": "Window",
@@ -200,7 +252,10 @@ DEFAULT_FENESTRATIONS = {
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",
@@ -209,7 +264,10 @@ DEFAULT_FENESTRATIONS = {
209
  "visible_transmittance": 0.78,
210
  "thickness": 0.024,
211
  "embodied_carbon": 950.0,
212
- "cost": 180.0
 
 
 
213
  },
214
  "Double Glazing (Argon)": {
215
  "type": "Window",
@@ -218,7 +276,10 @@ DEFAULT_FENESTRATIONS = {
218
  "visible_transmittance": 0.76,
219
  "thickness": 0.024,
220
  "embodied_carbon": 980.0,
221
- "cost": 220.0
 
 
 
222
  },
223
  "Triple Glazing": {
224
  "type": "Window",
@@ -227,7 +288,10 @@ DEFAULT_FENESTRATIONS = {
227
  "visible_transmittance": 0.68,
228
  "thickness": 0.036,
229
  "embodied_carbon": 1100.0,
230
- "cost": 320.0
 
 
 
231
  },
232
  "Low-E Double Glazing": {
233
  "type": "Window",
@@ -236,7 +300,10 @@ DEFAULT_FENESTRATIONS = {
236
  "visible_transmittance": 0.74,
237
  "thickness": 0.024,
238
  "embodied_carbon": 1050.0,
239
- "cost": 250.0
 
 
 
240
  },
241
  "Wooden Door": {
242
  "type": "Door",
@@ -245,7 +312,10 @@ DEFAULT_FENESTRATIONS = {
245
  "visible_transmittance": 0.0,
246
  "thickness": 0.04,
247
  "embodied_carbon": 400.0,
248
- "cost": 280.0
 
 
 
249
  },
250
  "Steel Door": {
251
  "type": "Door",
@@ -254,7 +324,10 @@ DEFAULT_FENESTRATIONS = {
254
  "visible_transmittance": 0.0,
255
  "thickness": 0.035,
256
  "embodied_carbon": 1200.0,
257
- "cost": 350.0
 
 
 
258
  },
259
  "Glass Door": {
260
  "type": "Door",
@@ -263,7 +336,10 @@ DEFAULT_FENESTRATIONS = {
263
  "visible_transmittance": 0.7,
264
  "thickness": 0.01,
265
  "embodied_carbon": 900.0,
266
- "cost": 420.0
 
 
 
267
  },
268
  "Skylight (Double Glazed)": {
269
  "type": "Skylight",
@@ -272,7 +348,10 @@ DEFAULT_FENESTRATIONS = {
272
  "visible_transmittance": 0.75,
273
  "thickness": 0.024,
274
  "embodied_carbon": 1050.0,
275
- "cost": 380.0
 
 
 
276
  },
277
  "Skylight (Low-E)": {
278
  "type": "Skylight",
@@ -281,7 +360,10 @@ DEFAULT_FENESTRATIONS = {
281
  "visible_transmittance": 0.7,
282
  "thickness": 0.024,
283
  "embodied_carbon": 1150.0,
284
- "cost": 450.0
 
 
 
285
  }
286
  }
287
 
@@ -325,46 +407,246 @@ def display_materials_tab():
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."""
@@ -390,8 +672,12 @@ def initialize_materials():
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():
@@ -417,144 +703,26 @@ def initialize_fenestrations():
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
@@ -565,8 +733,9 @@ def display_material_editor():
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
@@ -574,9 +743,10 @@ def display_material_editor():
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
@@ -584,9 +754,10 @@ def display_material_editor():
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:
@@ -595,20 +766,22 @@ def display_material_editor():
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
@@ -616,12 +789,13 @@ def display_material_editor():
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
 
@@ -631,9 +805,21 @@ def display_material_editor():
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:
@@ -642,31 +828,52 @@ def display_material_editor():
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:
@@ -679,168 +886,46 @@ def display_material_editor():
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
@@ -851,8 +936,9 @@ def display_fenestration_editor():
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
@@ -860,9 +946,10 @@ def display_fenestration_editor():
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
@@ -870,9 +957,10 @@ def display_fenestration_editor():
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:
@@ -881,9 +969,10 @@ def display_fenestration_editor():
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
@@ -891,12 +980,13 @@ def display_fenestration_editor():
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
 
@@ -906,9 +996,21 @@ def display_fenestration_editor():
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:
@@ -917,31 +1019,51 @@ def display_fenestration_editor():
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:
@@ -953,29 +1075,28 @@ def display_fenestration_editor():
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:
@@ -985,26 +1106,11 @@ def display_fenestration_editor():
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
 
@@ -1012,7 +1118,7 @@ def validate_material(
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.")
@@ -1051,30 +1157,39 @@ def validate_material(
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
 
@@ -1082,7 +1197,7 @@ def validate_fenestration(
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.")
@@ -1115,6 +1230,29 @@ def validate_fenestration(
1115
  if cost < 0:
1116
  errors.append("Cost cannot be negative.")
1117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1118
  return errors
1119
 
1120
  def reset_material_editor():
@@ -1129,8 +1267,12 @@ def reset_material_editor():
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():
@@ -1144,36 +1286,24 @@ def reset_fenestration_editor():
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,15 +1315,15 @@ 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
 
@@ -1205,11 +1335,14 @@ def display_materials_help():
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
- """)
 
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
+ # Default library materials with absorptivity, emissivity, and colour
48
  DEFAULT_MATERIALS = {
49
  "Brick": {
50
  "category": "Structural Materials",
 
54
  "thickness_range": [0.05, 0.3],
55
  "default_thickness": 0.1,
56
  "embodied_carbon": 240.0, # kg CO2e/m³
57
+ "cost": 180.0, # USD/m³
58
+ "absorptivity": 0.65, # Medium (reddish-brown brick)
59
+ "emissivity": 0.90, # Typical for ceramics
60
+ "colour": "Medium"
61
  },
62
  "Concrete": {
63
  "category": "Structural Materials",
 
67
  "thickness_range": [0.05, 0.5],
68
  "default_thickness": 0.15,
69
  "embodied_carbon": 320.0,
70
+ "cost": 120.0,
71
+ "absorptivity": 0.60, # Medium (grey concrete)
72
+ "emissivity": 0.88, # Typical for concrete
73
+ "colour": "Medium"
74
  },
75
  "Gypsum Board": {
76
  "category": "Finishing Materials",
 
80
  "thickness_range": [0.01, 0.05],
81
  "default_thickness": 0.0125,
82
  "embodied_carbon": 120.0,
83
+ "cost": 90.0,
84
+ "absorptivity": 0.40, # Light (white-painted surface)
85
+ "emissivity": 0.90, # Typical for painted surfaces
86
+ "colour": "Light"
87
  },
88
  "Mineral Wool": {
89
  "category": "Insulation",
 
93
  "thickness_range": [0.025, 0.3],
94
  "default_thickness": 0.1,
95
  "embodied_carbon": 45.0,
96
+ "cost": 70.0,
97
+ "absorptivity": 0.50, # Light (light-colored insulation)
98
+ "emissivity": 0.95, # Typical for fibrous materials
99
+ "colour": "Light"
100
  },
101
  "EPS Insulation": {
102
  "category": "Insulation",
 
106
  "thickness_range": [0.025, 0.3],
107
  "default_thickness": 0.1,
108
  "embodied_carbon": 88.0,
109
+ "cost": 65.0,
110
+ "absorptivity": 0.40, # Light (white foam)
111
+ "emissivity": 0.92, # Typical for polymers
112
+ "colour": "Light"
113
  },
114
  "Wood (Pine)": {
115
  "category": "Structural Materials",
 
119
  "thickness_range": [0.01, 0.3],
120
  "default_thickness": 0.05,
121
  "embodied_carbon": 110.0,
122
+ "cost": 250.0,
123
+ "absorptivity": 0.65, # Medium (natural wood)
124
+ "emissivity": 0.90, # Typical for wood
125
+ "colour": "Medium"
126
  },
127
  "Steel": {
128
  "category": "Structural Materials",
 
132
  "thickness_range": [0.001, 0.05],
133
  "default_thickness": 0.005,
134
  "embodied_carbon": 1750.0,
135
+ "cost": 800.0,
136
+ "absorptivity": 0.15, # Reflective (polished steel)
137
+ "emissivity": 0.20, # Typical for polished metals
138
+ "colour": "Reflective"
139
  },
140
  "Aluminum": {
141
  "category": "Structural Materials",
 
145
  "thickness_range": [0.001, 0.05],
146
  "default_thickness": 0.003,
147
  "embodied_carbon": 8500.0,
148
+ "cost": 1200.0,
149
+ "absorptivity": 0.10, # Reflective (polished aluminum)
150
+ "emissivity": 0.05, # Typical for polished aluminum
151
+ "colour": "Reflective"
152
  },
153
  "Glass Fiber Insulation": {
154
  "category": "Insulation",
 
158
  "thickness_range": [0.025, 0.3],
159
  "default_thickness": 0.1,
160
  "embodied_carbon": 28.0,
161
+ "cost": 45.0,
162
+ "absorptivity": 0.50, # Light (light-colored fibers)
163
+ "emissivity": 0.95, # Typical for fibrous materials
164
+ "colour": "Light"
165
  },
166
  "Ceramic Tile": {
167
  "category": "Finishing Materials",
 
171
  "thickness_range": [0.005, 0.025],
172
  "default_thickness": 0.01,
173
  "embodied_carbon": 650.0,
174
+ "cost": 280.0,
175
+ "absorptivity": 0.60, # Medium (neutral-colored tiles)
176
+ "emissivity": 0.90, # Typical for ceramics
177
+ "colour": "Medium"
178
  },
179
  "Carpet": {
180
  "category": "Finishing Materials",
 
184
  "thickness_range": [0.005, 0.02],
185
  "default_thickness": 0.01,
186
  "embodied_carbon": 40.0,
187
+ "cost": 150.0,
188
+ "absorptivity": 0.70, # Medium (typical carpet colors)
189
+ "emissivity": 0.95, # Typical for fabrics
190
+ "colour": "Medium"
191
  },
192
  "Plywood": {
193
  "category": "Sub-Structural Materials",
 
197
  "thickness_range": [0.006, 0.025],
198
  "default_thickness": 0.012,
199
  "embodied_carbon": 350.0,
200
+ "cost": 180.0,
201
+ "absorptivity": 0.65, # Medium (natural wood)
202
+ "emissivity": 0.90, # Typical for wood
203
+ "colour": "Medium"
204
  },
205
  "Concrete Block": {
206
  "category": "Structural Materials",
 
210
  "thickness_range": [0.1, 0.3],
211
  "default_thickness": 0.2,
212
  "embodied_carbon": 260.0,
213
+ "cost": 140.0,
214
+ "absorptivity": 0.60, # Medium (grey blocks)
215
+ "emissivity": 0.88, # Typical for concrete
216
+ "colour": "Medium"
217
  },
218
  "Stone (Granite)": {
219
  "category": "Finishing Materials",
 
223
  "thickness_range": [0.01, 0.1],
224
  "default_thickness": 0.03,
225
  "embodied_carbon": 170.0,
226
+ "cost": 450.0,
227
+ "absorptivity": 0.85, # Dark (dark stone)
228
+ "emissivity": 0.90, # Typical for stone
229
+ "colour": "Dark"
230
  },
231
  "Polyurethane Insulation": {
232
  "category": "Insulation",
 
236
  "thickness_range": [0.025, 0.2],
237
  "default_thickness": 0.075,
238
  "embodied_carbon": 102.0,
239
+ "cost": 95.0,
240
+ "absorptivity": 0.40, # Light (light-colored foam)
241
+ "emissivity": 0.92, # Typical for polymers
242
+ "colour": "Light"
243
  }
244
  }
245
 
246
+ # Default library fenestrations with absorptivity and emissivity
247
  DEFAULT_FENESTRATIONS = {
248
  "Single Glazing": {
249
  "type": "Window",
 
252
  "visible_transmittance": 0.9,
253
  "thickness": 0.006,
254
  "embodied_carbon": 800.0, # kg CO2e/m²
255
+ "cost": 120.0, # USD/m²
256
+ "absorptivity": 0.15, # Reflective (clear glass)
257
+ "emissivity": 0.84, # Typical for glass
258
+ "colour": "Reflective"
259
  },
260
  "Double Glazing (Air)": {
261
  "type": "Window",
 
264
  "visible_transmittance": 0.78,
265
  "thickness": 0.024,
266
  "embodied_carbon": 950.0,
267
+ "cost": 180.0,
268
+ "absorptivity": 0.15, # Reflective
269
+ "emissivity": 0.84,
270
+ "colour": "Reflective"
271
  },
272
  "Double Glazing (Argon)": {
273
  "type": "Window",
 
276
  "visible_transmittance": 0.76,
277
  "thickness": 0.024,
278
  "embodied_carbon": 980.0,
279
+ "cost": 220.0,
280
+ "absorptivity": 0.15, # Reflective
281
+ "emissivity": 0.84,
282
+ "colour": "Reflective"
283
  },
284
  "Triple Glazing": {
285
  "type": "Window",
 
288
  "visible_transmittance": 0.68,
289
  "thickness": 0.036,
290
  "embodied_carbon": 1100.0,
291
+ "cost": 320.0,
292
+ "absorptivity": 0.15, # Reflective
293
+ "emissivity": 0.84,
294
+ "colour": "Reflective"
295
  },
296
  "Low-E Double Glazing": {
297
  "type": "Window",
 
300
  "visible_transmittance": 0.74,
301
  "thickness": 0.024,
302
  "embodied_carbon": 1050.0,
303
+ "cost": 250.0,
304
+ "absorptivity": 0.10, # Reflective (low-E coating)
305
+ "emissivity": 0.10, # Typical for low-E glass
306
+ "colour": "Reflective"
307
  },
308
  "Wooden Door": {
309
  "type": "Door",
 
312
  "visible_transmittance": 0.0,
313
  "thickness": 0.04,
314
  "embodied_carbon": 400.0,
315
+ "cost": 280.0,
316
+ "absorptivity": 0.65, # Medium (natural wood)
317
+ "emissivity": 0.90,
318
+ "colour": "Medium"
319
  },
320
  "Steel Door": {
321
  "type": "Door",
 
324
  "visible_transmittance": 0.0,
325
  "thickness": 0.035,
326
  "embodied_carbon": 1200.0,
327
+ "cost": 350.0,
328
+ "absorptivity": 0.15, # Reflective (polished steel)
329
+ "emissivity": 0.20,
330
+ "colour": "Reflective"
331
  },
332
  "Glass Door": {
333
  "type": "Door",
 
336
  "visible_transmittance": 0.7,
337
  "thickness": 0.01,
338
  "embodied_carbon": 900.0,
339
+ "cost": 420.0,
340
+ "absorptivity": 0.15, # Reflective
341
+ "emissivity": 0.84,
342
+ "colour": "Reflective"
343
  },
344
  "Skylight (Double Glazed)": {
345
  "type": "Skylight",
 
348
  "visible_transmittance": 0.75,
349
  "thickness": 0.024,
350
  "embodied_carbon": 1050.0,
351
+ "cost": 380.0,
352
+ "absorptivity": 0.15, # Reflective
353
+ "emissivity": 0.84,
354
+ "colour": "Reflective"
355
  },
356
  "Skylight (Low-E)": {
357
  "type": "Skylight",
 
360
  "visible_transmittance": 0.7,
361
  "thickness": 0.024,
362
  "embodied_carbon": 1150.0,
363
+ "cost": 450.0,
364
+ "absorptivity": 0.10, # Reflective (low-E coating)
365
+ "emissivity": 0.10,
366
+ "colour": "Reflective"
367
  }
368
  }
369
 
 
407
  # Initialize materials in session state if not present
408
  initialize_materials()
409
 
410
+ st.subheader("Materials")
411
+ col1, col2 = st.columns([3, 2])
412
 
 
413
  with col1:
414
+ # Category filter
415
+ filter_options = ["All"] + MATERIAL_CATEGORIES
416
+ category = st.selectbox("Filter by Category", filter_options, key="material_filter")
417
+
418
+ # Library Materials
419
  st.subheader("Library Materials")
420
+ with st.container():
421
+ library_materials = st.session_state.project_data["materials"]["library"]
422
+ filtered_materials = []
423
+ if category == "All":
424
+ filtered_materials = list(library_materials.values())
425
+ else:
426
+ filtered_materials = [m for m in library_materials.values() if m["category"] == category]
427
+
428
+ if filtered_materials:
429
+ cols = st.columns([2, 1, 1, 1, 1])
430
+ cols[0].write("**Name**")
431
+ cols[1].write("**Thermal Mass (J/kg·K)**")
432
+ cols[2].write("**U-Value (W/m²·K)**")
433
+ cols[3].write("**Preview**")
434
+ cols[4].write("**Copy**")
435
+
436
+ for material in filtered_materials:
437
+ cols = st.columns([2, 1, 1, 1, 1])
438
+ name = [k for k, v in library_materials.items() if v == material][0]
439
+ u_value = material["thermal_conductivity"] / material["default_thickness"]
440
+ cols[0].write(name)
441
+ cols[1].write(f"{material['specific_heat']:.1f}")
442
+ cols[2].write(f"{u_value:.3f}")
443
+ if cols[3].button("Preview", key=f"preview_lib_mat_{name}"):
444
+ st.session_state.material_editor = {
445
+ "name": name,
446
+ "category": material["category"],
447
+ "thermal_conductivity": material["thermal_conductivity"],
448
+ "density": material["density"],
449
+ "specific_heat": material["specific_heat"],
450
+ "thickness_range": material["thickness_range"],
451
+ "default_thickness": material["default_thickness"],
452
+ "embodied_carbon": material["embodied_carbon"],
453
+ "cost": material["cost"],
454
+ "absorptivity": material["absorptivity"],
455
+ "emissivity": material["emissivity"],
456
+ "colour": material["colour"],
457
+ "edit_mode": False,
458
+ "original_name": name,
459
+ "is_library": True
460
+ }
461
+ st.session_state.active_tab = "Materials"
462
+ st.success(f"Previewing material '{name}'")
463
+ if cols[4].button("Copy", key=f"copy_lib_mat_{name}"):
464
+ new_name = f"{name}_Project"
465
+ counter = 1
466
+ while new_name in st.session_state.project_data["materials"]["project"] or new_name in library_materials:
467
+ new_name = f"{name}_Project_{counter}"
468
+ counter += 1
469
+ st.session_state.project_data["materials"]["project"][new_name] = material.copy()
470
+ st.success(f"Material '{new_name}' copied to project.")
471
+ logger.info(f"Copied library material '{name}' as '{new_name}' to project")
472
+ st.rerun()
473
+ else:
474
+ st.info("No materials found in the selected category.")
475
+
476
+ # Project Materials
477
  st.subheader("Project Materials")
478
+ with st.container():
479
+ project_materials = st.session_state.project_data["materials"]["project"]
480
+ if project_materials:
481
+ cols = st.columns([2, 1, 1, 1, 1])
482
+ cols[0].write("**Name**")
483
+ cols[1].write("**Thermal Mass (J/kg·K)**")
484
+ cols[2].write("**U-Value (W/m²·K)**")
485
+ cols[3].write("**Edit**")
486
+ cols[4].write("**Delete**")
487
+
488
+ for name, material in project_materials.items():
489
+ cols = st.columns([2, 1, 1, 1, 1])
490
+ u_value = material["thermal_conductivity"] / material["default_thickness"]
491
+ cols[0].write(name)
492
+ cols[1].write(f"{material['specific_heat']:.1f}")
493
+ cols[2].write(f"{u_value:.3f}")
494
+ if cols[3].button("Edit", key=f"edit_proj_mat_{name}"):
495
+ st.session_state.material_editor = {
496
+ "name": name,
497
+ "category": material["category"],
498
+ "thermal_conductivity": material["thermal_conductivity"],
499
+ "density": material["density"],
500
+ "specific_heat": material["specific_heat"],
501
+ "thickness_range": material["thickness_range"],
502
+ "default_thickness": material["default_thickness"],
503
+ "embodied_carbon": material["embodied_carbon"],
504
+ "cost": material["cost"],
505
+ "absorptivity": material["absorptivity"],
506
+ "emissivity": material["emissivity"],
507
+ "colour": material["colour"],
508
+ "edit_mode": True,
509
+ "original_name": name,
510
+ "is_library": False
511
+ }
512
+ st.session_state.active_tab = "Materials"
513
+ st.success(f"Editing material '{name}'")
514
+ if cols[4].button("Delete", key=f"delete_proj_mat_{name}"):
515
+ is_in_use = check_material_in_use(name)
516
+ if is_in_use:
517
+ st.error(f"Cannot delete material '{name}' because it is in use in constructions.")
518
+ else:
519
+ del st.session_state.project_data["materials"]["project"][name]
520
+ st.success(f"Material '{name}' deleted from project.")
521
+ logger.info(f"Deleted material '{name}' from project")
522
+ st.rerun()
523
+ else:
524
+ st.info("No project materials added.")
525
 
526
+ with col2:
527
+ st.subheader("Material Editor/Creator")
528
+ display_material_editor()
 
529
 
530
  def display_fenestrations_tab():
531
  """Display the fenestrations tab content."""
532
  # Initialize fenestrations in session state if not present
533
  initialize_fenestrations()
534
 
535
+ st.subheader("Fenestrations")
536
+ col1, col2 = st.columns([3, 2])
537
 
 
538
  with col1:
539
+ # Type filter
540
+ filter_options = ["All"] + FENESTRATION_TYPES
541
+ fen_type = st.selectbox("Filter by Type", filter_options, key="fenestration_filter")
542
+
543
+ # Library Fenestrations
544
  st.subheader("Library Fenestrations")
545
+ with st.container():
546
+ library_fenestrations = st.session_state.project_data["fenestrations"]["library"]
547
+ filtered_fenestrations = []
548
+ if fen_type == "All":
549
+ filtered_fenestrations = list(library_fenestrations.values())
550
+ else:
551
+ filtered_fenestrations = [f for f in library_fenestrations.values() if f["type"] == fen_type]
552
+
553
+ if filtered_fenestrations:
554
+ cols = st.columns([2, 1, 1, 1, 1])
555
+ cols[0].write("**Name**")
556
+ cols[1].write("**Type**")
557
+ cols[2].write("**U-Value (W/m²·K)**")
558
+ cols[3].write("**Preview**")
559
+ cols[4].write("**Copy**")
560
+
561
+ for fenestration in filtered_fenestrations:
562
+ cols = st.columns([2, 1, 1, 1, 1])
563
+ name = [k for k, v in library_fenestrations.items() if v == fenestration][0]
564
+ cols[0].write(name)
565
+ cols[1].write(fenestration["type"])
566
+ cols[2].write(f"{fenestration['u_value']:.2f}")
567
+ if cols[3].button("Preview", key=f"preview_lib_fen_{name}"):
568
+ st.session_state.fenestration_editor = {
569
+ "name": name,
570
+ "type": fenestration["type"],
571
+ "u_value": fenestration["u_value"],
572
+ "shgc": fenestration["shgc"],
573
+ "visible_transmittance": fenestration["visible_transmittance"],
574
+ "thickness": fenestration["thickness"],
575
+ "embodied_carbon": fenestration["embodied_carbon"],
576
+ "cost": fenestration["cost"],
577
+ "absorptivity": fenestration["absorptivity"],
578
+ "emissivity": fenestration["emissivity"],
579
+ "colour": fenestration["colour"],
580
+ "edit_mode": False,
581
+ "original_name": name,
582
+ "is_library": True
583
+ }
584
+ st.session_state.active_tab = "Fenestrations"
585
+ st.success(f"Previewing fenestration '{name}'")
586
+ if cols[4].button("Copy", key=f"copy_lib_fen_{name}"):
587
+ new_name = f"{name}_Project"
588
+ counter = 1
589
+ while new_name in st.session_state.project_data["fenestrations"]["project"] or new_name in library_fenestrations:
590
+ new_name = f"{name}_Project_{counter}"
591
+ counter += 1
592
+ st.session_state.project_data["fenestrations"]["project"][new_name] = fenestration.copy()
593
+ st.success(f"Fenestration '{new_name}' copied to project.")
594
+ logger.info(f"Copied library fenestration '{name}' as '{new_name}' to project")
595
+ st.rerun()
596
+ else:
597
+ st.info("No fenestrations found in the selected type.")
598
+
599
+ # Project Fenestrations
600
  st.subheader("Project Fenestrations")
601
+ with st.container():
602
+ project_fenestrations = st.session_state.project_data["fenestrations"]["project"]
603
+ if project_fenestrations:
604
+ cols = st.columns([2, 1, 1, 1, 1])
605
+ cols[0].write("**Name**")
606
+ cols[1].write("**Type**")
607
+ cols[2].write("**U-Value (W/m²·K)**")
608
+ cols[3].write("**Edit**")
609
+ cols[4].write("**Delete**")
610
+
611
+ for name, fenestration in project_fenestrations.items():
612
+ cols = st.columns([2, 1, 1, 1, 1])
613
+ cols[0].write(name)
614
+ cols[1].write(fenestration["type"])
615
+ cols[2].write(f"{fenestration['u_value']:.2f}")
616
+ if cols[3].button("Edit", key=f"edit_proj_fen_{name}"):
617
+ st.session_state.fenestration_editor = {
618
+ "name": name,
619
+ "type": fenestration["type"],
620
+ "u_value": fenestration["u_value"],
621
+ "shgc": fenestration["shgc"],
622
+ "visible_transmittance": fenestration["visible_transmittance"],
623
+ "thickness": fenestration["thickness"],
624
+ "embodied_carbon": fenestration["embodied_carbon"],
625
+ "cost": fenestration["cost"],
626
+ "absorptivity": fenestration["absorptivity"],
627
+ "emissivity": fenestration["emissivity"],
628
+ "colour": fenestration["colour"],
629
+ "edit_mode": True,
630
+ "original_name": name,
631
+ "is_library": False
632
+ }
633
+ st.session_state.active_tab = "Fenestrations"
634
+ st.success(f"Editing fenestration '{name}'")
635
+ if cols[4].button("Delete", key=f"delete_proj_fen_{name}"):
636
+ is_in_use = check_fenestration_in_use(name)
637
+ if is_in_use:
638
+ st.error(f"Cannot delete fenestration '{name}' because it is in use in components.")
639
+ else:
640
+ del st.session_state.project_data["fenestrations"]["project"][name]
641
+ st.success(f"Fenestration '{name}' deleted from project.")
642
+ logger.info(f"Deleted fenestration '{name}' from project")
643
+ st.rerun()
644
+ else:
645
+ st.info("No project fenestrations added.")
646
 
647
+ with col2:
648
+ st.subheader("Fenestration Editor/Creator")
649
+ display_fenestration_editor()
 
650
 
651
  def initialize_materials():
652
  """Initialize materials in session state if not present."""
 
672
  "default_thickness": 0.05,
673
  "embodied_carbon": 100.0,
674
  "cost": 100.0,
675
+ "absorptivity": 0.60,
676
+ "emissivity": 0.90,
677
+ "colour": "Medium",
678
  "edit_mode": False,
679
+ "original_name": "",
680
+ "is_library": False
681
  }
682
 
683
  def initialize_fenestrations():
 
703
  "thickness": 0.024,
704
  "embodied_carbon": 900.0,
705
  "cost": 200.0,
706
+ "absorptivity": 0.15,
707
+ "emissivity": 0.84,
708
+ "colour": "Reflective",
709
  "edit_mode": False,
710
+ "original_name": "",
711
+ "is_library": False
712
  }
713
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
714
  def display_material_editor():
715
  """Display the material editor form."""
716
  with st.form("material_editor_form"):
717
+ editor_state = st.session_state.material_editor
718
+ is_library = editor_state.get("is_library", False)
719
+
720
  # Material name
721
  name = st.text_input(
722
  "Material Name",
723
+ value=editor_state["name"],
724
+ help="Enter a unique name for the material.",
725
+ disabled=is_library
726
  )
727
 
728
  # Create two columns for layout
 
733
  category = st.selectbox(
734
  "Category",
735
  MATERIAL_CATEGORIES,
736
+ index=MATERIAL_CATEGORIES.index(editor_state["category"]) if editor_state["category"] in MATERIAL_CATEGORIES else 0,
737
+ help="Select the material category.",
738
+ disabled=is_library
739
  )
740
 
741
  # Thermal conductivity
 
743
  "Thermal Conductivity (W/m·K)",
744
  min_value=0.001,
745
  max_value=1000.0,
746
+ value=float(editor_state["thermal_conductivity"]),
747
  format="%.3f",
748
+ help="Thermal conductivity in W/m·K. Lower values indicate better insulation.",
749
+ disabled=is_library
750
  )
751
 
752
  # Density
 
754
  "Density (kg/m³)",
755
  min_value=1.0,
756
  max_value=20000.0,
757
+ value=float(editor_state["density"]),
758
  format="%.1f",
759
+ help="Material density in kg/m³.",
760
+ disabled=is_library
761
  )
762
 
763
  with col2:
 
766
  "Specific Heat (J/kg·K)",
767
  min_value=100.0,
768
  max_value=10000.0,
769
+ value=float(editor_state["specific_heat"]),
770
  format="%.1f",
771
+ help="Specific heat capacity in J/kg·K. Higher values indicate better thermal mass.",
772
+ disabled=is_library
773
  )
774
 
775
  # Thickness range
776
+ min_thickness, max_thickness = editor_state["thickness_range"]
777
  thickness_range = st.slider(
778
  "Thickness Range (m)",
779
  min_value=0.001,
780
  max_value=0.5,
781
  value=(float(min_thickness), float(max_thickness)),
782
  format="%.3f",
783
+ help="Minimum and maximum thickness range for this material in meters.",
784
+ disabled=is_library
785
  )
786
 
787
  # Default thickness
 
789
  "Default Thickness (m)",
790
  min_value=float(thickness_range[0]),
791
  max_value=float(thickness_range[1]),
792
+ value=min(max(float(editor_state["default_thickness"]), float(thickness_range[0])), float(thickness_range[1])),
793
  format="%.3f",
794
+ help="Default thickness for this material in meters.",
795
+ disabled=is_library
796
  )
797
 
798
+ # Additional properties
799
  st.subheader("Additional Properties")
800
  col1, col2 = st.columns(2)
801
 
 
805
  "Embodied Carbon (kg CO₂e/m³)",
806
  min_value=0.0,
807
  max_value=10000.0,
808
+ value=float(editor_state["embodied_carbon"]),
809
  format="%.1f",
810
+ help="Embodied carbon in kg CO₂e per cubic meter.",
811
+ disabled=is_library
812
+ )
813
+
814
+ # Absorptivity
815
+ absorptivity = st.number_input(
816
+ "Absorptivity",
817
+ min_value=0.0,
818
+ max_value=1.0,
819
+ value=float(editor_state["absorptivity"]),
820
+ format="%.2f",
821
+ help="Solar radiation absorbed (0-1).",
822
+ disabled=is_library
823
  )
824
 
825
  with col2:
 
828
  "Cost (USD/m³)",
829
  min_value=0.0,
830
  max_value=10000.0,
831
+ value=float(editor_state["cost"]),
832
  format="%.1f",
833
+ help="Material cost in USD per cubic meter.",
834
+ disabled=is_library
835
  )
836
+
837
+ # Emissivity
838
+ emissivity = st.number_input(
839
+ "Emissivity",
840
+ min_value=0.0,
841
+ max_value=1.0,
842
+ value=float(editor_state["emissivity"]),
843
+ format="%.2f",
844
+ help="Ratio of radiation emitted (0-1).",
845
+ disabled=is_library
846
+ )
847
+
848
+ # Colour
849
+ colour = st.selectbox(
850
+ "Colour Category",
851
+ COLOUR_CATEGORIES,
852
+ index=COLOUR_CATEGORIES.index(editor_state["colour"]) if editor_state["colour"] in COLOUR_CATEGORIES else 0,
853
+ help="Colour category affecting absorptivity.",
854
+ disabled=is_library
855
+ )
856
 
857
  # Form submission buttons
858
  col1, col2 = st.columns(2)
859
 
860
  with col1:
861
+ submit_button = st.form_submit_button("Save Material", disabled=is_library)
862
 
863
  with col2:
864
  clear_button = st.form_submit_button("Clear Form")
865
 
866
  # Handle form submission
867
+ if submit_button and not is_library:
868
  # Validate inputs
869
  validation_errors = validate_material(
870
  name, category, thermal_conductivity, density, specific_heat,
871
  thickness_range, default_thickness, embodied_carbon, cost,
872
+ editor_state["edit_mode"], editor_state["original_name"],
873
+ absorptivity, emissivity, colour
874
  )
875
 
876
  if validation_errors:
 
877
  for error in validation_errors:
878
  st.error(error)
879
  else:
 
886
  "thickness_range": list(thickness_range),
887
  "default_thickness": default_thickness,
888
  "embodied_carbon": embodied_carbon,
889
+ "cost": cost,
890
+ "absorptivity": absorptivity,
891
+ "emissivity": emissivity,
892
+ "colour": colour
893
  }
894
 
895
  # Handle edit mode
896
+ if editor_state["edit_mode"]:
897
+ original_name = editor_state["original_name"]
 
 
898
  if original_name != name:
899
  del st.session_state.project_data["materials"]["project"][original_name]
 
 
900
  st.session_state.project_data["materials"]["project"][name] = material_data
901
  st.success(f"Material '{name}' updated successfully.")
902
  logger.info(f"Updated material '{name}' in project")
903
  else:
 
904
  st.session_state.project_data["materials"]["project"][name] = material_data
905
  st.success(f"Material '{name}' added to your project.")
906
  logger.info(f"Added new material '{name}' to project")
907
 
908
  # Reset editor
909
  reset_material_editor()
910
+ st.rerun()
911
 
912
  # Handle clear button
913
  if clear_button:
914
  reset_material_editor()
915
  st.rerun()
916
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
917
  def display_fenestration_editor():
918
  """Display the fenestration editor form."""
919
  with st.form("fenestration_editor_form"):
920
+ editor_state = st.session_state.fenestration_editor
921
+ is_library = editor_state.get("is_library", False)
922
+
923
  # Fenestration name
924
  name = st.text_input(
925
  "Fenestration Name",
926
+ value=editor_state["name"],
927
+ help="Enter a unique name for the fenestration.",
928
+ disabled=is_library
929
  )
930
 
931
  # Create two columns for layout
 
936
  fenestration_type = st.selectbox(
937
  "Type",
938
  FENESTRATION_TYPES,
939
+ index=FENESTRATION_TYPES.index(editor_state["type"]) if editor_state["type"] in FENESTRATION_TYPES else 0,
940
+ help="Select the fenestration type.",
941
+ disabled=is_library
942
  )
943
 
944
  # U-value
 
946
  "U-Value (W/m²·K)",
947
  min_value=0.1,
948
  max_value=10.0,
949
+ value=float(editor_state["u_value"]),
950
  format="%.2f",
951
+ help="U-value in W/m²·K. Lower values indicate better insulation.",
952
+ disabled=is_library
953
  )
954
 
955
  # SHGC
 
957
  "Solar Heat Gain Coefficient (SHGC)",
958
  min_value=0.0,
959
  max_value=1.0,
960
+ value=float(editor_state["shgc"]),
961
  format="%.2f",
962
+ help="Solar Heat Gain Coefficient (0-1). Lower values indicate less solar heat transmission.",
963
+ disabled=is_library
964
  )
965
 
966
  with col2:
 
969
  "Visible Transmittance",
970
  min_value=0.0,
971
  max_value=1.0,
972
+ value=float(editor_state["visible_transmittance"]),
973
  format="%.2f",
974
+ help="Visible Transmittance (0-1). Higher values indicate more visible light transmission.",
975
+ disabled=is_library
976
  )
977
 
978
  # Thickness
 
980
  "Thickness (m)",
981
  min_value=0.001,
982
  max_value=0.1,
983
+ value=float(editor_state["thickness"]),
984
  format="%.3f",
985
+ help="Thickness in meters.",
986
+ disabled=is_library
987
  )
988
 
989
+ # Additional properties
990
  st.subheader("Additional Properties")
991
  col1, col2 = st.columns(2)
992
 
 
996
  "Embodied Carbon (kg CO₂e/m²)",
997
  min_value=0.0,
998
  max_value=5000.0,
999
+ value=float(editor_state["embodied_carbon"]),
1000
  format="%.1f",
1001
+ help="Embodied carbon in kg CO₂e per square meter.",
1002
+ disabled=is_library
1003
+ )
1004
+
1005
+ # Absorptivity
1006
+ absorptivity = st.number_input(
1007
+ "Absorptivity",
1008
+ min_value=0.0,
1009
+ max_value=1.0,
1010
+ value=float(editor_state["absorptivity"]),
1011
+ format="%.2f",
1012
+ help="Solar radiation absorbed (0-1).",
1013
+ disabled=is_library
1014
  )
1015
 
1016
  with col2:
 
1019
  "Cost (USD/m²)",
1020
  min_value=0.0,
1021
  max_value=5000.0,
1022
+ value=float(editor_state["cost"]),
1023
  format="%.1f",
1024
+ help="Fenestration cost in USD per square meter.",
1025
+ disabled=is_library
1026
+ )
1027
+
1028
+ # Emissivity
1029
+ emissivity = st.number_input(
1030
+ "Emissivity",
1031
+ min_value=0.0,
1032
+ max_value=1.0,
1033
+ value=float(editor_state["emissivity"]),
1034
+ format="%.2f",
1035
+ help="Ratio of radiation emitted (0-1).",
1036
+ disabled=is_library
1037
  )
1038
 
1039
+ # Colour
1040
+ colour = st.selectbox(
1041
+ "Colour Category",
1042
+ COLOUR_CATEGORIES,
1043
+ index=COLOUR_CATEGORIES.index(editor_state["colour"]) if editor_state["colour"] in COLOUR_CATEGORIES else 0,
1044
+ help="Colour category affecting absorptivity.",
1045
+ disabled=is_library
1046
+ )
1047
+
1048
  # Form submission buttons
1049
  col1, col2 = st.columns(2)
1050
 
1051
  with col1:
1052
+ submit_button = st.form_submit_button("Save Fenestration", disabled=is_library)
1053
 
1054
  with col2:
1055
  clear_button = st.form_submit_button("Clear Form")
1056
 
1057
  # Handle form submission
1058
+ if submit_button and not is_library:
1059
  # Validate inputs
1060
  validation_errors = validate_fenestration(
1061
  name, fenestration_type, u_value, shgc, visible_transmittance, thickness,
1062
+ embodied_carbon, cost, editor_state["edit_mode"], editor_state["original_name"],
1063
+ absorptivity, emissivity, colour
1064
  )
1065
 
1066
  if validation_errors:
 
1067
  for error in validation_errors:
1068
  st.error(error)
1069
  else:
 
1075
  "visible_transmittance": visible_transmittance,
1076
  "thickness": thickness,
1077
  "embodied_carbon": embodied_carbon,
1078
+ "cost": cost,
1079
+ "absorptivity": absorptivity,
1080
+ "emissivity": emissivity,
1081
+ "colour": colour
1082
  }
1083
 
1084
  # Handle edit mode
1085
+ if editor_state["edit_mode"]:
1086
+ original_name = editor_state["original_name"]
 
 
1087
  if original_name != name:
1088
  del st.session_state.project_data["fenestrations"]["project"][original_name]
 
 
1089
  st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1090
  st.success(f"Fenestration '{name}' updated successfully.")
1091
  logger.info(f"Updated fenestration '{name}' in project")
1092
  else:
 
1093
  st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1094
  st.success(f"Fenestration '{name}' added to your project.")
1095
  logger.info(f"Added new fenestration '{name}' to project")
1096
 
1097
  # Reset editor
1098
  reset_fenestration_editor()
1099
+ st.rerun()
1100
 
1101
  # Handle clear button
1102
  if clear_button:
 
1106
  def validate_material(
1107
  name: str, category: str, thermal_conductivity: float, density: float,
1108
  specific_heat: float, thickness_range: Tuple[float, float], default_thickness: float,
1109
+ embodied_carbon: float, cost: float, edit_mode: bool, original_name: str,
1110
+ absorptivity: float, emissivity: float, colour: str
1111
  ) -> List[str]:
1112
  """
1113
  Validate material inputs.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1114
  """
1115
  errors = []
1116
 
 
1118
  if not name or name.strip() == "":
1119
  errors.append("Material name is required.")
1120
 
1121
+ # Check for name uniqueness
1122
  if not edit_mode or (edit_mode and name != original_name):
1123
  if name in st.session_state.project_data["materials"]["project"]:
1124
  errors.append(f"Material name '{name}' already exists in your project.")
 
1157
  if cost < 0:
1158
  errors.append("Cost cannot be negative.")
1159
 
1160
+ # Validate absorptivity
1161
+ if absorptivity < 0 or absorptivity > 1:
1162
+ errors.append("Absorptivity must be between 0 and 1.")
1163
+
1164
+ # Validate emissivity
1165
+ if emissivity < 0 or emissivity > 1:
1166
+ errors.append("Emissivity must be between 0 and 1.")
1167
+
1168
+ # Validate colour
1169
+ if colour not in COLOUR_CATEGORIES:
1170
+ errors.append("Please select a valid colour category.")
1171
+
1172
+ # Validate absorptivity based on colour
1173
+ colour_ranges = {
1174
+ "Light": (0.30, 0.50),
1175
+ "Medium": (0.60, 0.70),
1176
+ "Dark": (0.85, 0.95),
1177
+ "Reflective": (0.10, 0.20)
1178
+ }
1179
+ min_abs, max_abs = colour_ranges[colour]
1180
+ if not (min_abs <= absorptivity <= max_abs):
1181
+ errors.append(f"Absorptivity for {colour} colour must be between {min_abs} and {max_abs}.")
1182
+
1183
  return errors
1184
 
1185
  def validate_fenestration(
1186
  name: str, fenestration_type: str, u_value: float, shgc: float,
1187
  visible_transmittance: float, thickness: float, embodied_carbon: float,
1188
+ cost: float, edit_mode: bool, original_name: str,
1189
+ absorptivity: float, emissivity: float, colour: str
1190
  ) -> List[str]:
1191
  """
1192
  Validate fenestration inputs.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1193
  """
1194
  errors = []
1195
 
 
1197
  if not name or name.strip() == "":
1198
  errors.append("Fenestration name is required.")
1199
 
1200
+ # Check for name uniqueness
1201
  if not edit_mode or (edit_mode and name != original_name):
1202
  if name in st.session_state.project_data["fenestrations"]["project"]:
1203
  errors.append(f"Fenestration name '{name}' already exists in your project.")
 
1230
  if cost < 0:
1231
  errors.append("Cost cannot be negative.")
1232
 
1233
+ # Validate absorptivity
1234
+ if absorptivity < 0 or absorptivity > 1:
1235
+ errors.append("Absorptivity must be between 0 and 1.")
1236
+
1237
+ # Validate emissivity
1238
+ if emissivity < 0 or emissivity > 1:
1239
+ errors.append("Emissivity must be between 0 and 1.")
1240
+
1241
+ # Validate colour
1242
+ if colour not in COLOUR_CATEGORIES:
1243
+ errors.append("Please select a valid colour category.")
1244
+
1245
+ # Validate absorptivity based on colour
1246
+ colour_ranges = {
1247
+ "Light": (0.30, 0.50),
1248
+ "Medium": (0.60, 0.70),
1249
+ "Dark": (0.85, 0.95),
1250
+ "Reflective": (0.10, 0.20)
1251
+ }
1252
+ min_abs, max_abs = colour_ranges[colour]
1253
+ if not (min_abs <= absorptivity <= max_abs):
1254
+ errors.append(f"Absorptivity for {colour} colour must be between {min_abs} and {max_abs}.")
1255
+
1256
  return errors
1257
 
1258
  def reset_material_editor():
 
1267
  "default_thickness": 0.05,
1268
  "embodied_carbon": 100.0,
1269
  "cost": 100.0,
1270
+ "absorptivity": 0.60,
1271
+ "emissivity": 0.90,
1272
+ "colour": "Medium",
1273
  "edit_mode": False,
1274
+ "original_name": "",
1275
+ "is_library": False
1276
  }
1277
 
1278
  def reset_fenestration_editor():
 
1286
  "thickness": 0.024,
1287
  "embodied_carbon": 900.0,
1288
  "cost": 200.0,
1289
+ "absorptivity": 0.15,
1290
+ "emissivity": 0.84,
1291
+ "colour": "Reflective",
1292
  "edit_mode": False,
1293
+ "original_name": "",
1294
+ "is_library": False
1295
  }
1296
 
1297
  def check_material_in_use(material_name: str) -> bool:
1298
  """
1299
  Check if a material is in use in any constructions.
 
 
 
 
 
 
1300
  """
 
 
1301
  return False
1302
 
1303
  def check_fenestration_in_use(fenestration_name: str) -> bool:
1304
  """
1305
  Check if a fenestration is in use in any components.
 
 
 
 
 
 
1306
  """
 
 
1307
  return False
1308
 
1309
  def display_materials_help():
 
1315
 
1316
  **Materials Tab:**
1317
 
1318
+ * **Library Materials**: Pre-defined materials with standard thermal properties. Use 'Preview' to view details or 'Copy' to add to your project.
1319
+ * **Project Materials**: Materials you've added to your project. Use 'Edit' to modify or 'Delete' to remove.
1320
+ * **Material Editor/Creator**: Create new materials or edit existing project materials. Library materials can only be previewed.
1321
 
1322
  **Fenestrations Tab:**
1323
 
1324
+ * **Library Fenestrations**: Pre-defined windows, doors, and skylights. Use 'Preview' to view details or 'Copy' to add to your project.
1325
+ * **Project Fenestrations**: Fenestrations you've added to your project. Use 'Edit' to modify or 'Delete' to remove.
1326
+ * **Fenestration Editor/Creator**: Create new fenestrations or edit existing project fenestrations. Library fenestrations can only be previewed.
1327
 
1328
  **Key Properties:**
1329
 
 
1335
  * **Visible Transmittance**: Fraction of visible light that passes through a fenestration.
1336
  * **Embodied Carbon**: Carbon emissions associated with material production, measured in kg CO₂e per unit volume or area.
1337
  * **Cost**: Material cost in USD per unit volume or area.
1338
+ * **Absorptivity**: Fraction of solar radiation absorbed (0-1). Depends on colour category.
1339
+ * **Emissivity**: Ratio of radiation emitted by the material (0-1).
1340
+ * **Colour**: Category affecting absorptivity (Light, Medium, Dark, Reflective).
1341
 
1342
  **Workflow:**
1343
 
1344
  1. Browse the library materials and fenestrations
1345
+ 2. Copy items to your project or create custom ones
1346
  3. Edit properties as needed for your specific project
1347
  4. Continue to the Construction page to create assemblies using these materials
1348
+ """)