|
|
""" |
|
|
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 |
|
|
|
|
|
|
|
|
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.""" |
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
if "components" not in session_state: |
|
|
session_state["components"] = { |
|
|
'walls': [], |
|
|
'roofs': [], |
|
|
'floors': [], |
|
|
'windows': [], |
|
|
'doors': [] |
|
|
} |
|
|
|
|
|
|
|
|
if "pending_actions" not in session_state: |
|
|
session_state["pending_actions"] = {} |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
if "components" not in session_state: |
|
|
session_state["components"] = { |
|
|
"walls": [], |
|
|
"roofs": [], |
|
|
"floors": [], |
|
|
"windows": [], |
|
|
"doors": [] |
|
|
} |
|
|
|
|
|
|
|
|
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 |
|
|
} |
|
|
|
|
|
|
|
|
self._display_components_by_orientation(session_state) |
|
|
|
|
|
|
|
|
self._display_roof_floor_components(session_state) |
|
|
|
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
walls = session_state["components"]["walls"] |
|
|
windows = session_state["components"]["windows"] |
|
|
doors = session_state["components"]["doors"] |
|
|
|
|
|
|
|
|
orientations = [ |
|
|
Orientation.NORTH, |
|
|
Orientation.NORTHEAST, |
|
|
Orientation.EAST, |
|
|
Orientation.SOUTHEAST, |
|
|
Orientation.SOUTH, |
|
|
Orientation.SOUTHWEST, |
|
|
Orientation.WEST, |
|
|
Orientation.NORTHWEST |
|
|
] |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
for orientation in orientations: |
|
|
|
|
|
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] |
|
|
|
|
|
|
|
|
if not orientation_walls and not orientation_windows and not orientation_doors: |
|
|
continue |
|
|
|
|
|
|
|
|
st.write(f"### {orientation.value} Components") |
|
|
|
|
|
|
|
|
component_data = [] |
|
|
|
|
|
|
|
|
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 |
|
|
}) |
|
|
|
|
|
|
|
|
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}" |
|
|
}) |
|
|
|
|
|
|
|
|
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 |
|
|
}) |
|
|
|
|
|
|
|
|
if component_data: |
|
|
component_df = pd.DataFrame(component_data) |
|
|
st.dataframe(component_df) |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
|
|
with col1: |
|
|
|
|
|
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}"): |
|
|
|
|
|
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}"): |
|
|
|
|
|
global_index = walls.index(orientation_walls[wall_to_modify]) |
|
|
session_state["pending_actions"]["delete_wall"] = global_index |
|
|
|
|
|
with col2: |
|
|
|
|
|
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}"): |
|
|
|
|
|
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}"): |
|
|
|
|
|
global_index = windows.index(orientation_windows[window_to_modify]) |
|
|
session_state["pending_actions"]["delete_window"] = global_index |
|
|
|
|
|
with col3: |
|
|
|
|
|
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}"): |
|
|
|
|
|
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}"): |
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
roofs = session_state["components"]["roofs"] |
|
|
floors = session_state["components"]["floors"] |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
if roofs: |
|
|
st.write("### 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 |
|
|
}) |
|
|
|
|
|
|
|
|
roof_df = pd.DataFrame(roof_data) |
|
|
st.dataframe(roof_df) |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
if floors: |
|
|
st.write("### 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" |
|
|
}) |
|
|
|
|
|
|
|
|
floor_df = pd.DataFrame(floor_data) |
|
|
st.dataframe(floor_df) |
|
|
|
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
editing_wall = "wall_to_edit" in session_state |
|
|
wall_to_edit = session_state.get("wall_to_edit", None) |
|
|
|
|
|
|
|
|
wall = None |
|
|
if editing_wall and wall_to_edit is not None: |
|
|
wall = session_state["components"]["walls"][wall_to_edit] |
|
|
|
|
|
|
|
|
if editing_wall: |
|
|
st.subheader(f"Edit Wall: {wall.name if wall else ''}") |
|
|
else: |
|
|
st.subheader("Add New Wall") |
|
|
|
|
|
|
|
|
with st.form(key="wall_form"): |
|
|
|
|
|
wall_name = st.text_input( |
|
|
"Wall Name:", |
|
|
value=wall.name if wall else "" |
|
|
) |
|
|
|
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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), |
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
wall_area = wall_height * wall_width |
|
|
st.write(f"Wall Area: {wall_area:.2f} m²") |
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
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_label = "Update Wall" if editing_wall else "Add Wall" |
|
|
submit = st.form_submit_button(submit_label) |
|
|
|
|
|
if submit: |
|
|
|
|
|
if wall_type == "Custom": |
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
if editing_wall: |
|
|
session_state["components"]["walls"][wall_to_edit] = new_wall |
|
|
del session_state["wall_to_edit"] |
|
|
else: |
|
|
|
|
|
session_state["pending_actions"]["add_wall"] = new_wall |
|
|
|
|
|
|
|
|
del session_state["add_component_type"] |
|
|
st.experimental_rerun() |
|
|
|
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
editing_window = "window_to_edit" in session_state |
|
|
window_to_edit = session_state.get("window_to_edit", None) |
|
|
|
|
|
|
|
|
window = None |
|
|
if editing_window and window_to_edit is not None: |
|
|
window = session_state["components"]["windows"][window_to_edit] |
|
|
|
|
|
|
|
|
if editing_window: |
|
|
st.subheader(f"Edit Window: {window.name if window else ''}") |
|
|
else: |
|
|
st.subheader("Add New Window") |
|
|
|
|
|
|
|
|
with st.form(key="window_form"): |
|
|
|
|
|
window_name = st.text_input( |
|
|
"Window Name:", |
|
|
value=window.name if window else "" |
|
|
) |
|
|
|
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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 = 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 |
|
|
) |
|
|
|
|
|
|
|
|
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), |
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
window_area = window_height * window_width |
|
|
st.write(f"Window Area: {window_area:.2f} m²") |
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
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_label = "Update Window" if editing_window else "Add Window" |
|
|
submit = st.form_submit_button(submit_label) |
|
|
|
|
|
if submit: |
|
|
|
|
|
if window_type == "Custom": |
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
if editing_window: |
|
|
session_state["components"]["windows"][window_to_edit] = new_window |
|
|
del session_state["window_to_edit"] |
|
|
else: |
|
|
|
|
|
session_state["pending_actions"]["add_window"] = new_window |
|
|
|
|
|
|
|
|
del session_state["add_component_type"] |
|
|
st.experimental_rerun() |
|
|
|
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
editing_door = "door_to_edit" in session_state |
|
|
door_to_edit = session_state.get("door_to_edit", None) |
|
|
|
|
|
|
|
|
door = None |
|
|
if editing_door and door_to_edit is not None: |
|
|
door = session_state["components"]["doors"][door_to_edit] |
|
|
|
|
|
|
|
|
if editing_door: |
|
|
st.subheader(f"Edit Door: {door.name if door else ''}") |
|
|
else: |
|
|
st.subheader("Add New Door") |
|
|
|
|
|
|
|
|
with st.form(key="door_form"): |
|
|
|
|
|
door_name = st.text_input( |
|
|
"Door Name:", |
|
|
value=door.name if door else "" |
|
|
) |
|
|
|
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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), |
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
door_area = door_height * door_width |
|
|
st.write(f"Door Area: {door_area:.2f} m²") |
|
|
|
|
|
|
|
|
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_label = "Update Door" if editing_door else "Add Door" |
|
|
submit = st.form_submit_button(submit_label) |
|
|
|
|
|
if submit: |
|
|
|
|
|
if door_type == "Custom": |
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
if editing_door: |
|
|
session_state["components"]["doors"][door_to_edit] = new_door |
|
|
del session_state["door_to_edit"] |
|
|
else: |
|
|
|
|
|
session_state["pending_actions"]["add_door"] = new_door |
|
|
|
|
|
|
|
|
del session_state["add_component_type"] |
|
|
st.experimental_rerun() |
|
|
|
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
editing_roof = "roof_to_edit" in session_state |
|
|
roof_to_edit = session_state.get("roof_to_edit", None) |
|
|
|
|
|
|
|
|
roof = None |
|
|
if editing_roof and roof_to_edit is not None: |
|
|
roof = session_state["components"]["roofs"][roof_to_edit] |
|
|
|
|
|
|
|
|
if editing_roof: |
|
|
st.subheader(f"Edit Roof: {roof.name if roof else ''}") |
|
|
else: |
|
|
st.subheader("Add New Roof") |
|
|
|
|
|
|
|
|
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"] |
|
|
|
|
|
|
|
|
with st.form(key="roof_form"): |
|
|
|
|
|
roof_name = st.text_input( |
|
|
"Roof Name:", |
|
|
value=roof.name if roof else "" |
|
|
) |
|
|
|
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
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 = 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_label = "Update Roof" if editing_roof else "Add Roof" |
|
|
submit = st.form_submit_button(submit_label) |
|
|
|
|
|
if submit: |
|
|
|
|
|
if roof_type == "Custom": |
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
if editing_roof: |
|
|
session_state["components"]["roofs"][roof_to_edit] = new_roof |
|
|
del session_state["roof_to_edit"] |
|
|
else: |
|
|
|
|
|
session_state["pending_actions"]["add_roof"] = new_roof |
|
|
|
|
|
|
|
|
del session_state["add_component_type"] |
|
|
st.experimental_rerun() |
|
|
|
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
editing_floor = "floor_to_edit" in session_state |
|
|
floor_to_edit = session_state.get("floor_to_edit", None) |
|
|
|
|
|
|
|
|
floor = None |
|
|
if editing_floor and floor_to_edit is not None: |
|
|
floor = session_state["components"]["floors"][floor_to_edit] |
|
|
|
|
|
|
|
|
if editing_floor: |
|
|
st.subheader(f"Edit Floor: {floor.name if floor else ''}") |
|
|
else: |
|
|
st.subheader("Add New Floor") |
|
|
|
|
|
|
|
|
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"] |
|
|
|
|
|
|
|
|
with st.form(key="floor_form"): |
|
|
|
|
|
floor_name = st.text_input( |
|
|
"Floor Name:", |
|
|
value=floor.name if floor else "" |
|
|
) |
|
|
|
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
is_ground_contact = st.checkbox( |
|
|
"Is in contact with ground", |
|
|
value=floor.is_ground_contact if floor else True |
|
|
) |
|
|
|
|
|
|
|
|
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)), |
|
|
step=1.0 |
|
|
) |
|
|
else: |
|
|
perimeter_length = 0.0 |
|
|
|
|
|
|
|
|
submit_label = "Update Floor" if editing_floor else "Add Floor" |
|
|
submit = st.form_submit_button(submit_label) |
|
|
|
|
|
if submit: |
|
|
|
|
|
if floor_type == "Custom": |
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
if editing_floor: |
|
|
session_state["components"]["floors"][floor_to_edit] = new_floor |
|
|
del session_state["floor_to_edit"] |
|
|
else: |
|
|
|
|
|
session_state["pending_actions"]["add_floor"] = new_floor |
|
|
|
|
|
|
|
|
del session_state["add_component_type"] |
|
|
st.experimental_rerun() |
|
|
|
|
|
|
|
|
if st.button("Cancel"): |
|
|
if editing_floor: |
|
|
del session_state["floor_to_edit"] |
|
|
del session_state["add_component_type"] |
|
|
st.experimental_rerun() |
|
|
|