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

Update app/materials_library.py

Browse files
Files changed (1) hide show
  1. app/materials_library.py +210 -242
app/materials_library.py CHANGED
@@ -332,9 +332,15 @@ def calculate_u_value(thermal_conductivity: float, thickness: float) -> float:
332
  r_total = R_SI + r_material + R_SE
333
  return 1 / r_total if r_total > 0 else 0.0
334
 
335
- def calculate_thermal_mass(density: float, specific_heat: float, thickness: float) -> float:
336
- """Calculate areal thermal mass for a material."""
337
- return density * specific_heat * thickness
 
 
 
 
 
 
338
 
339
  def display_materials_page():
340
  """
@@ -396,7 +402,7 @@ def display_materials_tab():
396
  if filtered_materials:
397
  cols = st.columns([2, 1, 1, 1, 1])
398
  cols[0].write("**Name**")
399
- cols[1].write("**Thermal Mass (J/m²·K)**")
400
  cols[2].write("**U-Value (W/m²·K)**")
401
  cols[3].write("**Preview**")
402
  cols[4].write("**Copy**")
@@ -407,43 +413,38 @@ def display_materials_tab():
407
  thermal_mass = calculate_thermal_mass(material["density"], material["specific_heat"], material["default_thickness"])
408
  u_value = calculate_u_value(material["thermal_conductivity"], material["default_thickness"])
409
  cols[0].write(name)
410
- cols[1].write(f"{thermal_mass:.1f}")
411
  cols[2].write(f"{u_value:.3f}")
412
- action_id = f"preview_lib_mat_{name}_{uuid.uuid4()}"
413
- if cols[3].button("Preview", key=action_id):
414
- if st.session_state.get("material_action_id") != action_id:
415
- st.session_state.material_action_id = action_id
416
- st.session_state.material_editor = {
417
- "name": name,
418
- "category": material["category"],
419
- "thermal_conductivity": material["thermal_conductivity"],
420
- "density": material["density"],
421
- "specific_heat": material["specific_heat"],
422
- "default_thickness": material["default_thickness"],
423
- "embodied_carbon": material["embodied_carbon"],
424
- "cost": material["cost"],
425
- "absorptivity": material["absorptivity"],
426
- "emissivity": material["emissivity"],
427
- "colour": material["colour"],
428
- "edit_mode": False,
429
- "original_name": name,
430
- "is_library": True
431
- }
432
- st.session_state.active_tab = "Materials"
433
- st.success(f"Previewing material '{name}'")
434
- action_id = f"copy_lib_mat_{name}_{uuid.uuid4()}"
435
- if cols[4].button("Copy", key=action_id):
436
- if st.session_state.get("material_action_id") != action_id:
437
- st.session_state.material_action_id = action_id
438
- new_name = f"{name}_Project"
439
- counter = 1
440
- while new_name in st.session_state.project_data["materials"]["project"] or new_name in library_materials:
441
- new_name = f"{name}_Project_{counter}"
442
- counter += 1
443
- st.session_state.project_data["materials"]["project"][new_name] = material.copy()
444
- st.success(f"Material '{new_name}' copied to project.")
445
- logger.info(f"Copied library material '{name}' as '{new_name}' to project")
446
- st.rerun()
447
  else:
448
  st.info("No materials found in the selected category.")
449
 
@@ -461,7 +462,7 @@ def display_materials_tab():
461
  if filtered_project_materials:
462
  cols = st.columns([2, 1, 1, 1, 1])
463
  cols[0].write("**Name**")
464
- cols[1].write("**Thermal Mass (J/m²·K)**")
465
  cols[2].write("**U-Value (W/m²·K)**")
466
  cols[3].write("**Edit**")
467
  cols[4].write("**Delete**")
@@ -472,42 +473,37 @@ def display_materials_tab():
472
  thermal_mass = calculate_thermal_mass(material["density"], material["specific_heat"], material["default_thickness"])
473
  u_value = calculate_u_value(material["thermal_conductivity"], material["default_thickness"])
474
  cols[0].write(name)
475
- cols[1].write(f"{thermal_mass:.1f}")
476
  cols[2].write(f"{u_value:.3f}")
477
- action_id = f"edit_proj_mat_{name}_{uuid.uuid4()}"
478
- if cols[3].button("Edit", key=action_id):
479
- if st.session_state.get("material_action_id") != action_id:
480
- st.session_state.material_action_id = action_id
481
- st.session_state.material_editor = {
482
- "name": name,
483
- "category": material["category"],
484
- "thermal_conductivity": material["thermal_conductivity"],
485
- "density": material["density"],
486
- "specific_heat": material["specific_heat"],
487
- "default_thickness": material["default_thickness"],
488
- "embodied_carbon": material["embodied_carbon"],
489
- "cost": material["cost"],
490
- "absorptivity": material["absorptivity"],
491
- "emissivity": material["emissivity"],
492
- "colour": material["colour"],
493
- "edit_mode": True,
494
- "original_name": name,
495
- "is_library": False
496
- }
497
- st.session_state.active_tab = "Materials"
498
- st.success(f"Editing material '{name}'")
499
- action_id = f"delete_proj_mat_{name}_{uuid.uuid4()}"
500
- if cols[4].button("Delete", key=action_id):
501
- if st.session_state.get("material_action_id") != action_id:
502
- st.session_state.material_action_id = action_id
503
- is_in_use = check_material_in_use(name)
504
- if is_in_use:
505
- st.error(f"Cannot delete material '{name}' because it is in use in constructions.")
506
- else:
507
- del st.session_state.project_data["materials"]["project"][name]
508
- st.success(f"Material '{name}' deleted from project.")
509
- logger.info(f"Deleted material '{name}' from project")
510
- st.rerun()
511
  else:
512
  st.info("No project materials in the selected category.")
513
 
@@ -531,7 +527,7 @@ def display_materials_tab():
531
  "Absorptivity": props["absorptivity"],
532
  "Emissivity": props["emissivity"],
533
  "Colour": props["colour"],
534
- "Thermal Mass (J/m²·K)": thermal_mass,
535
  "U-Value (W/m²·K)": u_value
536
  })
537
  df = pd.DataFrame(data)
@@ -581,38 +577,33 @@ def display_fenestrations_tab():
581
  cols[0].write(name)
582
  cols[1].write(fenestration["type"])
583
  cols[2].write(f"{fenestration['u_value']:.2f}")
584
- action_id = f"preview_lib_fen_{name}_{uuid.uuid4()}"
585
- if cols[3].button("Preview", key=action_id):
586
- if st.session_state.get("fenestration_action_id") != action_id:
587
- st.session_state.fenestration_action_id = action_id
588
- st.session_state.fenestration_editor = {
589
- "name": name,
590
- "type": fenestration["type"],
591
- "u_value": fenestration["u_value"],
592
- "shgc": fenestration["shgc"],
593
- "visible_transmittance": fenestration["visible_transmittance"],
594
- "thickness": fenestration["thickness"],
595
- "embodied_carbon": fenestration["embodied_carbon"],
596
- "cost": fenestration["cost"],
597
- "edit_mode": False,
598
- "original_name": name,
599
- "is_library": True
600
- }
601
- st.session_state.active_tab = "Fenestrations"
602
- st.success(f"Previewing fenestration '{name}'")
603
- action_id = f"copy_lib_fen_{name}_{uuid.uuid4()}"
604
- if cols[4].button("Copy", key=action_id):
605
- if st.session_state.get("fenestration_action_id") != action_id:
606
- st.session_state.fenestration_action_id = action_id
607
- new_name = f"{name}_Project"
608
- counter = 1
609
- while new_name in st.session_state.project_data["fenestrations"]["project"] or new_name in library_fenestrations:
610
- new_name = f"{name}_Project_{counter}"
611
- counter += 1
612
- st.session_state.project_data["fenestrations"]["project"][new_name] = fenestration.copy()
613
- st.success(f"Fenestration '{new_name}' copied to project.")
614
- logger.info(f"Copied library fenestration '{name}' as '{new_name}' to project")
615
- st.rerun()
616
  else:
617
  st.info("No fenestrations found in the selected type.")
618
 
@@ -641,37 +632,32 @@ def display_fenestrations_tab():
641
  cols[0].write(name)
642
  cols[1].write(fenestration["type"])
643
  cols[2].write(f"{fenestration['u_value']:.2f}")
644
- action_id = f"edit_proj_fen_{name}_{uuid.uuid4()}"
645
- if cols[3].button("Edit", key=action_id):
646
- if st.session_state.get("fenestration_action_id") != action_id:
647
- st.session_state.fenestration_action_id = action_id
648
- st.session_state.fenestration_editor = {
649
- "name": name,
650
- "type": fenestration["type"],
651
- "u_value": fenestration["u_value"],
652
- "shgc": fenestration["shgc"],
653
- "visible_transmittance": fenestration["visible_transmittance"],
654
- "thickness": fenestration["thickness"],
655
- "embodied_carbon": fenestration["embodied_carbon"],
656
- "cost": fenestration["cost"],
657
- "edit_mode": True,
658
- "original_name": name,
659
- "is_library": False
660
- }
661
- st.session_state.active_tab = "Fenestrations"
662
- st.success(f"Editing fenestration '{name}'")
663
- action_id = f"delete_proj_fen_{name}_{uuid.uuid4()}"
664
- if cols[4].button("Delete", key=action_id):
665
- if st.session_state.get("fenestration_action_id") != action_id:
666
- st.session_state.fenestration_action_id = action_id
667
- is_in_use = check_fenestration_in_use(name)
668
- if is_in_use:
669
- st.error(f"Cannot delete fenestration '{name}' because it is in use in components.")
670
- else:
671
- del st.session_state.project_data["fenestrations"]["project"][name]
672
- st.success(f"Fenestration '{name}' deleted from project.")
673
- logger.info(f"Deleted fenestration '{name}' from project")
674
- st.rerun()
675
  else:
676
  st.info("No project fenestrations in the selected type.")
677
 
@@ -732,8 +718,6 @@ def initialize_materials():
732
  "original_name": "",
733
  "is_library": False
734
  }
735
- if "material_action_id" not in st.session_state:
736
- st.session_state.material_action_id = None
737
 
738
  def initialize_fenestrations():
739
  """Initialize fenestrations in session state if not present."""
@@ -763,8 +747,6 @@ def initialize_fenestrations():
763
  "original_name": "",
764
  "is_library": False
765
  }
766
- if "fenestration_action_id" not in st.session_state:
767
- st.session_state.fenestration_action_id = None
768
 
769
  def display_material_editor():
770
  """Display the material editor form."""
@@ -913,59 +895,53 @@ def display_material_editor():
913
 
914
  # Handle form submission
915
  if submit_button and not is_library:
916
- action_id = str(uuid.uuid4())
917
- if st.session_state.get("material_action_id") != action_id:
918
- st.session_state.material_action_id = action_id
919
- # Validate inputs
920
- validation_errors = validate_material(
921
- name, category, thermal_conductivity, density, specific_heat,
922
- default_thickness, embodied_carbon, cost,
923
- editor_state["edit_mode"], editor_state["original_name"],
924
- absorptivity, emissivity, colour
925
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
926
 
927
- if validation_errors:
928
- for error in validation_errors:
929
- st.error(error)
 
 
 
 
 
930
  else:
931
- # Create material data
932
- material_data = {
933
- "category": category,
934
- "thermal_conductivity": thermal_conductivity,
935
- "density": density,
936
- "specific_heat": specific_heat,
937
- "default_thickness": default_thickness,
938
- "embodied_carbon": embodied_carbon,
939
- "cost": cost,
940
- "absorptivity": absorptivity,
941
- "emissivity": emissivity,
942
- "colour": colour
943
- }
944
-
945
- # Handle edit mode
946
- if editor_state["edit_mode"]:
947
- original_name = editor_state["original_name"]
948
- if original_name != name:
949
- del st.session_state.project_data["materials"]["project"][original_name]
950
- st.session_state.project_data["materials"]["project"][name] = material_data
951
- st.success(f"Material '{name}' updated successfully.")
952
- logger.info(f"Updated material '{name}' in project")
953
- else:
954
- st.session_state.project_data["materials"]["project"][name] = material_data
955
- st.success(f"Material '{name}' added to your project.")
956
- logger.info(f"Added new material '{name}' to project")
957
-
958
- # Reset editor
959
- reset_material_editor()
960
- st.rerun()
961
 
962
  # Handle clear button
963
  if clear_button:
964
- action_id = str(uuid.uuid4())
965
- if st.session_state.get("material_action_id") != action_id:
966
- st.session_state.material_action_id = action_id
967
- reset_material_editor()
968
- st.rerun()
969
 
970
  def display_fenestration_editor():
971
  """Display the fenestration editor form."""
@@ -1078,54 +1054,48 @@ def display_fenestration_editor():
1078
 
1079
  # Handle form submission
1080
  if submit_button and not is_library:
1081
- action_id = str(uuid.uuid4())
1082
- if st.session_state.get("fenestration_action_id") != action_id:
1083
- st.session_state.fenestration_action_id = action_id
1084
- # Validate inputs
1085
- validation_errors = validate_fenestration(
1086
- name, fenestration_type, u_value, shgc, visible_transmittance, thickness,
1087
- embodied_carbon, cost, editor_state["edit_mode"], editor_state["original_name"]
1088
- )
 
 
 
 
 
 
 
 
 
 
 
 
1089
 
1090
- if validation_errors:
1091
- for error in validation_errors:
1092
- st.error(error)
 
 
 
 
 
1093
  else:
1094
- # Create fenestration data
1095
- fenestration_data = {
1096
- "type": fenestration_type,
1097
- "u_value": u_value,
1098
- "shgc": shgc,
1099
- "visible_transmittance": visible_transmittance,
1100
- "thickness": thickness,
1101
- "embodied_carbon": embodied_carbon,
1102
- "cost": cost
1103
- }
1104
-
1105
- # Handle edit mode
1106
- if editor_state["edit_mode"]:
1107
- original_name = editor_state["original_name"]
1108
- if original_name != name:
1109
- del st.session_state.project_data["fenestrations"]["project"][original_name]
1110
- st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1111
- st.success(f"Fenestration '{name}' updated successfully.")
1112
- logger.info(f"Updated fenestration '{name}' in project")
1113
- else:
1114
- st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1115
- st.success(f"Fenestration '{name}' added to your project.")
1116
- logger.info(f"Added new fenestration '{name}' to project")
1117
-
1118
- # Reset editor
1119
- reset_fenestration_editor()
1120
- st.rerun()
1121
 
1122
  # Handle clear button
1123
  if clear_button:
1124
- action_id = str(uuid.uuid4())
1125
- if st.session_state.get("fenestration_action_id") != action_id:
1126
- st.session_state.fenestration_action_id = action_id
1127
- reset_fenestration_editor()
1128
- st.rerun()
1129
 
1130
  def validate_material(
1131
  name: str, category: str, thermal_conductivity: float, density: float,
@@ -1144,8 +1114,8 @@ def validate_material(
1144
 
1145
  # Check for name uniqueness
1146
  if not edit_mode or (edit_mode and name != original_name):
1147
- if name in st.session_state.project_data["materials"]["project"]:
1148
- errors.append(f"Material name '{name}' already exists in your project.")
1149
 
1150
  # Validate category
1151
  if category not in MATERIAL_CATEGORIES:
@@ -1216,8 +1186,8 @@ def validate_fenestration(
1216
 
1217
  # Check for name uniqueness
1218
  if not edit_mode or (edit_mode and name != original_name):
1219
- if name in st.session_state.project_data["fenestrations"]["project"]:
1220
- errors.append(f"Fenestration name '{name}' already exists in your project.")
1221
 
1222
  # Validate type
1223
  if fenestration_type not in FENESTRATION_TYPES:
@@ -1267,7 +1237,6 @@ def reset_material_editor():
1267
  "original_name": "",
1268
  "is_library": False
1269
  }
1270
- st.session_state.material_action_id = None
1271
 
1272
  def reset_fenestration_editor():
1273
  """Reset the fenestration editor to default values."""
@@ -1284,7 +1253,6 @@ def reset_fenestration_editor():
1284
  "original_name": "",
1285
  "is_library": False
1286
  }
1287
- st.session_state.fenestration_action_id = None
1288
 
1289
  def check_material_in_use(material_name: str) -> bool:
1290
  """
@@ -1322,7 +1290,7 @@ def display_materials_help():
1322
  * **Thermal Conductivity (W/m·K)**: Rate of heat transfer through a material. Lower values indicate better insulation.
1323
  * **Density (kg/m³)**: Mass per unit volume.
1324
  * **Specific Heat (J/kg·K)**: Energy required to raise the temperature of 1 kg by 1 K. Higher values indicate better thermal mass.
1325
- * **Thermal Mass (J/m²·K)**: Areal heat capacity (density × specific heat × thickness).
1326
  * **U-Value (W/m²·K)**: Overall heat transfer coefficient, accounting for material and surface resistances. Lower values indicate better insulation.
1327
  * **SHGC**: Solar Heat Gain Coefficient (0-1). Fraction of incident solar radiation that enters through a fenestration.
1328
  * **Visible Transmittance**: Fraction of visible light that passes through a fenestration.
 
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) -> str:
336
+ """Calculate areal thermal mass for a material and return category."""
337
+ thermal_mass = density * specific_heat * thickness
338
+ if thermal_mass < 30000:
339
+ return "Low"
340
+ elif 30000 <= thermal_mass <= 90000:
341
+ return "Medium"
342
+ else:
343
+ return "High"
344
 
345
  def display_materials_page():
346
  """
 
402
  if filtered_materials:
403
  cols = st.columns([2, 1, 1, 1, 1])
404
  cols[0].write("**Name**")
405
+ cols[1].write("**Thermal Mass**")
406
  cols[2].write("**U-Value (W/m²·K)**")
407
  cols[3].write("**Preview**")
408
  cols[4].write("**Copy**")
 
413
  thermal_mass = calculate_thermal_mass(material["density"], material["specific_heat"], material["default_thickness"])
414
  u_value = calculate_u_value(material["thermal_conductivity"], material["default_thickness"])
415
  cols[0].write(name)
416
+ cols[1].write(thermal_mass)
417
  cols[2].write(f"{u_value:.3f}")
418
+ if cols[3].button("Preview", key=f"preview_lib_mat_{name}"):
419
+ st.session_state.material_editor = {
420
+ "name": name,
421
+ "category": material["category"],
422
+ "thermal_conductivity": material["thermal_conductivity"],
423
+ "density": material["density"],
424
+ "specific_heat": material["specific_heat"],
425
+ "default_thickness": material["default_thickness"],
426
+ "embodied_carbon": material["embodied_carbon"],
427
+ "cost": material["cost"],
428
+ "absorptivity": material["absorptivity"],
429
+ "emissivity": material["emissivity"],
430
+ "colour": material["colour"],
431
+ "edit_mode": False,
432
+ "original_name": name,
433
+ "is_library": True
434
+ }
435
+ st.session_state.active_tab = "Materials"
436
+ st.success(f"Previewing material '{name}'")
437
+ logger.info(f"Previewed library material '{name}'")
438
+ if cols[4].button("Copy", key=f"copy_lib_mat_{name}"):
439
+ new_name = f"{name}_Project"
440
+ counter = 1
441
+ while new_name in st.session_state.project_data["materials"]["project"] or new_name in library_materials:
442
+ new_name = f"{name}_Project_{counter}"
443
+ counter += 1
444
+ st.session_state.project_data["materials"]["project"][new_name] = material.copy()
445
+ st.success(f"Material '{new_name}' copied to project.")
446
+ logger.info(f"Copied library material '{name}' as '{new_name}' to project")
447
+ st.rerun()
 
 
 
 
 
448
  else:
449
  st.info("No materials found in the selected category.")
450
 
 
462
  if filtered_project_materials:
463
  cols = st.columns([2, 1, 1, 1, 1])
464
  cols[0].write("**Name**")
465
+ cols[1].write("**Thermal Mass**")
466
  cols[2].write("**U-Value (W/m²·K)**")
467
  cols[3].write("**Edit**")
468
  cols[4].write("**Delete**")
 
473
  thermal_mass = calculate_thermal_mass(material["density"], material["specific_heat"], material["default_thickness"])
474
  u_value = calculate_u_value(material["thermal_conductivity"], material["default_thickness"])
475
  cols[0].write(name)
476
+ cols[1].write(thermal_mass)
477
  cols[2].write(f"{u_value:.3f}")
478
+ if cols[3].button("Edit", key=f"edit_proj_mat_{name}"):
479
+ st.session_state.material_editor = {
480
+ "name": name,
481
+ "category": material["category"],
482
+ "thermal_conductivity": material["thermal_conductivity"],
483
+ "density": material["density"],
484
+ "specific_heat": material["specific_heat"],
485
+ "default_thickness": material["default_thickness"],
486
+ "embodied_carbon": material["embodied_carbon"],
487
+ "cost": material["cost"],
488
+ "absorptivity": material["absorptivity"],
489
+ "emissivity": material["emissivity"],
490
+ "colour": material["colour"],
491
+ "edit_mode": True,
492
+ "original_name": name,
493
+ "is_library": False
494
+ }
495
+ st.session_state.active_tab = "Materials"
496
+ st.success(f"Editing material '{name}'")
497
+ logger.info(f"Editing project material '{name}'")
498
+ if cols[4].button("Delete", key=f"delete_proj_mat_{name}"):
499
+ is_in_use = check_material_in_use(name)
500
+ if is_in_use:
501
+ st.error(f"Cannot delete material '{name}' because it is in use in constructions.")
502
+ else:
503
+ del st.session_state.project_data["materials"]["project"][name]
504
+ st.success(f"Material '{name}' deleted from project.")
505
+ logger.info(f"Deleted material '{name}' from project")
506
+ st.rerun()
 
 
 
 
 
507
  else:
508
  st.info("No project materials in the selected category.")
509
 
 
527
  "Absorptivity": props["absorptivity"],
528
  "Emissivity": props["emissivity"],
529
  "Colour": props["colour"],
530
+ "Thermal Mass": thermal_mass,
531
  "U-Value (W/m²·K)": u_value
532
  })
533
  df = pd.DataFrame(data)
 
577
  cols[0].write(name)
578
  cols[1].write(fenestration["type"])
579
  cols[2].write(f"{fenestration['u_value']:.2f}")
580
+ if cols[3].button("Preview", key=f"preview_lib_fen_{name}"):
581
+ st.session_state.fenestration_editor = {
582
+ "name": name,
583
+ "type": fenestration["type"],
584
+ "u_value": fenestration["u_value"],
585
+ "shgc": fenestration["shgc"],
586
+ "visible_transmittance": fenestration["visible_transmittance"],
587
+ "thickness": fenestration["thickness"],
588
+ "embodied_carbon": fenestration["embodied_carbon"],
589
+ "cost": fenestration["cost"],
590
+ "edit_mode": False,
591
+ "original_name": name,
592
+ "is_library": True
593
+ }
594
+ st.session_state.active_tab = "Fenestrations"
595
+ st.success(f"Previewing fenestration '{name}'")
596
+ logger.info(f"Previewed library fenestration '{name}'")
597
+ if cols[4].button("Copy", key=f"copy_lib_fen_{name}"):
598
+ new_name = f"{name}_Project"
599
+ counter = 1
600
+ while new_name in st.session_state.project_data["fenestrations"]["project"] or new_name in library_fenestrations:
601
+ new_name = f"{name}_Project_{counter}"
602
+ counter += 1
603
+ st.session_state.project_data["fenestrations"]["project"][new_name] = fenestration.copy()
604
+ st.success(f"Fenestration '{new_name}' copied to project.")
605
+ logger.info(f"Copied library fenestration '{name}' as '{new_name}' to project")
606
+ st.rerun()
 
 
 
 
 
607
  else:
608
  st.info("No fenestrations found in the selected type.")
609
 
 
632
  cols[0].write(name)
633
  cols[1].write(fenestration["type"])
634
  cols[2].write(f"{fenestration['u_value']:.2f}")
635
+ if cols[3].button("Edit", key=f"edit_proj_fen_{name}"):
636
+ st.session_state.fenestration_editor = {
637
+ "name": name,
638
+ "type": fenestration["type"],
639
+ "u_value": fenestration["u_value"],
640
+ "shgc": fenestration["shgc"],
641
+ "visible_transmittance": fenestration["visible_transmittance"],
642
+ "thickness": fenestration["thickness"],
643
+ "embodied_carbon": fenestration["embodied_carbon"],
644
+ "cost": fenestration["cost"],
645
+ "edit_mode": True,
646
+ "original_name": name,
647
+ "is_library": False
648
+ }
649
+ st.session_state.active_tab = "Fenestrations"
650
+ st.success(f"Editing fenestration '{name}'")
651
+ logger.info(f"Editing project fenestration '{name}'")
652
+ if cols[4].button("Delete", key=f"delete_proj_fen_{name}"):
653
+ is_in_use = check_fenestration_in_use(name)
654
+ if is_in_use:
655
+ st.error(f"Cannot delete fenestration '{name}' because it is in use in components.")
656
+ else:
657
+ del st.session_state.project_data["fenestrations"]["project"][name]
658
+ st.success(f"Fenestration '{name}' deleted from project.")
659
+ logger.info(f"Deleted fenestration '{name}' from project")
660
+ st.rerun()
 
 
 
 
 
661
  else:
662
  st.info("No project fenestrations in the selected type.")
663
 
 
718
  "original_name": "",
719
  "is_library": False
720
  }
 
 
721
 
722
  def initialize_fenestrations():
723
  """Initialize fenestrations in session state if not present."""
 
747
  "original_name": "",
748
  "is_library": False
749
  }
 
 
750
 
751
  def display_material_editor():
752
  """Display the material editor form."""
 
895
 
896
  # Handle form submission
897
  if submit_button and not is_library:
898
+ # Validate inputs
899
+ validation_errors = validate_material(
900
+ name, category, thermal_conductivity, density, specific_heat,
901
+ default_thickness, embodied_carbon, cost,
902
+ editor_state["edit_mode"], editor_state["original_name"],
903
+ absorptivity, emissivity, colour
904
+ )
905
+
906
+ if validation_errors:
907
+ for error in validation_errors:
908
+ st.error(error)
909
+ else:
910
+ # Create material data
911
+ material_data = {
912
+ "category": category,
913
+ "thermal_conductivity": thermal_conductivity,
914
+ "density": density,
915
+ "specific_heat": specific_heat,
916
+ "default_thickness": default_thickness,
917
+ "embodied_carbon": embodied_carbon,
918
+ "cost": cost,
919
+ "absorptivity": absorptivity,
920
+ "emissivity": emissivity,
921
+ "colour": colour
922
+ }
923
 
924
+ # Handle edit mode
925
+ if editor_state["edit_mode"]:
926
+ original_name = editor_state["original_name"]
927
+ if original_name != name:
928
+ del st.session_state.project_data["materials"]["project"][original_name]
929
+ st.session_state.project_data["materials"]["project"][name] = material_data
930
+ st.success(f"Material '{name}' updated successfully.")
931
+ logger.info(f"Updated material '{name}' in project")
932
  else:
933
+ st.session_state.project_data["materials"]["project"][name] = material_data
934
+ st.success(f"Material '{name}' added to your project.")
935
+ logger.info(f"Added new material '{name}' to project")
936
+
937
+ # Reset editor
938
+ reset_material_editor()
939
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
940
 
941
  # Handle clear button
942
  if clear_button:
943
+ reset_material_editor()
944
+ st.rerun()
 
 
 
945
 
946
  def display_fenestration_editor():
947
  """Display the fenestration editor form."""
 
1054
 
1055
  # Handle form submission
1056
  if submit_button and not is_library:
1057
+ # Validate inputs
1058
+ validation_errors = validate_fenestration(
1059
+ name, fenestration_type, u_value, shgc, visible_transmittance, thickness,
1060
+ embodied_carbon, cost, editor_state["edit_mode"], editor_state["original_name"]
1061
+ )
1062
+
1063
+ if validation_errors:
1064
+ for error in validation_errors:
1065
+ st.error(error)
1066
+ else:
1067
+ # Create fenestration data
1068
+ fenestration_data = {
1069
+ "type": fenestration_type,
1070
+ "u_value": u_value,
1071
+ "shgc": shgc,
1072
+ "visible_transmittance": visible_transmittance,
1073
+ "thickness": thickness,
1074
+ "embodied_carbon": embodied_carbon,
1075
+ "cost": cost
1076
+ }
1077
 
1078
+ # Handle edit mode
1079
+ if editor_state["edit_mode"]:
1080
+ original_name = editor_state["original_name"]
1081
+ if original_name != name:
1082
+ del st.session_state.project_data["fenestrations"]["project"][original_name]
1083
+ st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1084
+ st.success(f"Fenestration '{name}' updated successfully.")
1085
+ logger.info(f"Updated fenestration '{name}' in project")
1086
  else:
1087
+ st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
1088
+ st.success(f"Fenestration '{name}' added to your project.")
1089
+ logger.info(f"Added new fenestration '{name}' to project")
1090
+
1091
+ # Reset editor
1092
+ reset_fenestration_editor()
1093
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1094
 
1095
  # Handle clear button
1096
  if clear_button:
1097
+ reset_fenestration_editor()
1098
+ st.rerun()
 
 
 
1099
 
1100
  def validate_material(
1101
  name: str, category: str, thermal_conductivity: float, density: float,
 
1114
 
1115
  # Check for name uniqueness
1116
  if not edit_mode or (edit_mode and name != original_name):
1117
+ if name in st.session_state.project_data["materials"]["project"] or name in st.session_state.project_data["materials"]["library"]:
1118
+ errors.append(f"Material name '{name}' already exists in your project or library.")
1119
 
1120
  # Validate category
1121
  if category not in MATERIAL_CATEGORIES:
 
1186
 
1187
  # Check for name uniqueness
1188
  if not edit_mode or (edit_mode and name != original_name):
1189
+ if name in st.session_state.project_data["fenestrations"]["project"] or name in st.session_state.project_data["fenestrations"]["library"]:
1190
+ errors.append(f"Fenestration name '{name}' already exists in your project or library.")
1191
 
1192
  # Validate type
1193
  if fenestration_type not in FENESTRATION_TYPES:
 
1237
  "original_name": "",
1238
  "is_library": False
1239
  }
 
1240
 
1241
  def reset_fenestration_editor():
1242
  """Reset the fenestration editor to default values."""
 
1253
  "original_name": "",
1254
  "is_library": False
1255
  }
 
1256
 
1257
  def check_material_in_use(material_name: str) -> bool:
1258
  """
 
1290
  * **Thermal Conductivity (W/m·K)**: Rate of heat transfer through a material. Lower values indicate better insulation.
1291
  * **Density (kg/m³)**: Mass per unit volume.
1292
  * **Specific Heat (J/kg·K)**: Energy required to raise the temperature of 1 kg by 1 K. Higher values indicate better thermal mass.
1293
+ * **Thermal Mass**: Areal heat capacity, categorized as Low (<30,000 J/m²·K), Medium (30,000–90,000 J/m²·K), or High (>90,000 J/m²·K).
1294
  * **U-Value (W/m²·K)**: Overall heat transfer coefficient, accounting for material and surface resistances. Lower values indicate better insulation.
1295
  * **SHGC**: Solar Heat Gain Coefficient (0-1). Fraction of incident solar radiation that enters through a fenestration.
1296
  * **Visible Transmittance**: Fraction of visible light that passes through a fenestration.