Spaces:
Sleeping
Sleeping
| """ | |
| BuildSustain - Building Components Module | |
| This module handles the definition of building envelope components (walls, roofs, floors, | |
| windows, skylights) for the BuildSustain application. It allows users to | |
| assign constructions and fenestrations to specific components, define their areas, | |
| orientations, and other relevant properties. | |
| Developed by: Dr Majed Abuseif, Deakin University | |
| © 2025 | |
| """ | |
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import json | |
| import logging | |
| from typing import Dict, List, Any, Optional, Tuple, Union | |
| from app.m_c_data import DEFAULT_WINDOW_PROPERTIES # Import DEFAULT_WINDOW_PROPERTIES | |
| from app.materials_library import Material, GlazingMaterial # Added import for GlazingMaterial | |
| import uuid | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| # Define constants | |
| COMPONENT_TYPES = ["Walls", "Roofs", "Floors", "Windows", "Skylights"] | |
| ORIENTATION_OPTIONS = ["A (North)", "B (South)", "C (East)", "D (West)", "Custom"] | |
| ORIENTATION_MAP = {'A (North)': 0.0, 'B (South)': 180.0, 'C (East)': 90.0, 'D (West)': 270.0, 'Custom': 0.0} | |
| def display_components_page(): | |
| """ | |
| Display the building components page. | |
| This is the main function called by main.py when the Building Components page is selected. | |
| """ | |
| st.title("Building Components") | |
| st.write("Define walls, roofs, floors, windows, and skylights based on ASHRAE 2005 Handbook of Fundamentals.") | |
| # Display help information in an expandable section | |
| with st.expander("Help & Information"): | |
| display_components_help() | |
| # Initialize components in session state if not present | |
| initialize_components() | |
| # Check if rerun is pending | |
| if 'components_rerun_pending' not in st.session_state: | |
| st.session_state.components_rerun_pending = False | |
| if st.session_state.components_rerun_pending: | |
| st.session_state.components_rerun_pending = False | |
| st.rerun() | |
| # Create tabs for different component types | |
| tabs = st.tabs(COMPONENT_TYPES) | |
| for i, component_type in enumerate(COMPONENT_TYPES): | |
| with tabs[i]: | |
| display_component_tab(component_type.lower()) | |
| # Navigation buttons | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if st.button("Back to Construction", key="back_to_construction"): | |
| st.session_state.current_page = "Construction" | |
| st.rerun() | |
| with col2: | |
| if st.button("Continue to Internal Loads", key="continue_to_internal_loads"): | |
| st.session_state.current_page = "Internal Loads" | |
| st.rerun() | |
| def initialize_components(): | |
| """Initialize components in session state if not present and migrate existing components.""" | |
| if "components" not in st.session_state.project_data: | |
| st.session_state.project_data["components"] = { | |
| "walls": [], | |
| "roofs": [], | |
| "floors": [], | |
| "windows": [], | |
| "skylights": [] | |
| } | |
| migrate_component_types() | |
| def migrate_component_types(): | |
| """Add 'type' key and rename 'tilt' to 'surface_tilt' for existing components for backward compatibility.""" | |
| for comp_type in st.session_state.project_data["components"]: | |
| for component in st.session_state.project_data["components"][comp_type]: | |
| if "type" not in component: | |
| component["type"] = comp_type | |
| logger.info(f"Added type '{comp_type}' to component '{component['name']}'") | |
| if comp_type != "floors" and "tilt" in component: | |
| component["surface_tilt"] = component.pop("tilt") | |
| logger.info(f"Renamed 'tilt' to 'surface_tilt' for component '{component['name']}'") | |
| # Add adiabatic and ground_contact if not present for walls, roofs, and floors | |
| if comp_type in ["walls", "roofs", "floors"]: | |
| if "adiabatic" not in component: | |
| component["adiabatic"] = False | |
| logger.info(f"Added 'adiabatic' to component '{component['name']}'") | |
| if "ground_contact" not in component: | |
| component["ground_contact"] = False | |
| logger.info(f"Added 'ground_contact' to component '{component['name']}'") | |
| def display_component_tab(comp_type: str): | |
| """ | |
| Display the content for a specific component type tab. | |
| Args: | |
| comp_type: The type of component (e.g., "walls", "windows") | |
| """ | |
| # Get singular form for display | |
| comp_singular = comp_type[:-1].capitalize() | |
| # Get components of this type | |
| components = st.session_state.project_data["components"].get(comp_type, []) | |
| # Get available items for this component type | |
| if comp_type in ["walls", "roofs", "floors"]: | |
| available_items = get_available_constructions() | |
| else: # Windows, skylights | |
| available_items = get_available_fenestrations() | |
| # Split the display into two columns | |
| col1, col2 = st.columns([3, 2]) | |
| with col1: | |
| st.subheader(f"Saved {comp_type.capitalize()}") | |
| if components: | |
| # Display components in a table format based on component type | |
| if comp_type in ["walls", "windows"]: | |
| display_wall_window_table(comp_type, components) | |
| elif comp_type in ["roofs", "skylights"]: | |
| display_roof_skylight_table(comp_type, components) | |
| elif comp_type == "floors": | |
| display_floor_table(comp_type, components) | |
| else: | |
| st.write(f"No {comp_type} defined.") | |
| with col2: | |
| st.subheader(f"{comp_singular} Editor/Creator") | |
| # Check if we have an editor state for this component type | |
| editor_key = f"{comp_type}_editor" | |
| if editor_key not in st.session_state: | |
| st.session_state[editor_key] = {} | |
| # Check if we have an action state for this component type | |
| action_key = f"{comp_type}_action" | |
| if action_key not in st.session_state: | |
| st.session_state[action_key] = {"action": None, "id": None} | |
| # Display the editor form | |
| with st.form(f"{comp_type}_editor_form", clear_on_submit=True): | |
| editor_state = st.session_state.get(editor_key, {}) | |
| is_edit = editor_state.get("is_edit", False) | |
| # Component name | |
| name = st.text_input( | |
| "Name", | |
| value=editor_state.get("name", ""), | |
| help="Enter a unique name for this component." | |
| ) | |
| # Area | |
| area = st.number_input( | |
| "Area (m²)", | |
| min_value=0.1, | |
| max_value=10000.0, | |
| value=float(editor_state.get("area", 10.0)), | |
| format="%.2f", | |
| help="Surface area of the component in square meters." | |
| ) | |
| # Component-specific inputs | |
| if comp_type in ["walls", "windows", "roofs", "skylights"]: | |
| # Elevation (orientation) | |
| elevation = st.selectbox( | |
| "Elevation", | |
| ORIENTATION_OPTIONS, | |
| index=ORIENTATION_OPTIONS.index(editor_state.get("elevation", ORIENTATION_OPTIONS[0])) if editor_state.get("elevation") in ORIENTATION_OPTIONS else 0, | |
| help="Orientation of the component relative to the building orientation angle." | |
| ) | |
| # Rotation | |
| rotation = st.number_input( | |
| "Rotation (°)", | |
| min_value=0.0, | |
| max_value=359.0, | |
| value=float(editor_state.get("rotation", 0.0)), | |
| step=1.0, | |
| format="%.0f", | |
| help="Rotation angle in degrees to adjust the elevation (0° = North, 90° = East, 180° = South, 270° = West)." | |
| ) | |
| # Calculate surface azimuth | |
| elevation_angle = ORIENTATION_MAP[elevation] | |
| surface_azimuth = (elevation_angle + rotation) % 360.0 | |
| # Surface Tilt | |
| default_tilt = 0.0 if comp_type in ["roofs", "skylights"] else 90.0 | |
| surface_tilt = st.number_input( | |
| "Surface Tilt (°)", | |
| min_value=0.0, | |
| max_value=180.0, | |
| value=float(editor_state.get("surface_tilt", default_tilt)), | |
| step=1.0, | |
| format="%.0f", | |
| help="Tilt angle of the component relative to horizontal (0° = horizontal, 90° = vertical)." | |
| ) | |
| # Adiabatic and Ground Contact for walls, roofs, and floors | |
| if comp_type in ["walls", "roofs", "floors"]: | |
| adiabatic = st.selectbox( | |
| "Adiabatic", | |
| ["Yes", "No"], | |
| index=0 if editor_state.get("adiabatic", False) else 1, | |
| help="Select 'Yes' if the component is adiabatic (no heat transfer across it)." | |
| ) | |
| ground_contact = st.selectbox( | |
| "Ground Contact", | |
| ["Yes", "No"], | |
| index=0 if editor_state.get("ground_contact", False) else 1, | |
| help="Select 'Yes' if the component is in contact with the ground." | |
| ) | |
| # Construction or fenestration selection | |
| if comp_type in ["walls", "roofs", "floors"]: | |
| construction_options = list(available_items.keys()) | |
| if not construction_options: | |
| st.error("No constructions available. Please create constructions first.") | |
| construction_options = [""] | |
| construction = st.selectbox( | |
| "Construction", | |
| construction_options, | |
| index=construction_options.index(editor_state.get("construction", "")) if editor_state.get("construction", "") in construction_options else 0, | |
| help="Select the construction assembly for this component." | |
| ) | |
| # Extract material properties from the first layer | |
| absorptivity = 0.7 | |
| emissivity = 0.9 | |
| if construction in available_items and "layers" in available_items[construction]: | |
| first_layer = available_items[construction]["layers"][0] | |
| if "material" in first_layer: | |
| material_data = get_material_data(first_layer["material"]) | |
| if material_data: | |
| if isinstance(material_data, Material): | |
| absorptivity = material_data.absorptivity | |
| emissivity = material_data.emissivity | |
| else: | |
| absorptivity = material_data.get("absorptivity", 0.7) | |
| emissivity = material_data.get("emissivity", 0.9) | |
| else: # Windows, skylights | |
| # For fenestrations, we need to select a parent component | |
| parent_components = get_parent_components(comp_type) | |
| parent_options = list(parent_components.keys()) | |
| if not parent_options: | |
| st.error(f"No parent components available. Please create {'walls' if comp_type == 'windows' else 'roofs'} first.") | |
| parent_options = [""] | |
| parent_component = st.selectbox( | |
| "Parent Component", | |
| parent_options, | |
| index=parent_options.index(editor_state.get("parent_component", "")) if editor_state.get("parent_component", "") in parent_options else 0, | |
| help="Select the wall or roof component this fenestration belongs to." | |
| ) | |
| fenestration_options = list(available_items.keys()) | |
| if not fenestration_options: | |
| st.error("No fenestrations available. Please create fenestrations first.") | |
| fenestration_options = [""] | |
| fenestration = st.selectbox( | |
| "Fenestration", | |
| fenestration_options, | |
| index=fenestration_options.index(editor_state.get("fenestration", "")) if editor_state.get("fenestration", "") in fenestration_options else 0, | |
| help="Select the fenestration type for this component." | |
| ) | |
| # Shading Coefficient | |
| shading_coefficient = st.number_input( | |
| "Shading Coefficient (SC)", | |
| min_value=0.0, | |
| max_value=1.0, | |
| value=float(editor_state.get("shading_coefficient", 1.0)), | |
| format="%.2f", | |
| help="Shading coefficient for external or internal shading devices (0.0 to 1.0)." | |
| ) | |
| # Shading Type | |
| shading_type_options = ["No Shading", "Internal Shading", "External Shading"] | |
| shading_type = st.selectbox( | |
| "Shading Type", | |
| shading_type_options, | |
| index=shading_type_options.index(editor_state.get("shading_type", "No Shading")) if editor_state.get("shading_type") in shading_type_options else 0, | |
| help="Select the type of shading for this fenestration." | |
| ) | |
| # Function Type (Operable/Fixed) | |
| function_type_options = ["Operable", "Fixed"] | |
| function_type = st.selectbox( | |
| "Function Type", | |
| function_type_options, | |
| index=function_type_options.index(editor_state.get("function_type", "Fixed")) if editor_state.get("function_type") in function_type_options else 0, | |
| help="Select whether the window or skylight is operable (can be opened) or fixed." | |
| ) | |
| # Submit buttons | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| submit_label = "Update" if is_edit else "Add" | |
| submit = st.form_submit_button(f"{submit_label} {comp_singular}") | |
| with col2: | |
| refresh = st.form_submit_button("Refresh") | |
| # Handle form submission | |
| if submit: | |
| # Validate name | |
| if not name or name.strip() == "": | |
| st.error("Component name is required.") | |
| return | |
| # Check for unique name | |
| existing_names = [comp["name"] for comp in components if not (is_edit and comp["name"] == editor_state.get("name"))] | |
| if name in existing_names: | |
| st.error("Component name must be unique.") | |
| return | |
| # Validate component type | |
| valid_types = set(COMPONENT_TYPES) # {'Walls', 'Roofs', 'Floors', 'Windows', 'Skylights'} | |
| if comp_type not in [t.lower() for t in valid_types]: | |
| st.error(f"Invalid component type '{comp_type}'. Please select a valid type.") | |
| logger.error(f"Invalid component type '{comp_type}' for component '{name}'.") | |
| return | |
| # Create component data | |
| component_data = { | |
| "id": str(uuid.uuid4()), | |
| "name": name, | |
| "area": area, | |
| "type": comp_type, # Explicitly set the component type | |
| } | |
| if comp_type in ["walls", "windows", "roofs", "skylights"]: | |
| component_data["elevation"] = elevation | |
| component_data["rotation"] = rotation | |
| component_data["surface_azimuth"] = surface_azimuth | |
| component_data["surface_tilt"] = surface_tilt | |
| if comp_type in ["walls", "roofs", "floors"]: | |
| component_data["adiabatic"] = adiabatic == "Yes" | |
| component_data["ground_contact"] = ground_contact == "Yes" | |
| if comp_type in ["walls", "roofs", "floors"]: | |
| component_data["construction"] = construction | |
| component_data["absorptivity"] = absorptivity | |
| component_data["emissivity"] = emissivity | |
| # Get U-value from construction if available | |
| if construction in available_items: | |
| component_data["u_value"] = available_items[construction].get("u_value", 0.0) | |
| else: | |
| component_data["u_value"] = 0.0 | |
| else: # Windows, skylights | |
| component_data["fenestration"] = fenestration | |
| component_data["parent_component"] = parent_component | |
| component_data["function_type"] = function_type | |
| component_data["shading_coefficient"] = shading_coefficient | |
| component_data["shading_type"] = shading_type | |
| # Fix: Handle both GlazingMaterial and dictionary for fenestration_data | |
| if fenestration in available_items: | |
| fenestration_data = available_items[fenestration] | |
| if isinstance(fenestration_data, GlazingMaterial): | |
| component_data["shgc"] = fenestration_data.shgc | |
| component_data["u_value"] = fenestration_data.u_value | |
| component_data["h_o"] = fenestration_data.h_o or DEFAULT_WINDOW_PROPERTIES["h_o"] | |
| component_data["visible_transmittance"] = fenestration_data.visible_transmittance | |
| else: | |
| component_data["shgc"] = fenestration_data.get("shgc", 0.7) | |
| component_data["u_value"] = fenestration_data.get("u_value", 5.0) | |
| component_data["h_o"] = fenestration_data.get("h_o", DEFAULT_WINDOW_PROPERTIES["h_o"]) | |
| component_data["visible_transmittance"] = fenestration_data.get("visible_transmittance", 0.7) | |
| else: | |
| logger.warning(f"Fenestration '{fenestration}' not found for {name}. Using defaults.") | |
| component_data["shgc"] = 0.7 | |
| component_data["u_value"] = 5.0 | |
| component_data["h_o"] = DEFAULT_WINDOW_PROPERTIES["h_o"] | |
| component_data["visible_transmittance"] = 0.7 | |
| # Handle edit mode | |
| if is_edit: | |
| index = editor_state.get("index", 0) | |
| st.session_state.project_data["components"][comp_type][index] = component_data | |
| st.success(f"{comp_singular} '{name}' updated!") | |
| else: | |
| st.session_state.project_data["components"][comp_type].append(component_data) | |
| st.success(f"{comp_singular} '{name}' added!") | |
| # Clear editor state | |
| st.session_state[editor_key] = {} | |
| st.session_state[action_key] = {"action": "save", "id": str(uuid.uuid4())} | |
| st.session_state.components_rerun_pending = True | |
| elif refresh: | |
| # Clear editor state | |
| st.session_state[editor_key] = {} | |
| st.session_state[action_key] = {"action": "refresh", "id": str(uuid.uuid4())} | |
| st.session_state.components_rerun_pending = True | |
| def display_wall_window_table(comp_type: str, components: List[Dict[str, Any]]): | |
| """Display a table of walls or windows with appropriate columns.""" | |
| # Create column headers | |
| cols = st.columns([2, 1, 1, 1, 1, 1, 1, 1, 1, 1] if comp_type == "walls" else [2, 1, 1, 1, 1, 1, 1, 1, 1]) | |
| cols[0].write("**Name**") | |
| cols[1].write("**U-Value**") | |
| cols[2].write("**Area (m²)**") | |
| cols[3].write("**Surface Azimuth (°)**") | |
| cols[4].write("**Surface Tilt (°)**") | |
| if comp_type == "walls": | |
| cols[5].write("**Adiabatic**") | |
| cols[6].write("**Ground Contact**") | |
| cols[7].write("**Edit**") | |
| cols[8].write("**Delete**") | |
| else: # windows | |
| cols[5].write("**SC**") | |
| cols[6].write("**Shading Type**") | |
| cols[7].write("**Edit**") | |
| cols[8].write("**Delete**") | |
| # Display each component | |
| for idx, comp in enumerate(components): | |
| cols = st.columns([2, 1, 1, 1, 1, 1, 1, 1, 1] if comp_type == "walls" else [2, 1, 1, 1, 1, 1, 1, 1, 1]) | |
| cols[0].write(comp["name"]) | |
| cols[1].write(f"{comp.get('u_value', 0.0):.3f}") | |
| cols[2].write(f"{comp['area']:.2f}") | |
| cols[3].write(f"{comp.get('surface_azimuth', 0.0):.0f}") | |
| cols[4].write(f"{comp.get('surface_tilt', 90.0):.0f}") | |
| if comp_type == "walls": | |
| cols[5].write("Yes" if comp.get("adiabatic", False) else "No") | |
| cols[6].write("Yes" if comp.get("ground_contact", False) else "No") | |
| else: # windows | |
| cols[5].write(f"{comp.get('shading_coefficient', 1.0):.2f}") | |
| cols[6].write(comp.get("shading_type", "No Shading")) | |
| # Edit button | |
| edit_key = f"edit_{comp_type}_{comp['name']}_{idx}" | |
| edit_col = 7 if comp_type == "walls" else 7 | |
| with cols[edit_col].container(): | |
| if st.button("Edit", key=edit_key): | |
| editor_data = { | |
| "index": idx, | |
| "name": comp["name"], | |
| "area": comp["area"], | |
| "elevation": comp.get("elevation", ORIENTATION_OPTIONS[0]), | |
| "rotation": comp.get("rotation", 0.0), | |
| "surface_tilt": comp.get("surface_tilt", 90.0), | |
| "is_edit": True | |
| } | |
| if comp_type == "walls": | |
| editor_data["construction"] = comp.get("construction", "") | |
| editor_data["adiabatic"] = comp.get("adiabatic", False) | |
| editor_data["ground_contact"] = comp.get("ground_contact", False) | |
| else: # windows | |
| editor_data["fenestration"] = comp.get("fenestration", "") | |
| editor_data["parent_component"] = comp.get("parent_component", "") | |
| editor_data["shading_coefficient"] = comp.get("shading_coefficient", 1.0) | |
| editor_data["shading_type"] = comp.get("shading_type", "No Shading") | |
| editor_data["function_type"] = comp.get("function_type", "Fixed") | |
| st.session_state[f"{comp_type}_editor"] = editor_data | |
| st.session_state[f"{comp_type}_action"] = {"action": "edit", "id": str(uuid.uuid4())} | |
| st.session_state.components_rerun_pending = True | |
| # Delete button | |
| delete_key = f"delete_{comp_type}_{comp['name']}_{idx}" | |
| delete_col = 8 if comp_type == "walls" else 8 | |
| with cols[delete_col].container(): | |
| if st.button("Delete", key=delete_key): | |
| st.session_state.project_data["components"][comp_type].pop(idx) | |
| st.success(f"{comp_type[:-1].capitalize()} '{comp['name']}' deleted!") | |
| st.session_state[f"{comp_type}_action"] = {"action": "delete", "id": str(uuid.uuid4())} | |
| st.session_state.components_rerun_pending = True | |
| def display_roof_skylight_table(comp_type: str, components: List[Dict[str, Any]]): | |
| """Display a table of roofs or skylights with appropriate columns.""" | |
| # Create column headers | |
| cols = st.columns([2, 1, 1, 1, 1, 1, 1, 1, 1, 1] if comp_type == "roofs" else [2, 1, 1, 1, 1, 1, 1, 1, 1]) | |
| cols[0].write("**Name**") | |
| cols[1].write("**U-Value**") | |
| cols[2].write("**Area (m²)**") | |
| cols[3].write("**Surface Azimuth (°)**") | |
| cols[4].write("**Surface Tilt (°)**") | |
| if comp_type == "roofs": | |
| cols[5].write("**Adiabatic**") | |
| cols[6].write("**Ground Contact**") | |
| cols[7].write("**Edit**") | |
| cols[8].write("**Delete**") | |
| else: # skylights | |
| cols[5].write("**SC**") | |
| cols[6].write("**Shading Type**") | |
| cols[7].write("**Edit**") | |
| cols[8].write("**Delete**") | |
| # Display each component | |
| for idx, comp in enumerate(components): | |
| cols = st.columns([2, 1, 1, 1, 1, 1, 1, 1, 1] if comp_type == "roofs" else [2, 1, 1, 1, 1, 1, 1, 1, 1]) | |
| cols[0].write(comp["name"]) | |
| cols[1].write(f"{comp.get('u_value', 0.0):.3f}") | |
| cols[2].write(f"{comp['area']:.2f}") | |
| cols[3].write(f"{comp.get('surface_azimuth', 0.0):.0f}") | |
| cols[4].write(f"{comp.get('surface_tilt', 0.0):.0f}") | |
| if comp_type == "roofs": | |
| cols[5].write("Yes" if comp.get("adiabatic", False) else "No") | |
| cols[6].write("Yes" if comp.get("ground_contact", False) else "No") | |
| else: # skylights | |
| cols[5].write(f"{comp.get('shading_coefficient', 1.0):.2f}") | |
| cols[6].write(comp.get("shading_type", "No Shading")) | |
| # Edit button | |
| edit_key = f"edit_{comp_type}_{comp['name']}_{idx}" | |
| edit_col = 7 if comp_type == "roofs" else 7 | |
| with cols[edit_col].container(): | |
| if st.button("Edit", key=edit_key): | |
| editor_data = { | |
| "index": idx, | |
| "name": comp["name"], | |
| "area": comp["area"], | |
| "elevation": comp.get("elevation", ORIENTATION_OPTIONS[0]), | |
| "rotation": comp.get("rotation", 0.0), | |
| "surface_tilt": comp.get("surface_tilt", 0.0), | |
| "is_edit": True | |
| } | |
| if comp_type == "roofs": | |
| editor_data["construction"] = comp.get("construction", "") | |
| editor_data["adiabatic"] = comp.get("adiabatic", False) | |
| editor_data["ground_contact"] = comp.get("ground_contact", False) | |
| else: # skylights | |
| editor_data["fenestration"] = comp.get("fenestration", "") | |
| editor_data["parent_component"] = comp.get("parent_component", "") | |
| editor_data["shading_coefficient"] = comp.get("shading_coefficient", 1.0) | |
| editor_data["shading_type"] = comp.get("shading_type", "No Shading") | |
| editor_data["function_type"] = comp.get("function_type", "Fixed") | |
| st.session_state[f"{comp_type}_editor"] = editor_data | |
| st.session_state[f"{comp_type}_action"] = {"action": "edit", "id": str(uuid.uuid4())} | |
| st.session_state.components_rerun_pending = True | |
| # Delete button | |
| delete_key = f"delete_{comp_type}_{comp['name']}_{idx}" | |
| delete_col = 8 if comp_type == "roofs" else 8 | |
| with cols[delete_col].container(): | |
| if st.button("Delete", key=delete_key): | |
| st.session_state.project_data["components"][comp_type].pop(idx) | |
| st.success(f"{comp_type[:-1].capitalize()} '{comp['name']}' deleted!") | |
| st.session_state[f"{comp_type}_action"] = {"action": "delete", "id": str(uuid.uuid4())} | |
| st.session_state.components_rerun_pending = True | |
| def display_floor_table(comp_type: str, components: List[Dict[str, Any]]): | |
| """Display a table of floors with appropriate columns.""" | |
| # Create column headers | |
| cols = st.columns([2, 1, 1, 1, 1, 1, 1]) | |
| cols[0].write("**Name**") | |
| cols[1].write("**U-Value**") | |
| cols[2].write("**Area (m²)**") | |
| cols[3].write("**Adiabatic**") | |
| cols[4].write("**Ground Contact**") | |
| cols[5].write("**Edit**") | |
| cols[6].write("**Delete**") | |
| # Display each component | |
| for idx, comp in enumerate(components): | |
| cols = st.columns([2, 1, 1, 1, 1, 1, 1]) | |
| cols[0].write(comp["name"]) | |
| cols[1].write(f"{comp.get('u_value', 0.0):.3f}") | |
| cols[2].write(f"{comp['area']:.2f}") | |
| cols[3].write("Yes" if comp.get("adiabatic", False) else "No") | |
| cols[4].write("Yes" if comp.get("ground_contact", False) else "No") | |
| # Edit button | |
| edit_key = f"edit_{comp_type}_{comp['name']}_{idx}" | |
| with cols[5].container(): | |
| if st.button("Edit", key=edit_key): | |
| editor_data = { | |
| "index": idx, | |
| "name": comp["name"], | |
| "area": comp["area"], | |
| "construction": comp.get("construction", ""), | |
| "adiabatic": comp.get("adiabatic", False), | |
| "ground_contact": comp.get("ground_contact", False), | |
| "is_edit": True | |
| } | |
| st.session_state[f"{comp_type}_editor"] = editor_data | |
| st.session_state[f"{comp_type}_action"] = {"action": "edit", "id": str(uuid.uuid4())} | |
| st.session_state.components_rerun_pending = True | |
| # Delete button | |
| delete_key = f"delete_{comp_type}_{comp['name']}_{idx}" | |
| with cols[6].container(): | |
| if st.button("Delete", key=delete_key): | |
| st.session_state.project_data["components"][comp_type].pop(idx) | |
| st.success(f"{comp_type[:-1].capitalize()} '{comp['name']}' deleted!") | |
| st.session_state[f"{comp_type}_action"] = {"action": "delete", "id": str(uuid.uuid4())} | |
| st.session_state.components_rerun_pending = True | |
| def get_available_constructions() -> Dict[str, Any]: | |
| """ | |
| Get all available constructions from both library and project. | |
| Returns: | |
| Dict of construction name to construction properties | |
| """ | |
| available_constructions = {} | |
| # Add library constructions | |
| if "constructions" in st.session_state.project_data: | |
| if "library" in st.session_state.project_data["constructions"]: | |
| for name, construction in st.session_state.project_data["constructions"]["library"].items(): | |
| available_constructions[name] = construction | |
| # Add project constructions | |
| if "project" in st.session_state.project_data["constructions"]: | |
| for name, construction in st.session_state.project_data["constructions"]["project"].items(): | |
| available_constructions[name] = construction | |
| return available_constructions | |
| def get_available_fenestrations() -> Dict[str, Any]: | |
| """ | |
| Get all available fenestrations from both library and project. | |
| Returns: | |
| Dict of fenestration name to fenestration properties | |
| """ | |
| available_fenestrations = {} | |
| # Add library fenestrations | |
| if "fenestrations" in st.session_state.project_data: | |
| if "library" in st.session_state.project_data["fenestrations"]: | |
| for name, fenestration in st.session_state.project_data["fenestrations"]["library"].items(): | |
| available_fenestrations[name] = fenestration | |
| # Add project fenestrations | |
| if "project" in st.session_state.project_data["fenestrations"]: | |
| for name, fenestration in st.session_state.project_data["fenestrations"]["project"].items(): | |
| available_fenestrations[name] = fenestration | |
| return available_fenestrations | |
| def get_parent_components(comp_type: str) -> Dict[str, Any]: | |
| """ | |
| Get appropriate parent components for a fenestration type. | |
| Args: | |
| comp_type: The type of fenestration component | |
| Returns: | |
| Dict of parent component name to parent component properties | |
| """ | |
| parent_components = {} | |
| if comp_type == "windows": | |
| # Windows can only be on walls | |
| if "walls" in st.session_state.project_data["components"]: | |
| for wall in st.session_state.project_data["components"]["walls"]: | |
| parent_components[wall["name"]] = wall | |
| elif comp_type == "skylights": | |
| # Skylights can only be on roofs | |
| if "roofs" in st.session_state.project_data["components"]: | |
| for roof in st.session_state.project_data["components"]["roofs"]: | |
| parent_components[roof["name"]] = roof | |
| return parent_components | |
| def get_material_data(material_name: str) -> Optional[Dict[str, Any]]: | |
| """ | |
| Get material data from session state. | |
| Args: | |
| material_name: Name of the material | |
| Returns: | |
| Dict of material properties or None if not found | |
| """ | |
| if "materials" in st.session_state.project_data: | |
| if "library" in st.session_state.project_data["materials"]: | |
| if material_name in st.session_state.project_data["materials"]["library"]: | |
| return st.session_state.project_data["materials"]["library"][material_name] | |
| if "project" in st.session_state.project_data["materials"]: | |
| if material_name in st.session_state.project_data["materials"]["project"]: | |
| return st.session_state.project_data["materials"]["project"][material_name] | |
| return None | |
| def display_components_help(): | |
| """ | |
| Display help information for the building components page. | |
| """ | |
| st.markdown(""" | |
| ### Building Components Help | |
| This section allows you to define the building envelope components using the constructions and fenestrations you've created. | |
| **Key Concepts:** | |
| * **Walls, Roofs, Floors**: Opaque components that use constructions from the Construction section. | |
| * **Windows, Skylights**: Transparent components that use fenestrations from the Material Library section. | |
| * **Surface Azimuth**: Direction the component faces, calculated as elevation plus rotation (0° = North, 90° = East, 180° = South, 270° = West). | |
| * **Surface Tilt**: Angle of the component relative to horizontal (0° = horizontal, 90° = vertical). | |
| * **Parent Component**: The wall or roof that contains a window or skylight. | |
| * **Shading Coefficient (SC)**: Factor for shading devices on windows/skylights (0.0 to 1.0). | |
| * **Shading Type**: Type of shading for windows/skylights (No Shading, Internal Shading, External Shading). | |
| * **Adiabatic**: Indicates if a wall, roof, or floor has no heat transfer across it (Yes/No). | |
| * **Ground Contact**: Indicates if a wall, roof, or floor is in contact with the ground (Yes/No). | |
| **Workflow:** | |
| 1. First define opaque components (walls, roofs, floors) using constructions. | |
| 2. Then define transparent components (windows, skylights) and assign them to parent components. | |
| 3. Ensure all major building envelope components are included for accurate load calculations. | |
| **Tips:** | |
| * Use elevation (A, B, C, D) and rotation to set the surface azimuth. | |
| * For roofs and skylights, set surface tilt to 0° for flat surfaces. | |
| * Ensure the total area of windows doesn't exceed the area of their parent walls. | |
| * Ensure the total area of skylights doesn't exceed the area of their parent roofs. | |
| * Set adiabatic to 'Yes' for components with no heat transfer, such as internal partitions. | |
| * Set ground contact to 'Yes' for components in direct contact with the ground for accurate heat transfer calculations. | |
| * Specify shading type for windows and skylights to account for different shading effects. | |
| """) |