test-29 / app /component_selection_redesign.py
mabuseif's picture
Upload 32 files
705acaf verified
"""
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()