mabuseif commited on
Commit
83fc946
·
verified ·
1 Parent(s): 6f3badf

Update app/component_selection.py

Browse files
Files changed (1) hide show
  1. app/component_selection.py +147 -78
app/component_selection.py CHANGED
@@ -12,6 +12,7 @@ import uuid
12
  from dataclasses import dataclass, field
13
  from enum import Enum
14
  from typing import Dict, List, Any, Optional
 
15
 
16
  # --- Enums ---
17
  class Orientation(Enum):
@@ -77,14 +78,17 @@ class BuildingComponent:
77
  class Wall(BuildingComponent):
78
  wall_type: str = "Brick"
79
  wall_group: str = "B"
 
80
 
81
  def __post_init__(self):
82
  super().__post_init__()
83
  self.component_type = ComponentType.WALL
 
 
84
 
85
  def to_dict(self) -> dict:
86
  base_dict = super().to_dict()
87
- base_dict.update({"wall_type": self.wall_type, "wall_group": self.wall_group})
88
  return base_dict
89
 
90
  @dataclass
@@ -223,36 +227,28 @@ class ReferenceData:
223
  "mat_005": {"name": "Wood", "conductivity": 0.15}
224
  },
225
  "wall_types": {
226
- "preset_wall_001": {
227
- "id": "preset_wall_001", "name": "Standard Brick Wall", "component_type": "Wall",
228
- "u_value": 0.5, "area": 0.0, "orientation": "North", "wall_type": "Brick", "wall_group": "B"
229
- }
 
 
 
 
 
 
230
  },
231
  "roof_types": {
232
- "preset_roof_001": {
233
- "id": "preset_roof_001", "name": "Concrete Roof", "component_type": "Roof",
234
- "u_value": 0.3, "area": 0.0, "orientation": "Horizontal", "roof_type": "Concrete", "roof_group": "C"
235
- }
236
  },
237
  "floor_types": {
238
- "preset_floor_001": {
239
- "id": "preset_floor_001", "name": "Concrete Floor", "component_type": "Floor",
240
- "u_value": 0.4, "area": 0.0, "orientation": "Horizontal", "floor_type": "Concrete"
241
- }
242
  },
243
  "window_types": {
244
- "preset_window_001": {
245
- "id": "preset_window_001", "name": "Double Glazed Window", "component_type": "Window",
246
- "u_value": 2.8, "area": 0.0, "orientation": "North", "shgc": 0.7, "vt": 0.8,
247
- "window_type": "Double Glazed", "glazing_layers": 2, "gas_fill": "Air", "low_e_coating": False,
248
- "shading_device_id": "preset_none"
249
- }
250
  },
251
  "door_types": {
252
- "preset_door_001": {
253
- "id": "preset_door_001", "name": "Solid Wood Door", "component_type": "Door",
254
- "u_value": 2.0, "area": 0.0, "orientation": "North", "door_type": "Solid Wood"
255
- }
256
  }
257
  }
258
 
@@ -366,7 +362,10 @@ class ComponentSelectionInterface:
366
  st.subheader(f"{type_name.capitalize()} Components")
367
 
368
  with st.expander(f"Add {type_name.capitalize()}", expanded=False):
369
- self._display_add_component_form(session_state, component_type)
 
 
 
370
 
371
  components = session_state.components.get(type_name + 's', [])
372
  if components:
@@ -375,11 +374,87 @@ class ComponentSelectionInterface:
375
  else:
376
  st.info(f"No {type_name} components defined yet.")
377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
  def _display_add_component_form(self, session_state: Any, component_type: ComponentType) -> None:
379
  type_name = component_type.value.lower()
380
  preset_components = self.component_library.get_preset_components_by_type(component_type)
381
 
382
- # Add a flag to prevent infinite rerun
383
  if f"add_{type_name}_submitted" not in session_state:
384
  session_state[f"add_{type_name}_submitted"] = False
385
 
@@ -400,10 +475,7 @@ class ComponentSelectionInterface:
400
  selected_preset = st.selectbox(f"Select Preset {type_name.capitalize()}", options=list(preset_options.keys()))
401
  component = self.component_library.get_component(preset_options[selected_preset])
402
  u_value = st.number_input("U-Value (W/m²·K)", value=component.u_value, disabled=True)
403
- if component_type == ComponentType.WALL:
404
- st.text_input("Wall Type", value=component.wall_type, disabled=True)
405
- st.text_input("Wall Group", value=component.wall_group, disabled=True)
406
- elif component_type == ComponentType.ROOF:
407
  st.text_input("Roof Type", value=component.roof_type, disabled=True)
408
  st.text_input("Roof Group", value=component.roof_group, disabled=True)
409
  elif component_type == ComponentType.FLOOR:
@@ -416,10 +488,7 @@ class ComponentSelectionInterface:
416
  st.text_input("Door Type", value=component.door_type, disabled=True)
417
  else:
418
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=0.5, step=0.01)
419
- if component_type == ComponentType.WALL:
420
- wall_type = st.text_input("Wall Type", "Custom")
421
- wall_group = st.selectbox("Wall Group", ["A", "B", "C", "D", "E", "F", "G", "H"], index=1)
422
- elif component_type == ComponentType.ROOF:
423
  roof_type = st.text_input("Roof Type", "Custom")
424
  roof_group = st.selectbox("Roof Group", ["A", "B", "C", "D", "E", "F", "G"], index=2)
425
  elif component_type == ComponentType.FLOOR:
@@ -433,10 +502,6 @@ class ComponentSelectionInterface:
433
  low_e_coating = st.checkbox("Low-E Coating")
434
  shading_options = {d.name: d.id for d in self.shading_system.shading_devices.values()}
435
  shading_device = st.selectbox("Shading Device", options=list(shading_options.keys()), index=0)
436
- coverage = st.number_input("Coverage (%)", min_value=0.0, max_value=100.0, value=100.0, step=5.0)
437
- device = self.shading_system.get_device(shading_options[shading_device])
438
- device.coverage_percentage = coverage
439
- st.write(f"Effective SHGC: {self.shading_system.calculate_effective_shgc(shgc, shading_options[shading_device]):.2f}")
440
  elif component_type == ComponentType.DOOR:
441
  door_type = st.text_input("Door Type", "Custom")
442
 
@@ -453,21 +518,15 @@ class ComponentSelectionInterface:
453
  if selection_method == "Select from Presets" and preset_components:
454
  component_id = preset_options[selected_preset]
455
  component = self.component_library.get_component(component_id)
456
- # Create a copy of __dict__ and remove overlapping keys
457
  preset_attrs = component.__dict__.copy()
458
- for key in ['id', 'name', 'area', 'orientation']: # Remove keys we explicitly provide
459
  preset_attrs.pop(key, None)
460
  new_component = component.__class__(
461
  id=str(uuid.uuid4()), name=name, area=area,
462
  orientation=Orientation(orientation), **preset_attrs
463
  )
464
  else:
465
- if component_type == ComponentType.WALL:
466
- new_component = Wall(
467
- id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
468
- orientation=Orientation(orientation), wall_type=wall_type, wall_group=wall_group
469
- )
470
- elif component_type == ComponentType.ROOF:
471
  new_component = Roof(
472
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
473
  orientation=Orientation(orientation), roof_type=roof_type, roof_group=roof_group
@@ -478,7 +537,7 @@ class ComponentSelectionInterface:
478
  orientation=Orientation(orientation), floor_type=floor_type
479
  )
480
  elif component_type == ComponentType.WINDOW:
481
- new_component = Wall(
482
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
483
  orientation=Orientation(orientation), shgc=shgc, vt=vt, window_type=window_type,
484
  glazing_layers=glazing_layers, gas_fill=gas_fill, low_e_coating=low_e_coating,
@@ -492,12 +551,11 @@ class ComponentSelectionInterface:
492
  self.component_library.add_component(new_component)
493
  session_state.components[type_name + 's'].append(new_component)
494
  st.success(f"Added {new_component.name}")
495
- session_state[f"add_{type_name}_submitted"] = True # Set flag to prevent rerun loop
496
  st.rerun()
497
  except ValueError as e:
498
  st.error(f"Error: {str(e)}")
499
 
500
- # Reset the flag after rerun to allow new submissions
501
  if session_state[f"add_{type_name}_submitted"]:
502
  session_state[f"add_{type_name}_submitted"] = False
503
 
@@ -507,7 +565,7 @@ class ComponentSelectionInterface:
507
  for comp in components:
508
  row = {"Name": comp.name, "Area (m²)": comp.area, "U-Value (W/m²·K)": comp.u_value, "Orientation": comp.orientation.value, "ID": comp.id}
509
  if component_type == ComponentType.WALL:
510
- row.update({"Wall Type": comp.wall_type, "Wall Group": comp.wall_group})
511
  elif component_type == ComponentType.ROOF:
512
  row.update({"Roof Type": comp.roof_type, "Roof Group": comp.roof_group})
513
  elif component_type == ComponentType.FLOOR:
@@ -521,25 +579,49 @@ class ComponentSelectionInterface:
521
 
522
  df = pd.DataFrame(data)
523
  display_cols = [col for col in df.columns if col != "ID"]
524
- st.dataframe(df[display_cols], use_container_width=True)
525
 
526
- for i, row in df.iterrows():
527
- col1, col2 = st.columns(2)
528
- with col1:
529
- if st.button("Edit", key=f"edit_{row['ID']}"):
530
- session_state[f"edit_{type_name}"] = row["ID"]
531
- st.rerun()
532
- with col2:
533
- if st.button("Delete", key=f"delete_{row['ID']}"):
 
 
 
 
 
534
  if not row["ID"].startswith("preset_"):
535
  self.component_library.remove_component(row["ID"])
536
- session_state.components[type_name + 's'] = [c for c in components if c.id != row["ID"]]
537
  st.success(f"Deleted {row['Name']}")
538
  st.rerun()
539
  else:
540
  st.warning("Cannot delete preset components.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
 
542
- if f"edit_{type_name}" in session_state:
543
  self._display_edit_component_form(session_state, component_type, session_state[f"edit_{type_name}"])
544
  del session_state[f"edit_{type_name}"]
545
 
@@ -563,13 +645,9 @@ class ComponentSelectionInterface:
563
 
564
  with col2:
565
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=component.u_value, step=0.01)
566
- if component_type == ComponentType.WALL:
567
- wall_type = st.text_input("Wall Type", value=component.wall_type)
568
- wall_group = st.selectbox("Wall Group", ["A", "B", "C", "D", "E", "F", "G", "H"],
569
- index=["A", "B", "C", "D", "E", "F", "G", "H"].index(component.wall_group))
570
- elif component_type == ComponentType.ROOF:
571
- wall_type = st.text_input("Roof Type", value=component.roof_type)
572
- wall_group = st.selectbox("Roof Group", ["A", "B", "C", "D", "E", "F", "G"],
573
  index=["A", "B", "C", "D", "E", "F", "G"].index(component.roof_group))
574
  elif component_type == ComponentType.FLOOR:
575
  floor_type = st.text_input("Floor Type", value=component.floor_type)
@@ -584,11 +662,6 @@ class ComponentSelectionInterface:
584
  shading_options = {d.name: d.id for d in self.shading_system.shading_devices.values()}
585
  shading_device = st.selectbox("Shading Device", options=list(shading_options.keys()),
586
  index=list(shading_options.keys()).index(self.shading_system.get_device(component.shading_device_id).name))
587
- coverage = st.number_input("Coverage (%)", min_value=0.0, max_value=100.0,
588
- value=self.shading_system.get_device(component.shading_device_id).coverage_percentage, step=5.0)
589
- device = self.shading_system.get_device(shading_options[shading_device])
590
- device.coverage_percentage = coverage
591
- st.write(f"Effective SHGC: {self.shading_system.calculate_effective_shgc(shgc, shading_options[shading_device]):.2f}")
592
  elif component_type == ComponentType.DOOR:
593
  door_type = st.text_input("Door Type", value=component.door_type)
594
 
@@ -605,10 +678,7 @@ class ComponentSelectionInterface:
605
  component.area = area
606
  component.u_value = u_value
607
  component.orientation = Orientation(orientation)
608
- if component_type == ComponentType.WALL:
609
- component.wall_type = wall_type
610
- component.wall_group = wall_group
611
- elif component_type == ComponentType.ROOF:
612
  component.roof_type = roof_type
613
  component.roof_group = roof_group
614
  elif component_type == ComponentType.FLOOR:
@@ -670,10 +740,9 @@ class ComponentSelectionInterface:
670
  self.component_library.update_component(component.id, component)
671
  session_state.material_layers = []
672
  st.success(f"U-Value set to {u_value:.3f} W/m²·K for {component.name}")
673
- session_state.u_value_applied = True # Set flag to prevent rerun loop
674
  st.rerun()
675
 
676
- # Reset the flag after rerun to allow new calculations
677
  if session_state.u_value_applied:
678
  session_state.u_value_applied = False
679
 
 
12
  from dataclasses import dataclass, field
13
  from enum import Enum
14
  from typing import Dict, List, Any, Optional
15
+ import io
16
 
17
  # --- Enums ---
18
  class Orientation(Enum):
 
78
  class Wall(BuildingComponent):
79
  wall_type: str = "Brick"
80
  wall_group: str = "B"
81
+ shading_percentage: float = 0.0 # New attribute for shading percentage
82
 
83
  def __post_init__(self):
84
  super().__post_init__()
85
  self.component_type = ComponentType.WALL
86
+ if not 0 <= self.shading_percentage <= 100:
87
+ raise ValueError("Shading percentage must be between 0 and 100")
88
 
89
  def to_dict(self) -> dict:
90
  base_dict = super().to_dict()
91
+ base_dict.update({"wall_type": self.wall_type, "wall_group": self.wall_group, "shading_percentage": self.shading_percentage})
92
  return base_dict
93
 
94
  @dataclass
 
227
  "mat_005": {"name": "Wood", "conductivity": 0.15}
228
  },
229
  "wall_types": {
230
+ "preset_wall_001": {"id": "preset_wall_001", "name": "Brick Wall (Single Layer)", "component_type": "Wall", "u_value": 2.0, "area": 0.0, "orientation": "North", "wall_type": "Brick", "wall_group": "B", "shading_percentage": 0.0},
231
+ "preset_wall_002": {"id": "preset_wall_002", "name": "Insulated Brick Wall", "component_type": "Wall", "u_value": 0.5, "area": 0.0, "orientation": "North", "wall_type": "Insulated Brick", "wall_group": "B", "shading_percentage": 0.0},
232
+ "preset_wall_003": {"id": "preset_wall_003", "name": "Concrete Block Wall", "component_type": "Wall", "u_value": 1.8, "area": 0.0, "orientation": "North", "wall_type": "Concrete Block", "wall_group": "C", "shading_percentage": 0.0},
233
+ "preset_wall_004": {"id": "preset_wall_004", "name": "Insulated Concrete Wall", "component_type": "Wall", "u_value": 0.4, "area": 0.0, "orientation": "North", "wall_type": "Insulated Concrete", "wall_group": "C", "shading_percentage": 0.0},
234
+ "preset_wall_005": {"id": "preset_wall_005", "name": "Timber Frame Wall", "component_type": "Wall", "u_value": 0.3, "area": 0.0, "orientation": "North", "wall_type": "Timber Frame", "wall_group": "D", "shading_percentage": 0.0},
235
+ "preset_wall_006": {"id": "preset_wall_006", "name": "Cavity Wall (Brick + Insulation)", "component_type": "Wall", "u_value": 0.6, "area": 0.0, "orientation": "North", "wall_type": "Cavity Brick", "wall_group": "B", "shading_percentage": 0.0},
236
+ "preset_wall_007": {"id": "preset_wall_007", "name": "Lightweight Panel Wall", "component_type": "Wall", "u_value": 1.0, "area": 0.0, "orientation": "North", "wall_type": "Lightweight Panel", "wall_group": "E", "shading_percentage": 0.0},
237
+ "preset_wall_008": {"id": "preset_wall_008", "name": "Reinforced Concrete Wall", "component_type": "Wall", "u_value": 1.5, "area": 0.0, "orientation": "North", "wall_type": "Reinforced Concrete", "wall_group": "C", "shading_percentage": 0.0},
238
+ "preset_wall_009": {"id": "preset_wall_009", "name": "SIP Wall (Structural Insulated Panel)", "component_type": "Wall", "u_value": 0.25, "area": 0.0, "orientation": "North", "wall_type": "SIP", "wall_group": "F", "shading_percentage": 0.0},
239
+ "preset_wall_010": {"id": "preset_wall_010", "name": "Custom Wall", "component_type": "Wall", "u_value": 0.5, "area": 0.0, "orientation": "North", "wall_type": "Custom", "wall_group": "G", "shading_percentage": 0.0}
240
  },
241
  "roof_types": {
242
+ "preset_roof_001": {"id": "preset_roof_001", "name": "Concrete Roof", "component_type": "Roof", "u_value": 0.3, "area": 0.0, "orientation": "Horizontal", "roof_type": "Concrete", "roof_group": "C"}
 
 
 
243
  },
244
  "floor_types": {
245
+ "preset_floor_001": {"id": "preset_floor_001", "name": "Concrete Floor", "component_type": "Floor", "u_value": 0.4, "area": 0.0, "orientation": "Horizontal", "floor_type": "Concrete"}
 
 
 
246
  },
247
  "window_types": {
248
+ "preset_window_001": {"id": "preset_window_001", "name": "Double Glazed Window", "component_type": "Window", "u_value": 2.8, "area": 0.0, "orientation": "North", "shgc": 0.7, "vt": 0.8, "window_type": "Double Glazed", "glazing_layers": 2, "gas_fill": "Air", "low_e_coating": False, "shading_device_id": "preset_none"}
 
 
 
 
 
249
  },
250
  "door_types": {
251
+ "preset_door_001": {"id": "preset_door_001", "name": "Solid Wood Door", "component_type": "Door", "u_value": 2.0, "area": 0.0, "orientation": "North", "door_type": "Solid Wood"}
 
 
 
252
  }
253
  }
254
 
 
362
  st.subheader(f"{type_name.capitalize()} Components")
363
 
364
  with st.expander(f"Add {type_name.capitalize()}", expanded=False):
365
+ if component_type == ComponentType.WALL:
366
+ self._display_add_wall_form(session_state)
367
+ else:
368
+ self._display_add_component_form(session_state, component_type)
369
 
370
  components = session_state.components.get(type_name + 's', [])
371
  if components:
 
374
  else:
375
  st.info(f"No {type_name} components defined yet.")
376
 
377
+ def _display_add_wall_form(self, session_state: Any) -> None:
378
+ # Wall-specific form with manual entry and Excel upload options
379
+ st.write("Add walls manually or upload an Excel file.")
380
+ wall_method = st.radio("Add Wall Method", ["Manual Entry", "Excel Upload"])
381
+
382
+ if wall_method == "Manual Entry":
383
+ with st.form("add_wall_form"):
384
+ col1, col2 = st.columns(2)
385
+ with col1:
386
+ name = st.text_input("Name", "New Wall")
387
+ area = st.number_input("Area (m²)", min_value=0.0, value=1.0, step=0.1)
388
+ orientation = st.selectbox("Orientation", [o.value for o in Orientation], index=0)
389
+ with col2:
390
+ preset_walls = self.component_library.get_preset_components_by_type(ComponentType.WALL)
391
+ wall_options = {comp.name: comp for comp in preset_walls}
392
+ selected_wall = st.selectbox("Select Wall Type", options=list(wall_options.keys()))
393
+ component = wall_options[selected_wall]
394
+ if selected_wall == "Custom Wall":
395
+ u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, max_value=9.0, value=0.5, step=0.01)
396
+ else:
397
+ u_value = st.number_input("U-Value (W/m²·K)", value=component.u_value, disabled=True)
398
+ shading_percentage = st.number_input("Shading Percentage (%)", min_value=0.0, max_value=100.0, value=0.0, step=5.0)
399
+
400
+ submitted = st.form_submit_button("Add Wall")
401
+ if submitted:
402
+ if not name:
403
+ st.error("Wall name is required!")
404
+ elif area <= 0:
405
+ st.error("Wall area must be greater than zero!")
406
+ elif u_value <= 0 or u_value >= 9:
407
+ st.error("Wall U-value must be between 0 and 9!")
408
+ else:
409
+ new_wall = Wall(
410
+ id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
411
+ orientation=Orientation(orientation), wall_type=component.wall_type,
412
+ wall_group=component.wall_group, shading_percentage=shading_percentage
413
+ )
414
+ self.component_library.add_component(new_wall)
415
+ session_state.components['walls'].append(new_wall)
416
+ st.success(f"Added {new_wall.name}")
417
+ st.rerun()
418
+
419
+ elif wall_method == "Excel Upload":
420
+ st.info("Upload an Excel file with columns: Name, Area (m²), U-Value (W/m²·K), Orientation, Wall Type, Wall Group, Shading (%)")
421
+ uploaded_file = st.file_uploader("Upload Walls Excel", type=["xlsx"], key="wall_upload")
422
+ if uploaded_file and st.button("Process Excel"):
423
+ df = pd.read_excel(uploaded_file)
424
+ required_cols = ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Wall Type", "Wall Group", "Shading (%)"]
425
+ if not all(col in df.columns for col in required_cols):
426
+ st.error("Excel file must contain all required columns!")
427
+ else:
428
+ for _, row in df.iterrows():
429
+ try:
430
+ new_wall = Wall(
431
+ id=str(uuid.uuid4()), name=row["Name"], u_value=row["U-Value (W/m²·K)"],
432
+ area=row["Area (m²)"], orientation=Orientation(row["Orientation"]),
433
+ wall_type=row["Wall Type"], wall_group=row["Wall Group"],
434
+ shading_percentage=row["Shading (%)"]
435
+ )
436
+ self.component_library.add_component(new_wall)
437
+ session_state.components['walls'].append(new_wall)
438
+ except ValueError as e:
439
+ st.error(f"Error in row: {row['Name']} - {str(e)}")
440
+ st.success("Walls uploaded successfully!")
441
+ st.rerun()
442
+ # Provide a downloadable template
443
+ template_df = pd.DataFrame(columns=required_cols)
444
+ buffer = io.BytesIO()
445
+ with pd.ExcelWriter(buffer, engine='xlsxwriter') as writer:
446
+ template_df.to_excel(writer, index=False)
447
+ st.download_button(
448
+ label="Download Wall Template",
449
+ data=buffer.getvalue(),
450
+ file_name="wall_template.xlsx",
451
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
452
+ )
453
+
454
  def _display_add_component_form(self, session_state: Any, component_type: ComponentType) -> None:
455
  type_name = component_type.value.lower()
456
  preset_components = self.component_library.get_preset_components_by_type(component_type)
457
 
 
458
  if f"add_{type_name}_submitted" not in session_state:
459
  session_state[f"add_{type_name}_submitted"] = False
460
 
 
475
  selected_preset = st.selectbox(f"Select Preset {type_name.capitalize()}", options=list(preset_options.keys()))
476
  component = self.component_library.get_component(preset_options[selected_preset])
477
  u_value = st.number_input("U-Value (W/m²·K)", value=component.u_value, disabled=True)
478
+ if component_type == ComponentType.ROOF:
 
 
 
479
  st.text_input("Roof Type", value=component.roof_type, disabled=True)
480
  st.text_input("Roof Group", value=component.roof_group, disabled=True)
481
  elif component_type == ComponentType.FLOOR:
 
488
  st.text_input("Door Type", value=component.door_type, disabled=True)
489
  else:
490
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=0.5, step=0.01)
491
+ if component_type == ComponentType.ROOF:
 
 
 
492
  roof_type = st.text_input("Roof Type", "Custom")
493
  roof_group = st.selectbox("Roof Group", ["A", "B", "C", "D", "E", "F", "G"], index=2)
494
  elif component_type == ComponentType.FLOOR:
 
502
  low_e_coating = st.checkbox("Low-E Coating")
503
  shading_options = {d.name: d.id for d in self.shading_system.shading_devices.values()}
504
  shading_device = st.selectbox("Shading Device", options=list(shading_options.keys()), index=0)
 
 
 
 
505
  elif component_type == ComponentType.DOOR:
506
  door_type = st.text_input("Door Type", "Custom")
507
 
 
518
  if selection_method == "Select from Presets" and preset_components:
519
  component_id = preset_options[selected_preset]
520
  component = self.component_library.get_component(component_id)
 
521
  preset_attrs = component.__dict__.copy()
522
+ for key in ['id', 'name', 'area', 'orientation']:
523
  preset_attrs.pop(key, None)
524
  new_component = component.__class__(
525
  id=str(uuid.uuid4()), name=name, area=area,
526
  orientation=Orientation(orientation), **preset_attrs
527
  )
528
  else:
529
+ if component_type == ComponentType.ROOF:
 
 
 
 
 
530
  new_component = Roof(
531
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
532
  orientation=Orientation(orientation), roof_type=roof_type, roof_group=roof_group
 
537
  orientation=Orientation(orientation), floor_type=floor_type
538
  )
539
  elif component_type == ComponentType.WINDOW:
540
+ new_component = Window(
541
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
542
  orientation=Orientation(orientation), shgc=shgc, vt=vt, window_type=window_type,
543
  glazing_layers=glazing_layers, gas_fill=gas_fill, low_e_coating=low_e_coating,
 
551
  self.component_library.add_component(new_component)
552
  session_state.components[type_name + 's'].append(new_component)
553
  st.success(f"Added {new_component.name}")
554
+ session_state[f"add_{type_name}_submitted"] = True
555
  st.rerun()
556
  except ValueError as e:
557
  st.error(f"Error: {str(e)}")
558
 
 
559
  if session_state[f"add_{type_name}_submitted"]:
560
  session_state[f"add_{type_name}_submitted"] = False
561
 
 
565
  for comp in components:
566
  row = {"Name": comp.name, "Area (m²)": comp.area, "U-Value (W/m²·K)": comp.u_value, "Orientation": comp.orientation.value, "ID": comp.id}
567
  if component_type == ComponentType.WALL:
568
+ row.update({"Wall Type": comp.wall_type, "Wall Group": comp.wall_group, "Shading (%)": comp.shading_percentage})
569
  elif component_type == ComponentType.ROOF:
570
  row.update({"Roof Type": comp.roof_type, "Roof Group": comp.roof_group})
571
  elif component_type == ComponentType.FLOOR:
 
579
 
580
  df = pd.DataFrame(data)
581
  display_cols = [col for col in df.columns if col != "ID"]
 
582
 
583
+ if component_type == ComponentType.WALL:
584
+ # Custom table rendering for walls with delete button next to each row
585
+ st.write("Walls Table:")
586
+ for i, row in df.iterrows():
587
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1, 1])
588
+ cols[0].write(row["Name"])
589
+ cols[1].write(row["Area (m²)"])
590
+ cols[2].write(row["U-Value (W/m²·K)"])
591
+ cols[3].write(row["Orientation"])
592
+ cols[4].write(row["Wall Type"])
593
+ cols[5].write(row["Wall Group"])
594
+ cols[6].write(row["Shading (%)"])
595
+ if cols[7].button("Delete", key=f"delete_{row['ID']}"):
596
  if not row["ID"].startswith("preset_"):
597
  self.component_library.remove_component(row["ID"])
598
+ session_state.components['walls'] = [c for c in components if c.id != row["ID"]]
599
  st.success(f"Deleted {row['Name']}")
600
  st.rerun()
601
  else:
602
  st.warning("Cannot delete preset components.")
603
+ # Download walls table as CSV
604
+ csv = df[display_cols].to_csv(index=False)
605
+ st.download_button(label="Download Walls Table", data=csv, file_name="walls_table.csv", mime="text/csv")
606
+ else:
607
+ st.dataframe(df[display_cols], use_container_width=True)
608
+ for i, row in df.iterrows():
609
+ col1, col2 = st.columns(2)
610
+ with col1:
611
+ if st.button("Edit", key=f"edit_{row['ID']}"):
612
+ session_state[f"edit_{type_name}"] = row["ID"]
613
+ st .rerun()
614
+ with col2:
615
+ if st.button("Delete", key=f"delete_{row['ID']}"):
616
+ if not row["ID"].startswith("preset_"):
617
+ self.component_library.remove_component(row["ID"])
618
+ session_state.components[type_name + 's'] = [c for c in components if c.id != row["ID"]]
619
+ st.success(f"Deleted {row['Name']}")
620
+ st.rerun()
621
+ else:
622
+ st.warning("Cannot delete preset components.")
623
 
624
+ if f"edit_{type_name}" in session_state and component_type != ComponentType.WALL:
625
  self._display_edit_component_form(session_state, component_type, session_state[f"edit_{type_name}"])
626
  del session_state[f"edit_{type_name}"]
627
 
 
645
 
646
  with col2:
647
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=component.u_value, step=0.01)
648
+ if component_type == ComponentType.ROOF:
649
+ roof_type = st.text_input("Roof Type", value=component.roof_type)
650
+ roof_group = st.selectbox("Roof Group", ["A", "B", "C", "D", "E", "F", "G"],
 
 
 
 
651
  index=["A", "B", "C", "D", "E", "F", "G"].index(component.roof_group))
652
  elif component_type == ComponentType.FLOOR:
653
  floor_type = st.text_input("Floor Type", value=component.floor_type)
 
662
  shading_options = {d.name: d.id for d in self.shading_system.shading_devices.values()}
663
  shading_device = st.selectbox("Shading Device", options=list(shading_options.keys()),
664
  index=list(shading_options.keys()).index(self.shading_system.get_device(component.shading_device_id).name))
 
 
 
 
 
665
  elif component_type == ComponentType.DOOR:
666
  door_type = st.text_input("Door Type", value=component.door_type)
667
 
 
678
  component.area = area
679
  component.u_value = u_value
680
  component.orientation = Orientation(orientation)
681
+ if component_type == ComponentType.ROOF:
 
 
 
682
  component.roof_type = roof_type
683
  component.roof_group = roof_group
684
  elif component_type == ComponentType.FLOOR:
 
740
  self.component_library.update_component(component.id, component)
741
  session_state.material_layers = []
742
  st.success(f"U-Value set to {u_value:.3f} W/m²·K for {component.name}")
743
+ session_state.u_value_applied = True
744
  st.rerun()
745
 
 
746
  if session_state.u_value_applied:
747
  session_state.u_value_applied = False
748