""" Component selection interface for HVAC Load Calculator. This module provides the UI components for selecting building components. This is a redesigned version with simplified grouping by orientation. """ import streamlit as st import pandas as pd import numpy as np from typing import Dict, List, Any, Optional, Tuple import json import os import uuid # Import data models from data.building_components import Wall, Roof, Floor, Window, Door, Orientation, ComponentType from utils.component_library import ComponentLibrary from utils.u_value_calculator import UValueCalculator class ComponentSelectionRedesigned: """Class for redesigned component selection interface with orientation-based grouping.""" def __init__(self): """Initialize component selection interface.""" self.component_library = ComponentLibrary() self.u_value_calculator = UValueCalculator() def display(self) -> None: """Display component selection interface in Streamlit.""" # Check for pending actions in session state and process them self._process_pending_actions(st.session_state) self.display_component_selection(st.session_state) def _process_pending_actions(self, session_state: Dict[str, Any]) -> None: """ Process any pending actions stored in session state. Args: session_state: Streamlit session state """ # Ensure components structure exists if "components" not in session_state: session_state["components"] = { 'walls': [], 'roofs': [], 'floors': [], 'windows': [], 'doors': [] } # Initialize pending actions if not exists if "pending_actions" not in session_state: session_state["pending_actions"] = {} # Ensure all required pending action keys exist required_keys = [ "add_wall", "delete_wall", "add_roof", "delete_roof", "add_floor", "delete_floor", "add_window", "delete_window", "add_door", "delete_door" ] for key in required_keys: if key not in session_state["pending_actions"]: session_state["pending_actions"][key] = None # Process add wall action if session_state["pending_actions"].get("add_wall") is not None: new_wall = session_state["pending_actions"]["add_wall"] session_state["components"]["walls"].append(new_wall) session_state["pending_actions"]["add_wall"] = None # Process delete wall action if session_state["pending_actions"].get("delete_wall") is not None: wall_index = session_state["pending_actions"]["delete_wall"] if 0 <= wall_index < len(session_state["components"]["walls"]): session_state["components"]["walls"].pop(wall_index) session_state["pending_actions"]["delete_wall"] = None # Process add roof action if session_state["pending_actions"].get("add_roof") is not None: new_roof = session_state["pending_actions"]["add_roof"] session_state["components"]["roofs"].append(new_roof) session_state["pending_actions"]["add_roof"] = None # Process delete roof action if session_state["pending_actions"].get("delete_roof") is not None: roof_index = session_state["pending_actions"]["delete_roof"] if 0 <= roof_index < len(session_state["components"]["roofs"]): session_state["components"]["roofs"].pop(roof_index) session_state["pending_actions"]["delete_roof"] = None # Process add floor action if session_state["pending_actions"].get("add_floor") is not None: new_floor = session_state["pending_actions"]["add_floor"] session_state["components"]["floors"].append(new_floor) session_state["pending_actions"]["add_floor"] = None # Process delete floor action if session_state["pending_actions"].get("delete_floor") is not None: floor_index = session_state["pending_actions"]["delete_floor"] if 0 <= floor_index < len(session_state["components"]["floors"]): session_state["components"]["floors"].pop(floor_index) session_state["pending_actions"]["delete_floor"] = None # Process add window action if session_state["pending_actions"].get("add_window") is not None: new_window = session_state["pending_actions"]["add_window"] session_state["components"]["windows"].append(new_window) session_state["pending_actions"]["add_window"] = None # Process delete window action if session_state["pending_actions"].get("delete_window") is not None: window_index = session_state["pending_actions"]["delete_window"] if 0 <= window_index < len(session_state["components"]["windows"]): session_state["components"]["windows"].pop(window_index) session_state["pending_actions"]["delete_window"] = None # Process add door action if session_state["pending_actions"].get("add_door") is not None: new_door = session_state["pending_actions"]["add_door"] session_state["components"]["doors"].append(new_door) session_state["pending_actions"]["add_door"] = None # Process delete door action if session_state["pending_actions"].get("delete_door") is not None: door_index = session_state["pending_actions"]["delete_door"] if 0 <= door_index < len(session_state["components"]["doors"]): session_state["components"]["doors"].pop(door_index) session_state["pending_actions"]["delete_door"] = None def display_component_selection(self, session_state: Dict[str, Any]) -> None: """ Display component selection interface in Streamlit. Args: session_state: Streamlit session state for storing form data """ st.header("Building Components") # Initialize components in session state if not exists if "components" not in session_state: session_state["components"] = { "walls": [], "roofs": [], "floors": [], "windows": [], "doors": [] } # Initialize form submission flags if not exists if "form_submitted" not in session_state: session_state["form_submitted"] = { "wall_form": False, "roof_form": False, "floor_form": False, "window_form": False, "door_form": False } # Display components grouped by orientation self._display_components_by_orientation(session_state) # Display roof and floor components (using building info area) self._display_roof_floor_components(session_state) # Add component buttons st.subheader("Add New Components") col1, col2, col3, col4, col5 = st.columns(5) with col1: if st.button("Add Wall"): session_state["add_component_type"] = "wall" with col2: if st.button("Add Window"): session_state["add_component_type"] = "window" with col3: if st.button("Add Door"): session_state["add_component_type"] = "door" with col4: if st.button("Add Roof"): session_state["add_component_type"] = "roof" with col5: if st.button("Add Floor"): session_state["add_component_type"] = "floor" # Display appropriate form based on selected component type if "add_component_type" in session_state: component_type = session_state["add_component_type"] if component_type == "wall": self._display_wall_form(session_state) elif component_type == "window": self._display_window_form(session_state) elif component_type == "door": self._display_door_form(session_state) elif component_type == "roof": self._display_roof_form(session_state) elif component_type == "floor": self._display_floor_form(session_state) def _display_components_by_orientation(self, session_state: Dict[str, Any]) -> None: """ Display components grouped by orientation. Args: session_state: Streamlit session state """ st.subheader("Components by Orientation") # Get all components walls = session_state["components"]["walls"] windows = session_state["components"]["windows"] doors = session_state["components"]["doors"] # Group components by orientation orientations = [ Orientation.NORTH, Orientation.NORTHEAST, Orientation.EAST, Orientation.SOUTHEAST, Orientation.SOUTH, Orientation.SOUTHWEST, Orientation.WEST, Orientation.NORTHWEST ] # Check if there are any components to display has_components = len(walls) > 0 or len(windows) > 0 or len(doors) > 0 if not has_components: st.info("No components added yet. Use the buttons below to add components.") return # Display components for each orientation for orientation in orientations: # Filter components by orientation orientation_walls = [wall for wall in walls if wall.orientation == orientation] orientation_windows = [window for window in windows if window.orientation == orientation] orientation_doors = [door for door in doors if door.orientation == orientation] # Skip if no components for this orientation if not orientation_walls and not orientation_windows and not orientation_doors: continue # Display orientation header st.write(f"### {orientation.value} Components") # Create data for all components in this orientation component_data = [] # Add walls for i, wall in enumerate(orientation_walls): component_data.append({ "ID": f"W{i+1}", "Type": "Wall", "Name": wall.name, "U-Value (W/m²K)": f"{wall.u_value:.3f}", "Area (m²)": f"{wall.area:.2f}", "Group": wall.wall_group }) # Add windows for i, window in enumerate(orientation_windows): component_data.append({ "ID": f"G{i+1}", "Type": "Window", "Name": window.name, "U-Value (W/m²K)": f"{window.u_value:.3f}", "Area (m²)": f"{window.area:.2f}", "SHGC": f"{window.shgc:.2f}" }) # Add doors for i, door in enumerate(orientation_doors): component_data.append({ "ID": f"D{i+1}", "Type": "Door", "Name": door.name, "U-Value (W/m²K)": f"{door.u_value:.3f}", "Area (m²)": f"{door.area:.2f}", "Material": door.door_type }) # Display components table if component_data: component_df = pd.DataFrame(component_data) st.dataframe(component_df) # Add edit/delete options col1, col2, col3 = st.columns(3) with col1: # Wall edit/delete if orientation_walls: wall_to_modify = st.selectbox( f"Select wall ({orientation.value}):", options=range(len(orientation_walls)), format_func=lambda x: f"Wall #{x+1}: {orientation_walls[x].name}", key=f"wall_select_{orientation.value}" ) edit_col, delete_col = st.columns(2) with edit_col: if st.button("Edit", key=f"edit_wall_{orientation.value}"): # Find the global index of this wall global_index = walls.index(orientation_walls[wall_to_modify]) session_state["wall_to_edit"] = global_index session_state["add_component_type"] = "wall" with delete_col: if st.button("Delete", key=f"delete_wall_{orientation.value}"): # Find the global index of this wall global_index = walls.index(orientation_walls[wall_to_modify]) session_state["pending_actions"]["delete_wall"] = global_index with col2: # Window edit/delete if orientation_windows: window_to_modify = st.selectbox( f"Select window ({orientation.value}):", options=range(len(orientation_windows)), format_func=lambda x: f"Window #{x+1}: {orientation_windows[x].name}", key=f"window_select_{orientation.value}" ) edit_col, delete_col = st.columns(2) with edit_col: if st.button("Edit", key=f"edit_window_{orientation.value}"): # Find the global index of this window global_index = windows.index(orientation_windows[window_to_modify]) session_state["window_to_edit"] = global_index session_state["add_component_type"] = "window" with delete_col: if st.button("Delete", key=f"delete_window_{orientation.value}"): # Find the global index of this window global_index = windows.index(orientation_windows[window_to_modify]) session_state["pending_actions"]["delete_window"] = global_index with col3: # Door edit/delete if orientation_doors: door_to_modify = st.selectbox( f"Select door ({orientation.value}):", options=range(len(orientation_doors)), format_func=lambda x: f"Door #{x+1}: {orientation_doors[x].name}", key=f"door_select_{orientation.value}" ) edit_col, delete_col = st.columns(2) with edit_col: if st.button("Edit", key=f"edit_door_{orientation.value}"): # Find the global index of this door global_index = doors.index(orientation_doors[door_to_modify]) session_state["door_to_edit"] = global_index session_state["add_component_type"] = "door" with delete_col: if st.button("Delete", key=f"delete_door_{orientation.value}"): # Find the global index of this door global_index = doors.index(orientation_doors[door_to_modify]) session_state["pending_actions"]["delete_door"] = global_index st.markdown("---") def _display_roof_floor_components(self, session_state: Dict[str, Any]) -> None: """ Display roof and floor components. Args: session_state: Streamlit session state """ st.subheader("Roof and Floor Components") # Get roof and floor components roofs = session_state["components"]["roofs"] floors = session_state["components"]["floors"] # Check if there are any components to display has_components = len(roofs) > 0 or len(floors) > 0 if not has_components: st.info("No roof or floor components added yet. Use the buttons below to add components.") return # Display roof components if roofs: st.write("### Roof Components") # Create data for roof components roof_data = [] for i, roof in enumerate(roofs): roof_data.append({ "ID": i + 1, "Name": roof.name, "Type": roof.roof_type, "U-Value (W/m²K)": f"{roof.u_value:.3f}", "Area (m²)": f"{roof.area:.2f}", "Group": roof.roof_group }) # Display roof table roof_df = pd.DataFrame(roof_data) st.dataframe(roof_df) # Add edit/delete options col1, col2 = st.columns(2) with col1: roof_to_modify = st.selectbox( "Select roof to modify:", options=range(len(roofs)), format_func=lambda x: f"Roof #{x+1}: {roofs[x].name}" ) with col2: edit_col, delete_col = st.columns(2) with edit_col: if st.button("Edit Roof"): session_state["roof_to_edit"] = roof_to_modify session_state["add_component_type"] = "roof" with delete_col: if st.button("Delete Roof"): session_state["pending_actions"]["delete_roof"] = roof_to_modify # Display floor components if floors: st.write("### Floor Components") # Create data for floor components floor_data = [] for i, floor in enumerate(floors): floor_data.append({ "ID": i + 1, "Name": floor.name, "Type": floor.floor_type, "U-Value (W/m²K)": f"{floor.u_value:.3f}", "Area (m²)": f"{floor.area:.2f}", "Ground Contact": "Yes" if floor.is_ground_contact else "No" }) # Display floor table floor_df = pd.DataFrame(floor_data) st.dataframe(floor_df) # Add edit/delete options col1, col2 = st.columns(2) with col1: floor_to_modify = st.selectbox( "Select floor to modify:", options=range(len(floors)), format_func=lambda x: f"Floor #{x+1}: {floors[x].name}" ) with col2: edit_col, delete_col = st.columns(2) with edit_col: if st.button("Edit Floor"): session_state["floor_to_edit"] = floor_to_modify session_state["add_component_type"] = "floor" with delete_col: if st.button("Delete Floor"): session_state["pending_actions"]["delete_floor"] = floor_to_modify st.markdown("---") def _display_wall_form(self, session_state: Dict[str, Any]) -> None: """ Display wall form for adding or editing a wall. Args: session_state: Streamlit session state """ # Check if we're editing an existing wall editing_wall = "wall_to_edit" in session_state wall_to_edit = session_state.get("wall_to_edit", None) # Get the wall to edit if we're editing wall = None if editing_wall and wall_to_edit is not None: wall = session_state["components"]["walls"][wall_to_edit] # Form title if editing_wall: st.subheader(f"Edit Wall: {wall.name if wall else ''}") else: st.subheader("Add New Wall") # Wall form with st.form(key="wall_form"): # Wall name wall_name = st.text_input( "Wall Name:", value=wall.name if wall else "" ) # Wall type selection col1, col2 = st.columns(2) with col1: # Get preset wall types preset_walls = self.component_library.get_preset_components_by_type(ComponentType.WALL) preset_wall_names = [wall.name for wall in preset_walls] wall_type = st.selectbox( "Wall Type:", options=["Custom"] + preset_wall_names, index=preset_wall_names.index(wall.wall_type) + 1 if wall and wall.wall_type in preset_wall_names else 0 ) # U-value with col2: if wall_type == "Custom": u_value = st.number_input( "U-Value (W/m²K):", min_value=0.0, max_value=10.0, value=wall.u_value if wall else 0.5, step=0.01 ) else: # Get U-value from preset wall selected_wall = next((w for w in preset_walls if w.name == wall_type), None) u_value = selected_wall.u_value if selected_wall else 0.5 st.write(f"U-Value: {u_value:.3f} W/m²K") # Wall dimensions col1, col2 = st.columns(2) with col1: wall_height = st.number_input( "Wall Height (m):", min_value=0.1, max_value=50.0, value=3.0 if not wall else (wall.area / 10.0), # Assuming width of 10m if editing step=0.1 ) with col2: wall_width = st.number_input( "Wall Width (m):", min_value=0.1, max_value=100.0, value=10.0 if not wall else (wall.area / wall_height), step=0.1 ) # Calculate wall area wall_area = wall_height * wall_width st.write(f"Wall Area: {wall_area:.2f} m²") # Wall orientation orientation_options = [ Orientation.NORTH, Orientation.NORTHEAST, Orientation.EAST, Orientation.SOUTHEAST, Orientation.SOUTH, Orientation.SOUTHWEST, Orientation.WEST, Orientation.NORTHWEST ] orientation = st.selectbox( "Orientation:", options=orientation_options, index=orientation_options.index(wall.orientation) if wall else 0, format_func=lambda x: x.value ) # Wall color color_options = ["Light", "Medium", "Dark"] color = st.selectbox( "Color:", options=color_options, index=color_options.index(wall.color) if wall and wall.color in color_options else 1 ) # Submit button submit_label = "Update Wall" if editing_wall else "Add Wall" submit = st.form_submit_button(submit_label) if submit: # Create or update wall if wall_type == "Custom": # Create custom wall new_wall = Wall( id=f"custom_wall_{str(uuid.uuid4())[:8]}", name=wall_name, component_type=ComponentType.WALL, u_value=u_value, area=wall_area, orientation=orientation, color=color, wall_type="Custom", wall_group="Custom" ) else: # Clone preset wall and update properties selected_wall = next((w for w in preset_walls if w.name == wall_type), None) if selected_wall: new_wall = Wall( id=f"custom_wall_{str(uuid.uuid4())[:8]}", name=wall_name, component_type=ComponentType.WALL, u_value=selected_wall.u_value, area=wall_area, orientation=orientation, color=color, wall_type=selected_wall.wall_type, wall_group=selected_wall.wall_group, material_layers=selected_wall.material_layers ) # Add or update wall in session state if editing_wall: session_state["components"]["walls"][wall_to_edit] = new_wall del session_state["wall_to_edit"] else: # Queue the add action in pending_actions instead of doing it immediately session_state["pending_actions"]["add_wall"] = new_wall # Clear the component type selection del session_state["add_component_type"] st.experimental_rerun() # Cancel button outside the form if st.button("Cancel"): if editing_wall: del session_state["wall_to_edit"] del session_state["add_component_type"] st.experimental_rerun() def _display_window_form(self, session_state: Dict[str, Any]) -> None: """ Display window form for adding or editing a window. Args: session_state: Streamlit session state """ # Check if we're editing an existing window editing_window = "window_to_edit" in session_state window_to_edit = session_state.get("window_to_edit", None) # Get the window to edit if we're editing window = None if editing_window and window_to_edit is not None: window = session_state["components"]["windows"][window_to_edit] # Form title if editing_window: st.subheader(f"Edit Window: {window.name if window else ''}") else: st.subheader("Add New Window") # Window form with st.form(key="window_form"): # Window name window_name = st.text_input( "Window Name:", value=window.name if window else "" ) # Window type selection col1, col2 = st.columns(2) with col1: # Get preset window types preset_windows = self.component_library.get_preset_components_by_type(ComponentType.WINDOW) preset_window_names = [window.name for window in preset_windows] window_type = st.selectbox( "Window Type:", options=["Custom"] + preset_window_names, index=0 ) # U-value and SHGC with col2: if window_type == "Custom": u_value = st.number_input( "U-Value (W/m²K):", min_value=0.0, max_value=10.0, value=window.u_value if window else 2.8, step=0.01 ) else: # Get U-value from preset window selected_window = next((w for w in preset_windows if w.name == window_type), None) u_value = selected_window.u_value if selected_window else 2.8 st.write(f"U-Value: {u_value:.3f} W/m²K") # SHGC shgc = st.number_input( "Solar Heat Gain Coefficient (SHGC):", min_value=0.0, max_value=1.0, value=window.shgc if window else 0.7, step=0.01 ) # Window dimensions col1, col2 = st.columns(2) with col1: window_height = st.number_input( "Window Height (m):", min_value=0.1, max_value=10.0, value=1.2 if not window else (window.area / 1.5), # Assuming width of 1.5m if editing step=0.1 ) with col2: window_width = st.number_input( "Window Width (m):", min_value=0.1, max_value=10.0, value=1.5 if not window else (window.area / window_height), step=0.1 ) # Calculate window area window_area = window_height * window_width st.write(f"Window Area: {window_area:.2f} m²") # Window orientation orientation_options = [ Orientation.NORTH, Orientation.NORTHEAST, Orientation.EAST, Orientation.SOUTHEAST, Orientation.SOUTH, Orientation.SOUTHWEST, Orientation.WEST, Orientation.NORTHWEST ] orientation = st.selectbox( "Orientation:", options=orientation_options, index=orientation_options.index(window.orientation) if window else 0, format_func=lambda x: x.value ) # Shading options has_shading = st.checkbox( "Has Shading", value=window.has_shading if window else False ) if has_shading: shading_type_options = ["Internal", "External", "Between-glass"] shading_type = st.selectbox( "Shading Type:", options=shading_type_options, index=0 if not window or not window.shading_type else shading_type_options.index(window.shading_type) ) shading_coefficient = st.slider( "Shading Coefficient (0-1):", min_value=0.0, max_value=1.0, value=window.shading_coefficient if window else 0.7, step=0.01 ) else: shading_type = None shading_coefficient = 1.0 # Submit button submit_label = "Update Window" if editing_window else "Add Window" submit = st.form_submit_button(submit_label) if submit: # Create or update window if window_type == "Custom": # Create custom window new_window = Window( id=f"custom_window_{str(uuid.uuid4())[:8]}", name=window_name, component_type=ComponentType.WINDOW, u_value=u_value, area=window_area, orientation=orientation, shgc=shgc, has_shading=has_shading, shading_type=shading_type, shading_coefficient=shading_coefficient ) else: # Clone preset window and update properties selected_window = next((w for w in preset_windows if w.name == window_type), None) if selected_window: new_window = Window( id=f"custom_window_{str(uuid.uuid4())[:8]}", name=window_name, component_type=ComponentType.WINDOW, u_value=selected_window.u_value, area=window_area, orientation=orientation, shgc=selected_window.shgc if hasattr(selected_window, 'shgc') else shgc, has_shading=has_shading, shading_type=shading_type, shading_coefficient=shading_coefficient ) # Add or update window in session state if editing_window: session_state["components"]["windows"][window_to_edit] = new_window del session_state["window_to_edit"] else: # Queue the add action in pending_actions instead of doing it immediately session_state["pending_actions"]["add_window"] = new_window # Clear the component type selection del session_state["add_component_type"] st.experimental_rerun() # Cancel button outside the form if st.button("Cancel"): if editing_window: del session_state["window_to_edit"] del session_state["add_component_type"] st.experimental_rerun() def _display_door_form(self, session_state: Dict[str, Any]) -> None: """ Display door form for adding or editing a door. Args: session_state: Streamlit session state """ # Check if we're editing an existing door editing_door = "door_to_edit" in session_state door_to_edit = session_state.get("door_to_edit", None) # Get the door to edit if we're editing door = None if editing_door and door_to_edit is not None: door = session_state["components"]["doors"][door_to_edit] # Form title if editing_door: st.subheader(f"Edit Door: {door.name if door else ''}") else: st.subheader("Add New Door") # Door form with st.form(key="door_form"): # Door name door_name = st.text_input( "Door Name:", value=door.name if door else "" ) # Door type selection col1, col2 = st.columns(2) with col1: # Get preset door types preset_doors = self.component_library.get_preset_components_by_type(ComponentType.DOOR) preset_door_names = [door.name for door in preset_doors] door_type = st.selectbox( "Door Type:", options=["Custom"] + preset_door_names, index=0 ) # U-value with col2: if door_type == "Custom": u_value = st.number_input( "U-Value (W/m²K):", min_value=0.0, max_value=10.0, value=door.u_value if door else 2.0, step=0.01 ) else: # Get U-value from preset door selected_door = next((d for d in preset_doors if d.name == door_type), None) u_value = selected_door.u_value if selected_door else 2.0 st.write(f"U-Value: {u_value:.3f} W/m²K") # Door dimensions col1, col2 = st.columns(2) with col1: door_height = st.number_input( "Door Height (m):", min_value=0.1, max_value=5.0, value=2.0 if not door else (door.area / 0.9), # Assuming width of 0.9m if editing step=0.1 ) with col2: door_width = st.number_input( "Door Width (m):", min_value=0.1, max_value=5.0, value=0.9 if not door else (door.area / door_height), step=0.1 ) # Calculate door area door_area = door_height * door_width st.write(f"Door Area: {door_area:.2f} m²") # Door orientation orientation_options = [ Orientation.NORTH, Orientation.NORTHEAST, Orientation.EAST, Orientation.SOUTHEAST, Orientation.SOUTH, Orientation.SOUTHWEST, Orientation.WEST, Orientation.NORTHWEST ] orientation = st.selectbox( "Orientation:", options=orientation_options, index=orientation_options.index(door.orientation) if door else 0, format_func=lambda x: x.value ) # Submit button submit_label = "Update Door" if editing_door else "Add Door" submit = st.form_submit_button(submit_label) if submit: # Create or update door if door_type == "Custom": # Create custom door new_door = Door( id=f"custom_door_{str(uuid.uuid4())[:8]}", name=door_name, component_type=ComponentType.DOOR, u_value=u_value, area=door_area, orientation=orientation, door_type="Custom" ) else: # Clone preset door and update properties selected_door = next((d for d in preset_doors if d.name == door_type), None) if selected_door: new_door = Door( id=f"custom_door_{str(uuid.uuid4())[:8]}", name=door_name, component_type=ComponentType.DOOR, u_value=selected_door.u_value, area=door_area, orientation=orientation, door_type=selected_door.door_type ) # Add or update door in session state if editing_door: session_state["components"]["doors"][door_to_edit] = new_door del session_state["door_to_edit"] else: # Queue the add action in pending_actions instead of doing it immediately session_state["pending_actions"]["add_door"] = new_door # Clear the component type selection del session_state["add_component_type"] st.experimental_rerun() # Cancel button outside the form if st.button("Cancel"): if editing_door: del session_state["door_to_edit"] del session_state["add_component_type"] st.experimental_rerun() def _display_roof_form(self, session_state: Dict[str, Any]) -> None: """ Display roof form for adding or editing a roof. Args: session_state: Streamlit session state """ # Check if we're editing an existing roof editing_roof = "roof_to_edit" in session_state roof_to_edit = session_state.get("roof_to_edit", None) # Get the roof to edit if we're editing roof = None if editing_roof and roof_to_edit is not None: roof = session_state["components"]["roofs"][roof_to_edit] # Form title if editing_roof: st.subheader(f"Edit Roof: {roof.name if roof else ''}") else: st.subheader("Add New Roof") # Get building floor area from building info if available building_area = 0.0 if "building_info" in session_state and "floor_area" in session_state["building_info"]: building_area = session_state["building_info"]["floor_area"] # Roof form with st.form(key="roof_form"): # Roof name roof_name = st.text_input( "Roof Name:", value=roof.name if roof else "" ) # Roof type selection col1, col2 = st.columns(2) with col1: # Get preset roof types preset_roofs = self.component_library.get_preset_components_by_type(ComponentType.ROOF) preset_roof_names = [roof.name for roof in preset_roofs] roof_type = st.selectbox( "Roof Type:", options=["Custom"] + preset_roof_names, index=0 ) # U-value with col2: if roof_type == "Custom": u_value = st.number_input( "U-Value (W/m²K):", min_value=0.0, max_value=10.0, value=roof.u_value if roof else 0.25, step=0.01 ) else: # Get U-value from preset roof selected_roof = next((r for r in preset_roofs if r.name == roof_type), None) u_value = selected_roof.u_value if selected_roof else 0.25 st.write(f"U-Value: {u_value:.3f} W/m²K") # Roof area if building_area > 0: st.write(f"Building Floor Area: {building_area:.2f} m²") use_building_area = st.checkbox( "Use Building Floor Area", value=True ) if use_building_area: roof_area = building_area st.write(f"Roof Area: {roof_area:.2f} m²") else: roof_area = st.number_input( "Roof Area (m²):", min_value=0.1, max_value=10000.0, value=roof.area if roof else building_area, step=1.0 ) else: roof_area = st.number_input( "Roof Area (m²):", min_value=0.1, max_value=10000.0, value=roof.area if roof else 100.0, step=1.0 ) # Roof color color_options = ["Light", "Medium", "Dark"] color = st.selectbox( "Color:", options=color_options, index=color_options.index(roof.color) if roof and roof.color in color_options else 1 ) # Roof pitch roof_pitch = st.number_input( "Roof Pitch (degrees):", min_value=0.0, max_value=60.0, value=roof.pitch if roof else 0.0, step=1.0 ) # Submit button submit_label = "Update Roof" if editing_roof else "Add Roof" submit = st.form_submit_button(submit_label) if submit: # Create or update roof if roof_type == "Custom": # Create custom roof new_roof = Roof( id=f"custom_roof_{str(uuid.uuid4())[:8]}", name=roof_name, component_type=ComponentType.ROOF, u_value=u_value, area=roof_area, orientation=Orientation.HORIZONTAL, color=color, roof_type="Custom", roof_group="Custom", pitch=roof_pitch ) else: # Clone preset roof and update properties selected_roof = next((r for r in preset_roofs if r.name == roof_type), None) if selected_roof: new_roof = Roof( id=f"custom_roof_{str(uuid.uuid4())[:8]}", name=roof_name, component_type=ComponentType.ROOF, u_value=selected_roof.u_value, area=roof_area, orientation=Orientation.HORIZONTAL, color=color, roof_type=selected_roof.roof_type, roof_group=selected_roof.roof_group, pitch=roof_pitch, material_layers=selected_roof.material_layers ) # Add or update roof in session state if editing_roof: session_state["components"]["roofs"][roof_to_edit] = new_roof del session_state["roof_to_edit"] else: # Queue the add action in pending_actions instead of doing it immediately session_state["pending_actions"]["add_roof"] = new_roof # Clear the component type selection del session_state["add_component_type"] st.experimental_rerun() # Cancel button outside the form if st.button("Cancel"): if editing_roof: del session_state["roof_to_edit"] del session_state["add_component_type"] st.experimental_rerun() def _display_floor_form(self, session_state: Dict[str, Any]) -> None: """ Display floor form for adding or editing a floor. Args: session_state: Streamlit session state """ # Check if we're editing an existing floor editing_floor = "floor_to_edit" in session_state floor_to_edit = session_state.get("floor_to_edit", None) # Get the floor to edit if we're editing floor = None if editing_floor and floor_to_edit is not None: floor = session_state["components"]["floors"][floor_to_edit] # Form title if editing_floor: st.subheader(f"Edit Floor: {floor.name if floor else ''}") else: st.subheader("Add New Floor") # Get building floor area from building info if available building_area = 0.0 if "building_info" in session_state and "floor_area" in session_state["building_info"]: building_area = session_state["building_info"]["floor_area"] # Floor form with st.form(key="floor_form"): # Floor name floor_name = st.text_input( "Floor Name:", value=floor.name if floor else "" ) # Floor type selection col1, col2 = st.columns(2) with col1: # Get preset floor types preset_floors = self.component_library.get_preset_components_by_type(ComponentType.FLOOR) preset_floor_names = [floor.name for floor in preset_floors] floor_type = st.selectbox( "Floor Type:", options=["Custom"] + preset_floor_names, index=0 ) # U-value with col2: if floor_type == "Custom": u_value = st.number_input( "U-Value (W/m²K):", min_value=0.0, max_value=10.0, value=floor.u_value if floor else 0.25, step=0.01 ) else: # Get U-value from preset floor selected_floor = next((f for f in preset_floors if f.name == floor_type), None) u_value = selected_floor.u_value if selected_floor else 0.25 st.write(f"U-Value: {u_value:.3f} W/m²K") # Floor area if building_area > 0: st.write(f"Building Floor Area: {building_area:.2f} m²") use_building_area = st.checkbox( "Use Building Floor Area", value=True ) if use_building_area: floor_area = building_area st.write(f"Floor Area: {floor_area:.2f} m²") else: floor_area = st.number_input( "Floor Area (m²):", min_value=0.1, max_value=10000.0, value=floor.area if floor else building_area, step=1.0 ) else: floor_area = st.number_input( "Floor Area (m²):", min_value=0.1, max_value=10000.0, value=floor.area if floor else 100.0, step=1.0 ) # Ground contact is_ground_contact = st.checkbox( "Is in contact with ground", value=floor.is_ground_contact if floor else True ) # Perimeter length (for slab-on-grade floors) if is_ground_contact: perimeter_length = st.number_input( "Perimeter Length (m):", min_value=0.0, max_value=1000.0, value=floor.perimeter_length if floor else (4 * (floor_area ** 0.5)), # Estimate perimeter as 4 * sqrt(area) for a square step=1.0 ) else: perimeter_length = 0.0 # Submit button submit_label = "Update Floor" if editing_floor else "Add Floor" submit = st.form_submit_button(submit_label) if submit: # Create or update floor if floor_type == "Custom": # Create custom floor new_floor = Floor( id=f"custom_floor_{str(uuid.uuid4())[:8]}", name=floor_name, component_type=ComponentType.FLOOR, u_value=u_value, area=floor_area, orientation=Orientation.HORIZONTAL, floor_type="Custom", is_ground_contact=is_ground_contact, perimeter_length=perimeter_length ) else: # Clone preset floor and update properties selected_floor = next((f for f in preset_floors if f.name == floor_type), None) if selected_floor: new_floor = Floor( id=f"custom_floor_{str(uuid.uuid4())[:8]}", name=floor_name, component_type=ComponentType.FLOOR, u_value=selected_floor.u_value, area=floor_area, orientation=Orientation.HORIZONTAL, floor_type=selected_floor.floor_type, is_ground_contact=is_ground_contact, perimeter_length=perimeter_length, material_layers=selected_floor.material_layers ) # Add or update floor in session state if editing_floor: session_state["components"]["floors"][floor_to_edit] = new_floor del session_state["floor_to_edit"] else: # Queue the add action in pending_actions instead of doing it immediately session_state["pending_actions"]["add_floor"] = new_floor # Clear the component type selection del session_state["add_component_type"] st.experimental_rerun() # Cancel button outside the form if st.button("Cancel"): if editing_floor: del session_state["floor_to_edit"] del session_state["add_component_type"] st.experimental_rerun()