mabuseif commited on
Commit
6feb67a
verified
1 Parent(s): f18c313

Update app/materials_library.py

Browse files
Files changed (1) hide show
  1. app/materials_library.py +74 -71
app/materials_library.py CHANGED
@@ -2,8 +2,8 @@
2
  BuildSustain - Material Library Module
3
 
4
  This module handles the material library functionality of the BuildSustain application,
5
- allowing users to manage building materials and fenestrations (windows, skylights).
6
- It provides both predefined library materials and the ability to create custom materials.
7
 
8
  Developed by: Dr Majed Abuseif, Deakin University
9
  漏 2025
@@ -17,8 +17,6 @@ import logging
17
  import uuid
18
  from typing import Dict, List, Any, Optional, Tuple, Union
19
  from enum import Enum
20
-
21
- # Import default data and constants from centralized module
22
  from app.m_c_data import SAMPLE_MATERIALS, SAMPLE_FENESTRATIONS, DEFAULT_MATERIAL_PROPERTIES, DEFAULT_WINDOW_PROPERTIES
23
 
24
  # Configure logging
@@ -27,27 +25,22 @@ logger = logging.getLogger(__name__)
27
 
28
  # Define constants
29
  MATERIAL_CATEGORIES = [
30
- "Masonry",
31
  "Insulation",
32
- "Metal",
33
- "Wood",
34
  "Finishing",
35
- "Custom"
36
  ]
37
 
38
  FENESTRATION_TYPES = [
39
- "Window",
40
- "Skylight",
41
- "Custom"
42
  ]
43
 
44
  class MaterialCategory(Enum):
45
- MASONRY = "Masonry"
46
  INSULATION = "Insulation"
47
- METAL = "Metal"
48
- WOOD = "Wood"
49
  FINISHING = "Finishing"
50
- CUSTOM = "Custom"
51
 
52
  class Material:
53
  def __init__(self, name: str, category: MaterialCategory, conductivity: float, density: float,
@@ -65,12 +58,19 @@ class Material:
65
  self.emissivity = emissivity
66
  self.is_library = is_library
67
 
68
- def get_thermal_mass(self):
69
- class ThermalMass:
70
- def __init__(self, value): self.value = value
71
- return ThermalMass(self.density * self.specific_heat * self.default_thickness)
 
 
 
 
 
 
72
 
73
- def get_u_value(self):
 
74
  return self.conductivity / self.default_thickness if self.default_thickness > 0 else 0.0
75
 
76
  class GlazingMaterial:
@@ -100,7 +100,8 @@ class MaterialLibrary:
100
  "Embodied Carbon (kgCO鈧俥/kg)": m.embodied_carbon,
101
  "Absorptivity": m.absorptivity,
102
  "Price (USD/m虏)": m.price,
103
- "Emissivity": m.emissivity
 
104
  }
105
  for m in project_materials.values()
106
  ]
@@ -226,7 +227,7 @@ def display_materials_page():
226
  try:
227
  material_library.library_materials[k] = Material(
228
  name=k,
229
- category=MaterialCategory(m["category"]),
230
  conductivity=m["thermal_properties"]["conductivity"],
231
  density=m["thermal_properties"]["density"],
232
  specific_heat=m["thermal_properties"]["specific_heat"],
@@ -243,18 +244,17 @@ def display_materials_page():
243
 
244
  material_library.library_glazing_materials = {}
245
  for k, f in st.session_state.project_data["fenestrations"]["library"].items():
246
- if f["type"] in ["window", "skylight"]:
247
- try:
248
- material_library.library_glazing_materials[k] = GlazingMaterial(
249
- name=k,
250
- shgc=f["performance"]["shgc"],
251
- u_value=f["performance"]["u_value"],
252
- h_o=f.get("h_o", DEFAULT_WINDOW_PROPERTIES["h_o"]),
253
- is_library=True
254
- )
255
- except KeyError as e:
256
- logger.error(f"Error processing fenestration {k}: Missing key {e}")
257
- continue
258
 
259
  with tab1:
260
  display_materials_tab(material_library)
@@ -308,11 +308,11 @@ def display_materials_tab(material_library: MaterialLibrary):
308
  cols[1].write("**Thermal Mass**")
309
  cols[2].write("**U-Value (W/m虏路K)**")
310
  cols[3].write("**Preview**")
311
- cols[4].write("**Copy**")
312
  for material in library_materials:
313
  cols = st.columns([2, 1, 1, 1, 1])
314
  cols[0].write(material.name)
315
- cols[1].write(f"{material.get_thermal_mass().value:.0f}")
316
  cols[2].write(f"{material.get_u_value():.3f}")
317
  if cols[3].button("Preview", key=f"preview_lib_mat_{material.name}"):
318
  if st.session_state.get("rerun_trigger") != f"preview_mat_{material.name}":
@@ -344,7 +344,7 @@ def display_materials_tab(material_library: MaterialLibrary):
344
  "emissivity": material.emissivity
345
  }
346
  st.session_state.active_tab = "Materials"
347
- if cols[4].button("Copy", key=f"copy_lib_mat_{material.name}"):
348
  new_name = f"{material.name}_Project"
349
  counter = 1
350
  while new_name in st.session_state.project_data["materials"]["project"] or new_name in st.session_state.project_data["materials"]["library"]:
@@ -382,9 +382,9 @@ def display_materials_tab(material_library: MaterialLibrary):
382
  for material in st.session_state.project_data["materials"]["project"].values():
383
  cols = st.columns([2, 1, 1, 1, 1])
384
  cols[0].write(material.name)
385
- cols[1].write(f"{material.get_thermal_mass().value:.0f}")
386
  cols[2].write(f"{material.get_u_value():.3f}")
387
- if cols[3].button("Edit", key=f"edit_proj_mat_{material.name}"):
388
  if st.session_state.get("rerun_trigger") != f"edit_mat_{material.name}":
389
  st.session_state.rerun_trigger = f"edit_mat_{material.name}"
390
  st.session_state.material_editor = {
@@ -415,7 +415,7 @@ def display_materials_tab(material_library: MaterialLibrary):
415
  "emissivity": material.emissivity
416
  }
417
  st.session_state.active_tab = "Materials"
418
- if cols[4].button("Delete", key=f"delete_proj_mat_{material.name}"):
419
  success, message = material_library.delete_project_material(
420
  material.name, st.session_state.project_data["materials"]["project"], st.session_state.get("components", {})
421
  )
@@ -459,7 +459,7 @@ def display_materials_tab(material_library: MaterialLibrary):
459
  original_name = editor_state.get("original_name", "")
460
  name = st.text_input(
461
  "Material Name",
462
- value=form_state.get("name", editor_state.get("name", "")),
463
  help="Unique material identifier",
464
  key="material_name_input"
465
  )
@@ -559,7 +559,7 @@ def display_materials_tab(material_library: MaterialLibrary):
559
  try:
560
  new_material = Material(
561
  name=name,
562
- category=MaterialCategory(category),
563
  conductivity=conductivity,
564
  density=density,
565
  specific_heat=specific_heat,
@@ -589,18 +589,18 @@ def display_materials_tab(material_library: MaterialLibrary):
589
  "embodied_carbon": 0.5,
590
  "absorptivity": DEFAULT_MATERIAL_PROPERTIES["absorptivity"],
591
  "price": 50.0,
592
- "emissivity": DEFAULT_MATERIAL_PROPERTIES["emissivity"]
593
- }
594
- st.session_state.material_action = {"action": None, "id": None}
595
  st.session_state.rerun_trigger = None
596
  st.session_state.materials_rerun_pending = True
597
  else:
598
  st.error(f"Failed to save material: {message}")
599
  except Exception as e:
600
- st.error(f"Error saving material: {str(e)}")
601
 
602
  def display_fenestrations_tab(material_library: MaterialLibrary):
603
- """Display the fenestrations tab content with two-column layout."""
604
  col1, col2 = st.columns([3, 2])
605
  with col1:
606
  st.subheader("Fenestrations")
@@ -613,41 +613,44 @@ def display_fenestrations_tab(material_library: MaterialLibrary):
613
  if st.session_state["fenestration_filter"] == "None":
614
  library_fenestrations = []
615
  elif fenestration_filter != "All":
616
- library_fenestrations = [f for f in library_fenestrations if f.__class__.__name__ == fenestration_filter]
617
- cols = st.columns([2, 1, 1, 1, 1])
 
 
 
618
  cols[0].write("**Name**")
619
  cols[1].write("**SHGC**")
620
  cols[2].write("**U-Value (W/m虏路K)**")
621
  cols[3].write("**Preview**")
622
- cols[4].write("**Copy**")
623
  for fenestration in library_fenestrations:
624
  cols = st.columns([2, 1, 1, 1, 1])
625
  cols[0].write(fenestration.name)
626
- cols[1].write(f"{fenestration.shgc:.2f}")
627
- cols[2].write(f"{fenestration.u_value:.3f}")
628
- if cols[3].button("Preview", key=f"preview_lib_fen_{fenestration.name}"):
629
- if st.session_state.get("rerun_trigger") != f"preview_fen_{fenestration.name}":
630
  st.session_state.rerun_trigger = f"preview_fen_{fenestration.name}"
631
  st.session_state.fenestration_editor = {
632
- "name": fenestration.name,
633
- "shgc": fenestration.shgc,
634
  "u_value": fenestration.u_value,
635
- "h_o": fenestration.h_o,
636
  "is_edit": False,
637
- "edit_source": "library"
638
- }
639
  st.session_state.fenestration_form_state = {
640
  "name": fenestration.name,
641
- "shgc": fenestration.shgc,
642
  "u_value": fenestration.u_value,
643
- "h_o": fenestration.h_o
644
- }
645
  st.session_state.active_tab = "Fenestrations"
646
- if cols[4].button("Copy", key=f"copy_lib_fen_{fenestration.name}"):
647
- new_name = f"{fenestration.name}_Project"
648
  counter = 1
649
  while new_name in st.session_state.project_data["fenestrations"]["project"] or new_name in st.session_state.project_data["fenestrations"]["library"]:
650
- new_name = f"{fenestration.name}_Project_{counter}"
651
  counter += 1
652
  new_fenestration = GlazingMaterial(
653
  name=new_name,
@@ -657,7 +660,7 @@ def display_fenestrations_tab(material_library: MaterialLibrary):
657
  is_library=False
658
  )
659
  st.session_state.project_data["fenestrations"]["project"][new_name] = new_fenestration
660
- st.success(f"Fenestration '{new_name}' copied!")
661
  st.session_state.materials_rerun_pending = True
662
 
663
  st.subheader("Project Fenestrations")
@@ -674,7 +677,7 @@ def display_fenestrations_tab(material_library: MaterialLibrary):
674
  cols[0].write(fenestration.name)
675
  cols[1].write(f"{fenestration.shgc:.2f}")
676
  cols[2].write(f"{fenestration.u_value:.3f}")
677
- if cols[3].button("Edit", key=f"edit_proj_fen_{fenestration.name}"):
678
  if st.session_state.get("rerun_trigger") != f"edit_fen_{fenestration.name}":
679
  st.session_state.rerun_trigger = f"edit_fen_{fenestration.name}"
680
  st.session_state.fenestration_editor = {
@@ -693,13 +696,13 @@ def display_fenestrations_tab(material_library: MaterialLibrary):
693
  "h_o": fenestration.h_o
694
  }
695
  st.session_state.active_tab = "Fenestrations"
696
- if cols[4].button("Delete", key=f"delete_proj_fen_{fenestration.name}"):
697
  if any(comp.get("fenestration_material") and comp["fenestration_material"].name == fenestration.name
698
  for comp_list in st.session_state.get("components", {}).values() for comp in comp_list):
699
  st.error(f"Fenestration '{fenestration.name}' is used in components and cannot be deleted.")
700
  else:
701
  del st.session_state.project_data["fenestrations"]["project"][fenestration.name]
702
- st.success(f"Fenestration '{fenestration.name}' deleted!")
703
  st.session_state.materials_rerun_pending = True
704
  else:
705
  st.write("No project fenestrations added.")
@@ -810,10 +813,10 @@ def display_fenestrations_tab(material_library: MaterialLibrary):
810
  )
811
  if is_edit and editor_state.get("edit_source") == "project" and name == original_name:
812
  st.session_state.project_data["fenestrations"]["project"][original_name] = new_fenestration
813
- st.success(f"Fenestration '{name}' updated!")
814
  else:
815
  st.session_state.project_data["fenestrations"]["project"][name] = new_fenestration
816
- st.success(f"Fenestration '{name}' added!")
817
  st.session_state.fenestration_editor = {}
818
  st.session_state.fenestration_form_state = {
819
  "name": "",
 
2
  BuildSustain - Material Library Module
3
 
4
  This module handles the material library functionality of the BuildSustain application,
5
+ allowing users to manage building materials and fenestrations (windows/skylights, doors).
6
+ It provides both predefined library materials and the ability to create project-specific materials.
7
 
8
  Developed by: Dr Majed Abuseif, Deakin University
9
  漏 2025
 
17
  import uuid
18
  from typing import Dict, List, Any, Optional, Tuple, Union
19
  from enum import Enum
 
 
20
  from app.m_c_data import SAMPLE_MATERIALS, SAMPLE_FENESTRATIONS, DEFAULT_MATERIAL_PROPERTIES, DEFAULT_WINDOW_PROPERTIES
21
 
22
  # Configure logging
 
25
 
26
  # Define constants
27
  MATERIAL_CATEGORIES = [
 
28
  "Insulation",
29
+ "Structural",
 
30
  "Finishing",
31
+ "Sub-Structural"
32
  ]
33
 
34
  FENESTRATION_TYPES = [
35
+ "Window/Skylight",
36
+ "Door"
 
37
  ]
38
 
39
  class MaterialCategory(Enum):
 
40
  INSULATION = "Insulation"
41
+ STRUCTURAL = "Structural"
 
42
  FINISHING = "Finishing"
43
+ SUB_STRUCTURAL = "Sub-Structural"
44
 
45
  class Material:
46
  def __init__(self, name: str, category: MaterialCategory, conductivity: float, density: float,
 
58
  self.emissivity = emissivity
59
  self.is_library = is_library
60
 
61
+ def get_thermal_mass(self) -> str:
62
+ # Calculate areal heat capacity: 蟻 * cp * d (J/m虏路K)
63
+ thermal_mass = self.density * self.specific_heat * self.default_thickness
64
+ # Categorize based on thresholds
65
+ if thermal_mass < 30000:
66
+ return "Low"
67
+ elif 30000 <= thermal_mass <= 90000:
68
+ return "Medium"
69
+ else:
70
+ return "High"
71
 
72
+ def get_u_value(self) -> float:
73
+ # Calculate U-value: 位 / d (W/m虏路K)
74
  return self.conductivity / self.default_thickness if self.default_thickness > 0 else 0.0
75
 
76
  class GlazingMaterial:
 
100
  "Embodied Carbon (kgCO鈧俥/kg)": m.embodied_carbon,
101
  "Absorptivity": m.absorptivity,
102
  "Price (USD/m虏)": m.price,
103
+ "Emissivity": m.emissivity,
104
+ "Thermal Mass Category": m.get_thermal_mass()
105
  }
106
  for m in project_materials.values()
107
  ]
 
227
  try:
228
  material_library.library_materials[k] = Material(
229
  name=k,
230
+ category=MaterialCategory(m["category"].upper().replace("-", "_")),
231
  conductivity=m["thermal_properties"]["conductivity"],
232
  density=m["thermal_properties"]["density"],
233
  specific_heat=m["thermal_properties"]["specific_heat"],
 
244
 
245
  material_library.library_glazing_materials = {}
246
  for k, f in st.session_state.project_data["fenestrations"]["library"].items():
247
+ try:
248
+ material_library.library_glazing_materials[k] = GlazingMaterial(
249
+ name=k,
250
+ shgc=f["performance"]["shgc"],
251
+ u_value=f["performance"]["u_value"],
252
+ h_o=f.get("h_o", DEFAULT_WINDOW_PROPERTIES["h_o"]),
253
+ is_library=True
254
+ )
255
+ except KeyError as e:
256
+ logger.error(f"Error processing fenestration {k}: Missing key {e}")
257
+ continue
 
258
 
259
  with tab1:
260
  display_materials_tab(material_library)
 
308
  cols[1].write("**Thermal Mass**")
309
  cols[2].write("**U-Value (W/m虏路K)**")
310
  cols[3].write("**Preview**")
311
+ cols[4].write("**Clone**")
312
  for material in library_materials:
313
  cols = st.columns([2, 1, 1, 1, 1])
314
  cols[0].write(material.name)
315
+ cols[1].write(material.get_thermal_mass())
316
  cols[2].write(f"{material.get_u_value():.3f}")
317
  if cols[3].button("Preview", key=f"preview_lib_mat_{material.name}"):
318
  if st.session_state.get("rerun_trigger") != f"preview_mat_{material.name}":
 
344
  "emissivity": material.emissivity
345
  }
346
  st.session_state.active_tab = "Materials"
347
+ if cols[4].button("Clone", key=f"copy_lib_mat_{material.name}"):
348
  new_name = f"{material.name}_Project"
349
  counter = 1
350
  while new_name in st.session_state.project_data["materials"]["project"] or new_name in st.session_state.project_data["materials"]["library"]:
 
382
  for material in st.session_state.project_data["materials"]["project"].values():
383
  cols = st.columns([2, 1, 1, 1, 1])
384
  cols[0].write(material.name)
385
+ cols[1].write(material.get_thermal_mass())
386
  cols[2].write(f"{material.get_u_value():.3f}")
387
+ if cols[3].button("Edit", key=f"edit_mat_{material.name}"):
388
  if st.session_state.get("rerun_trigger") != f"edit_mat_{material.name}":
389
  st.session_state.rerun_trigger = f"edit_mat_{material.name}"
390
  st.session_state.material_editor = {
 
415
  "emissivity": material.emissivity
416
  }
417
  st.session_state.active_tab = "Materials"
418
+ if cols[4].button("Delete", key=f"delete_mat_{material.name}"):
419
  success, message = material_library.delete_project_material(
420
  material.name, st.session_state.project_data["materials"]["project"], st.session_state.get("components", {})
421
  )
 
459
  original_name = editor_state.get("original_name", "")
460
  name = st.text_input(
461
  "Material Name",
462
+ value=form_state.get("name", editor_state.get("name", ""),
463
  help="Unique material identifier",
464
  key="material_name_input"
465
  )
 
559
  try:
560
  new_material = Material(
561
  name=name,
562
+ category=MaterialCategory(category.upper().replace("-", "_")),
563
  conductivity=conductivity,
564
  density=density,
565
  specific_heat=specific_heat,
 
589
  "embodied_carbon": 0.5,
590
  "absorptivity": DEFAULT_MATERIAL_PROPERTIES["absorptivity"],
591
  "price": 50.0,
592
+ "emissivity": DEFAULT_PROPERTIES["emissivity"]
593
+ },
594
+ st.session_state.material_action = {"action": None, "material_action_id": None}
595
  st.session_state.rerun_trigger = None
596
  st.session_state.materials_rerun_pending = True
597
  else:
598
  st.error(f"Failed to save material: {message}")
599
  except Exception as e:
600
+ st.error("Error saving material: {str(e)}")
601
 
602
  def display_fenestrations_tab(material_library: MaterialLibrary):
603
+ """Display the fenestrations tab with two-column layout."""
604
  col1, col2 = st.columns([3, 2])
605
  with col1:
606
  st.subheader("Fenestrations")
 
613
  if st.session_state["fenestration_filter"] == "None":
614
  library_fenestrations = []
615
  elif fenestration_filter != "All":
616
+ # Map fenestration types for filtering
617
+ fen_type = "window" if fenestration_filter == "Window/Skylight" else "door"
618
+ library_fenestrations = [f for f in library_fenestrations if
619
+ st.session_state.project_data["fenestrations"]["library"][f.name]["type"] == fen_type]
620
+ cols = st.columns([2, cols, 1, 1, 1, 1])
621
  cols[0].write("**Name**")
622
  cols[1].write("**SHGC**")
623
  cols[2].write("**U-Value (W/m虏路K)**")
624
  cols[3].write("**Preview**")
625
+ cols[4].write("**Clone**")
626
  for fenestration in library_fenestrations:
627
  cols = st.columns([2, 1, 1, 1, 1])
628
  cols[0].write(fenestration.name)
629
+ cols[1].write(f"{fenestration_shgc:.2f}")
630
+ cols[2].write(f"{fenestration_data.u_value:.3f}")
631
+ if cols[3].button("Preview", key=f"preview_fen_btn_{fenestration.name}"):
632
+ if st.session_state.get("rerun_trigger") != f"preview_fen_{fenestration.name}"):
633
  st.session_state.rerun_trigger = f"preview_fen_{fenestration.name}"
634
  st.session_state.fenestration_editor = {
635
+ "name": fenestration_name,
636
+ "shgc": fenestration_shgc,
637
  "u_value": fenestration.u_value,
638
+ "shgc": fenestration_h_o,
639
  "is_edit": False,
640
+ "edit": "library"
641
+ },
642
  st.session_state.fenestration_form_state = {
643
  "name": fenestration.name,
644
+ "shgc": fenestration_shgc,
645
  "u_value": fenestration.u_value,
646
+ "shgc": fenestration_h_o
647
+ },
648
  st.session_state.active_tab = "Fenestrations"
649
+ if cols[4].button("Clone", key=f"copy_fen_btn_{fenestration.name}"):
650
+ new_name = f"{fenestration_name}_Project"
651
  counter = 1
652
  while new_name in st.session_state.project_data["fenestrations"]["project"] or new_name in st.session_state.project_data["fenestrations"]["library"]:
653
+ new_name = f"{fenestration_name}_Project_{counter}"
654
  counter += 1
655
  new_fenestration = GlazingMaterial(
656
  name=new_name,
 
660
  is_library=False
661
  )
662
  st.session_state.project_data["fenestrations"]["project"][new_name] = new_fenestration
663
+ st.success(f"Fenestration '{new_name}' cloned successfully!")
664
  st.session_state.materials_rerun_pending = True
665
 
666
  st.subheader("Project Fenestrations")
 
677
  cols[0].write(fenestration.name)
678
  cols[1].write(f"{fenestration.shgc:.2f}")
679
  cols[2].write(f"{fenestration.u_value:.3f}")
680
+ if cols[3].button("Edit", key=f"edit_fen_{fenestration.name}"):
681
  if st.session_state.get("rerun_trigger") != f"edit_fen_{fenestration.name}":
682
  st.session_state.rerun_trigger = f"edit_fen_{fenestration.name}"
683
  st.session_state.fenestration_editor = {
 
696
  "h_o": fenestration.h_o
697
  }
698
  st.session_state.active_tab = "Fenestrations"
699
+ if cols[4].button("Delete", key=f"delete_fen_{fenestration.name}"):
700
  if any(comp.get("fenestration_material") and comp["fenestration_material"].name == fenestration.name
701
  for comp_list in st.session_state.get("components", {}).values() for comp in comp_list):
702
  st.error(f"Fenestration '{fenestration.name}' is used in components and cannot be deleted.")
703
  else:
704
  del st.session_state.project_data["fenestrations"]["project"][fenestration.name]
705
+ st.success(f"Fenestration '{fenestration.name}' deleted successfully!")
706
  st.session_state.materials_rerun_pending = True
707
  else:
708
  st.write("No project fenestrations added.")
 
813
  )
814
  if is_edit and editor_state.get("edit_source") == "project" and name == original_name:
815
  st.session_state.project_data["fenestrations"]["project"][original_name] = new_fenestration
816
+ st.success(f"Fenestration '{name}' updated successfully!")
817
  else:
818
  st.session_state.project_data["fenestrations"]["project"][name] = new_fenestration
819
+ st.success(f"Fenestration '{name}' added successfully!")
820
  st.session_state.fenestration_editor = {}
821
  st.session_state.fenestration_form_state = {
822
  "name": "",