mabuseif commited on
Commit
1ed2aaa
·
verified ·
1 Parent(s): 7563ab8

Update app/components.py

Browse files
Files changed (1) hide show
  1. app/components.py +226 -88
app/components.py CHANGED
@@ -1,8 +1,8 @@
1
  """
2
- HVAC Load Calculator - Building Components Module
3
 
4
  This module handles the definition of building envelope components (walls, roofs, floors,
5
- windows, doors, skylights) for the HVAC Load Calculator application. It allows users to
6
  assign constructions and fenestrations to specific components, define their areas,
7
  orientations, and other relevant properties.
8
 
@@ -25,7 +25,7 @@ logger = logging.getLogger(__name__)
25
  # Define constants
26
  COMPONENT_TYPES = ["Wall", "Roof", "Floor", "Window", "Door", "Skylight"]
27
  ORIENTATION_OPTIONS = ["A (North)", "B (South)", "C (East)", "D (West)", "Custom"]
28
- ORIENTATION_MAP = {'A (North)': 0.0, 'B (South)': 180.0, 'C (East)': 90.0, 'D (West)': 270.0, 'Custom': None}
29
 
30
  def display_components_page():
31
  """
@@ -156,7 +156,7 @@ def display_component_tab(comp_type: str):
156
  )
157
 
158
  # Component-specific inputs
159
- if comp_type in ["walls", "windows", "doors"]:
160
  # Elevation (orientation)
161
  elevation = st.selectbox(
162
  "Elevation",
@@ -165,51 +165,42 @@ def display_component_tab(comp_type: str):
165
  help="Orientation of the component relative to the building orientation angle."
166
  )
167
 
168
- # Custom rotation if elevation is custom
169
- rotation = 0.0
170
- if elevation == "Custom":
171
- rotation = st.number_input(
172
- "Rotation (°)",
173
- min_value=0.0,
174
- max_value=359.9,
175
- value=float(editor_state.get("rotation", 0.0)),
176
- format="%.1f",
177
- help="Custom rotation angle in degrees (0° = North, 90° = East, 180° = South, 270° = West)."
178
- )
179
- else:
180
- rotation = ORIENTATION_MAP[elevation]
181
-
182
- # Tilt
183
- tilt = st.number_input(
184
- "Tilt (°)",
185
- min_value=0.0,
186
- max_value=180.0,
187
- value=float(editor_state.get("tilt", 90.0)),
188
- format="%.1f",
189
- help="Tilt angle of the component relative to horizontal (0° = horizontal, 90° = vertical)."
190
- )
191
-
192
- elif comp_type in ["roofs", "skylights"]:
193
- # Orientation
194
- orientation = st.number_input(
195
- "Orientation (°)",
196
  min_value=0.0,
197
  max_value=359.9,
198
- value=float(editor_state.get("orientation", 0.0)),
199
  format="%.1f",
200
- help="Orientation angle in degrees (0° = North, 90° = East, 180° = South, 270° = West)."
201
  )
202
 
 
 
 
 
203
  # Tilt
 
204
  tilt = st.number_input(
205
  "Tilt (°)",
206
  min_value=0.0,
207
  max_value=180.0,
208
- value=float(editor_state.get("tilt", 30.0)),
209
  format="%.1f",
210
  help="Tilt angle of the component relative to horizontal (0° = horizontal, 90° = vertical)."
211
  )
212
 
 
 
 
 
 
 
 
 
 
 
 
213
  # Construction or fenestration selection
214
  if comp_type in ["walls", "roofs", "floors"]:
215
  construction_options = list(available_items.keys())
@@ -223,6 +214,43 @@ def display_component_tab(comp_type: str):
223
  index=construction_options.index(editor_state.get("construction", "")) if editor_state.get("construction", "") in construction_options else 0,
224
  help="Select the construction assembly for this component."
225
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  else: # Windows, doors, skylights
227
  # For fenestrations, we need to select a parent component
228
  parent_components = get_parent_components(comp_type)
@@ -250,6 +278,54 @@ def display_component_tab(comp_type: str):
250
  index=fenestration_options.index(editor_state.get("fenestration", "")) if editor_state.get("fenestration", "") in fenestration_options else 0,
251
  help="Select the fenestration type for this component."
252
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
  # Submit buttons
255
  col1, col2 = st.columns(2)
@@ -269,40 +345,49 @@ def display_component_tab(comp_type: str):
269
  "area": area
270
  }
271
 
272
- if comp_type in ["walls", "windows", "doors"]:
273
  component_data["elevation"] = elevation
274
  component_data["rotation"] = rotation
 
275
  component_data["tilt"] = tilt
276
 
277
- elif comp_type in ["roofs", "skylights"]:
278
- component_data["orientation"] = orientation
279
- component_data["tilt"] = tilt
280
 
281
  if comp_type in ["walls", "roofs", "floors"]:
282
  component_data["construction"] = construction
 
 
 
283
  # Get U-value from construction if available
284
  if construction in available_items:
285
  component_data["u_value"] = available_items[construction].get("u_value", 0.0)
286
  else:
287
  component_data["u_value"] = 0.0
288
- else:
289
  component_data["fenestration"] = fenestration
290
  component_data["parent_component"] = parent_component
291
  # Get U-value and other properties from fenestration if available
292
  if fenestration in available_items:
293
  component_data["u_value"] = available_items[fenestration].get("u_value", 0.0)
294
  if comp_type == "doors":
295
- component_data["solar_absorptivity"] = available_items[fenestration].get("solar_absorptivity", 0.7)
 
 
296
  else:
297
  component_data["shgc"] = available_items[fenestration].get("shgc", 0.7)
298
  component_data["visible_transmittance"] = available_items[fenestration].get("visible_transmittance", 0.7)
 
299
  else:
300
  component_data["u_value"] = 0.0
301
  if comp_type == "doors":
302
- component_data["solar_absorptivity"] = 0.7
 
 
303
  else:
304
  component_data["shgc"] = 0.7
305
  component_data["visible_transmittance"] = 0.7
 
306
 
307
  # Handle edit mode
308
  if is_edit:
@@ -327,29 +412,35 @@ def display_component_tab(comp_type: str):
327
  def display_wall_window_table(comp_type: str, components: List[Dict[str, Any]]):
328
  """Display a table of walls or windows with appropriate columns."""
329
  # Create column headers
330
- cols = st.columns([2, 1, 1, 1, 1, 1, 1, 1])
331
  cols[0].write("**Name**")
332
  cols[1].write("**U-Value**")
333
  cols[2].write("**Area (m²)**")
334
- cols[3].write("**Elevation**")
335
- cols[4].write("**Rotation (°)**")
336
- cols[5].write("**Tilt (°)**")
337
- cols[6].write("**Edit**")
338
- cols[7].write("**Delete**")
 
 
 
 
339
 
340
  # Display each component
341
  for idx, comp in enumerate(components):
342
- cols = st.columns([2, 1, 1, 1, 1, 1, 1, 1])
343
  cols[0].write(comp["name"])
344
  cols[1].write(f"{comp.get('u_value', 0.0):.3f}")
345
  cols[2].write(f"{comp['area']:.2f}")
346
- cols[3].write(comp.get("elevation", "N/A"))
347
- cols[4].write(f"{comp.get('rotation', 0.0):.1f}")
348
- cols[5].write(f"{comp.get('tilt', 90.0):.1f}")
 
349
 
350
  # Edit button
351
  edit_key = f"edit_{comp_type}_{comp['name']}_{idx}"
352
- with cols[6].container():
 
353
  if st.button("Edit", key=edit_key):
354
  editor_data = {
355
  "index": idx,
@@ -361,11 +452,15 @@ def display_wall_window_table(comp_type: str, components: List[Dict[str, Any]]):
361
  "is_edit": True
362
  }
363
 
364
- if comp_type in ["walls", "roofs", "floors"]:
365
  editor_data["construction"] = comp.get("construction", "")
366
- else: # Windows, doors, skylights
 
 
 
367
  editor_data["fenestration"] = comp.get("fenestration", "")
368
  editor_data["parent_component"] = comp.get("parent_component", "")
 
369
 
370
  st.session_state[f"{comp_type}_editor"] = editor_data
371
  st.session_state[f"{comp_type}_action"] = {"action": "edit", "id": str(uuid.uuid4())}
@@ -373,7 +468,8 @@ def display_wall_window_table(comp_type: str, components: List[Dict[str, Any]]):
373
 
374
  # Delete button
375
  delete_key = f"delete_{comp_type}_{comp['name']}_{idx}"
376
- with cols[7].container():
 
377
  if st.button("Delete", key=delete_key):
378
  st.session_state.project_data["components"][comp_type].pop(idx)
379
  st.success(f"{comp_type[:-1].capitalize()} '{comp['name']}' deleted!")
@@ -383,42 +479,55 @@ def display_wall_window_table(comp_type: str, components: List[Dict[str, Any]]):
383
  def display_roof_skylight_table(comp_type: str, components: List[Dict[str, Any]]):
384
  """Display a table of roofs or skylights with appropriate columns."""
385
  # Create column headers
386
- cols = st.columns([2, 1, 1, 1, 1, 1, 1])
387
  cols[0].write("**Name**")
388
  cols[1].write("**U-Value**")
389
  cols[2].write("**Area (m²)**")
390
- cols[3].write("**Orientation (°)**")
391
  cols[4].write("**Tilt (°)**")
392
- cols[5].write("**Edit**")
393
- cols[6].write("**Delete**")
 
 
 
 
 
394
 
395
  # Display each component
396
  for idx, comp in enumerate(components):
397
- cols = st.columns([2, 1, 1, 1, 1, 1, 1])
398
  cols[0].write(comp["name"])
399
  cols[1].write(f"{comp.get('u_value', 0.0):.3f}")
400
  cols[2].write(f"{comp['area']:.2f}")
401
- cols[3].write(f"{comp.get('orientation', 0.0):.1f}")
402
  cols[4].write(f"{comp.get('tilt', 0.0):.1f}")
 
 
403
 
404
  # Edit button
405
  edit_key = f"edit_{comp_type}_{comp['name']}_{idx}"
406
- with cols[5].container():
 
407
  if st.button("Edit", key=edit_key):
408
  editor_data = {
409
  "index": idx,
410
  "name": comp["name"],
411
  "area": comp["area"],
412
- "orientation": comp.get("orientation", 0.0),
 
413
  "tilt": comp.get("tilt", 0.0),
414
  "is_edit": True
415
  }
416
 
417
- if comp_type in ["roofs"]:
418
  editor_data["construction"] = comp.get("construction", "")
 
 
 
419
  else: # skylights
420
  editor_data["fenestration"] = comp.get("fenestration", "")
421
  editor_data["parent_component"] = comp.get("parent_component", "")
 
422
 
423
  st.session_state[f"{comp_type}_editor"] = editor_data
424
  st.session_state[f"{comp_type}_action"] = {"action": "edit", "id": str(uuid.uuid4())}
@@ -426,7 +535,8 @@ def display_roof_skylight_table(comp_type: str, components: List[Dict[str, Any]]
426
 
427
  # Delete button
428
  delete_key = f"delete_{comp_type}_{comp['name']}_{idx}"
429
- with cols[6].container():
 
430
  if st.button("Delete", key=delete_key):
431
  st.session_state.project_data["components"][comp_type].pop(idx)
432
  st.success(f"{comp_type[:-1].capitalize()} '{comp['name']}' deleted!")
@@ -436,29 +546,35 @@ def display_roof_skylight_table(comp_type: str, components: List[Dict[str, Any]]
436
  def display_floor_table(comp_type: str, components: List[Dict[str, Any]]):
437
  """Display a table of floors with appropriate columns."""
438
  # Create column headers
439
- cols = st.columns([2, 1, 1, 1, 1])
440
  cols[0].write("**Name**")
441
  cols[1].write("**U-Value**")
442
  cols[2].write("**Area (m²)**")
443
- cols[3].write("**Edit**")
444
- cols[4].write("**Delete**")
 
445
 
446
  # Display each component
447
  for idx, comp in enumerate(components):
448
- cols = st.columns([2, 1, 1, 1, 1])
449
  cols[0].write(comp["name"])
450
  cols[1].write(f"{comp.get('u_value', 0.0):.3f}")
451
  cols[2].write(f"{comp['area']:.2f}")
 
452
 
453
  # Edit button
454
  edit_key = f"edit_{comp_type}_{comp['name']}_{idx}"
455
- with cols[3].container():
456
  if st.button("Edit", key=edit_key):
457
  editor_data = {
458
  "index": idx,
459
  "name": comp["name"],
460
  "area": comp["area"],
461
  "construction": comp.get("construction", ""),
 
 
 
 
462
  "is_edit": True
463
  }
464
 
@@ -468,7 +584,7 @@ def display_floor_table(comp_type: str, components: List[Dict[str, Any]]):
468
 
469
  # Delete button
470
  delete_key = f"delete_{comp_type}_{comp['name']}_{idx}"
471
- with cols[4].container():
472
  if st.button("Delete", key=delete_key):
473
  st.session_state.project_data["components"][comp_type].pop(idx)
474
  st.success(f"{comp_type[:-1].capitalize()} '{comp['name']}' deleted!")
@@ -481,26 +597,24 @@ def display_door_table(comp_type: str, components: List[Dict[str, Any]]):
481
  cols = st.columns([2, 1, 1, 1, 1, 1, 1, 1])
482
  cols[0].write("**Name**")
483
  cols[1].write("**U-Value**")
484
- cols[2].write("**Solar Abs.**")
485
- cols[3].write("**Elevation**")
486
- cols[4].write("**Rotation (°)**")
487
- cols[5].write("**Tilt (°)**")
488
- cols[6].write("**Edit**")
489
- cols[7].write("**Delete**")
490
 
491
  # Display each component
492
  for idx, comp in enumerate(components):
493
  cols = st.columns([2, 1, 1, 1, 1, 1, 1, 1])
494
  cols[0].write(comp["name"])
495
  cols[1].write(f"{comp.get('u_value', 0.0):.3f}")
496
- cols[2].write(f"{comp.get('solar_absorptivity', 0.7):.2f}")
497
- cols[3].write(comp.get("elevation", "N/A"))
498
- cols[4].write(f"{comp.get('rotation', 0.0):.1f}")
499
- cols[5].write(f"{comp.get('tilt', 90.0):.1f}")
500
 
501
  # Edit button
502
  edit_key = f"edit_{comp_type}_{comp['name']}_{idx}"
503
- with cols[6].container():
504
  if st.button("Edit", key=edit_key):
505
  editor_data = {
506
  "index": idx,
@@ -511,6 +625,9 @@ def display_door_table(comp_type: str, components: List[Dict[str, Any]]):
511
  "tilt": comp.get("tilt", 90.0),
512
  "fenestration": comp.get("fenestration", ""),
513
  "parent_component": comp.get("parent_component", ""),
 
 
 
514
  "is_edit": True
515
  }
516
 
@@ -520,7 +637,7 @@ def display_door_table(comp_type: str, components: List[Dict[str, Any]]):
520
 
521
  # Delete button
522
  delete_key = f"delete_{comp_type}_{comp['name']}_{idx}"
523
- with cols[7].container():
524
  if st.button("Delete", key=delete_key):
525
  st.session_state.project_data["components"][comp_type].pop(idx)
526
  st.success(f"{comp_type[:-1].capitalize()} '{comp['name']}' deleted!")
@@ -597,6 +714,25 @@ def get_parent_components(comp_type: str) -> Dict[str, Any]:
597
 
598
  return parent_components
599
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
600
  def display_components_help():
601
  """
602
  Display help information for the building components page.
@@ -610,9 +746,11 @@ def display_components_help():
610
 
611
  * **Walls, Roofs, Floors**: Opaque components that use constructions from the Construction section.
612
  * **Windows, Doors, Skylights**: Transparent components that use fenestrations from the Material Library section.
613
- * **Orientation**: Direction the component faces (A=North, B=South, C=East, D=West).
614
  * **Tilt**: Angle of the component relative to horizontal (0° = horizontal, 90° = vertical).
615
  * **Parent Component**: The wall or roof that contains a window, door, or skylight.
 
 
616
 
617
  **Workflow:**
618
 
@@ -622,8 +760,8 @@ def display_components_help():
622
 
623
  **Tips:**
624
 
625
- * For walls, use the elevation options (A, B, C, D) to easily align with the building orientation.
626
- * For roofs and skylights, specify the orientation angle directly.
627
  * Ensure the total area of windows and doors doesn't exceed the area of their parent walls.
628
  * Ensure the total area of skylights doesn't exceed the area of their parent roofs.
629
- """)
 
1
  """
2
+ BuildSustain - Building Components Module
3
 
4
  This module handles the definition of building envelope components (walls, roofs, floors,
5
+ windows, doors, skylights) for the BuildSustain application. It allows users to
6
  assign constructions and fenestrations to specific components, define their areas,
7
  orientations, and other relevant properties.
8
 
 
25
  # Define constants
26
  COMPONENT_TYPES = ["Wall", "Roof", "Floor", "Window", "Door", "Skylight"]
27
  ORIENTATION_OPTIONS = ["A (North)", "B (South)", "C (East)", "D (West)", "Custom"]
28
+ ORIENTATION_MAP = {'A (North)': 0.0, 'B (South)': 180.0, 'C (East)': 90.0, 'D (West)': 270.0, 'Custom': 0.0}
29
 
30
  def display_components_page():
31
  """
 
156
  )
157
 
158
  # Component-specific inputs
159
+ if comp_type in ["walls", "windows", "doors", "roofs", "skylights"]:
160
  # Elevation (orientation)
161
  elevation = st.selectbox(
162
  "Elevation",
 
165
  help="Orientation of the component relative to the building orientation angle."
166
  )
167
 
168
+ # Rotation
169
+ rotation = st.number_input(
170
+ "Rotation (°)",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  min_value=0.0,
172
  max_value=359.9,
173
+ value=float(editor_state.get("rotation", 0.0)),
174
  format="%.1f",
175
+ help="Rotation angle in degrees to adjust the elevation (0° = North, 90° = East, 180° = South, 270° = West)."
176
  )
177
 
178
+ # Calculate surface azimuth
179
+ elevation_angle = ORIENTATION_MAP[elevation]
180
+ surface_azimuth = (elevation_angle + rotation) % 360.0
181
+
182
  # Tilt
183
+ default_tilt = 0.0 if comp_type in ["roofs", "skylights"] else 90.0
184
  tilt = st.number_input(
185
  "Tilt (°)",
186
  min_value=0.0,
187
  max_value=180.0,
188
+ value=float(editor_state.get("tilt", default_tilt)),
189
  format="%.1f",
190
  help="Tilt angle of the component relative to horizontal (0° = horizontal, 90° = vertical)."
191
  )
192
 
193
+ elif comp_type == "floors":
194
+ # Contact with ground percentage
195
+ contact_with_ground_percentage = st.number_input(
196
+ "Contact with Ground (%)",
197
+ min_value=0.0,
198
+ max_value=100.0,
199
+ value=float(editor_state.get("contact_with_ground_percentage", 0.0)),
200
+ format="%.1f",
201
+ help="Percentage of the floor in contact with the ground (0% = fully above ground, 100% = fully in contact)."
202
+ )
203
+
204
  # Construction or fenestration selection
205
  if comp_type in ["walls", "roofs", "floors"]:
206
  construction_options = list(available_items.keys())
 
214
  index=construction_options.index(editor_state.get("construction", "")) if editor_state.get("construction", "") in construction_options else 0,
215
  help="Select the construction assembly for this component."
216
  )
217
+
218
+ # Extract material properties from the first layer
219
+ absorptivity = 0.7
220
+ emissivity = 0.9
221
+ colour = "Unknown"
222
+ if construction in available_items and "layers" in available_items[construction]:
223
+ first_layer = available_items[construction]["layers"][0]
224
+ if "material" in first_layer:
225
+ material_data = get_material_data(first_layer["material"])
226
+ if material_data:
227
+ absorptivity = material_data.get("absorptivity", 0.7)
228
+ emissivity = material_data.get("emissivity", 0.9)
229
+ colour = material_data.get("colour", "Unknown")
230
+
231
+ # Material property inputs
232
+ absorptivity = st.number_input(
233
+ "Absorptivity",
234
+ min_value=0.0,
235
+ max_value=1.0,
236
+ value=float(editor_state.get("absorptivity", absorptivity)),
237
+ format="%.2f",
238
+ help="Solar absorptivity of the component's outer surface (0.0 to 1.0)."
239
+ )
240
+ emissivity = st.number_input(
241
+ "Emissivity",
242
+ min_value=0.0,
243
+ max_value=1.0,
244
+ value=float(editor_state.get("emissivity", emissivity)),
245
+ format="%.2f",
246
+ help="Thermal emissivity of the component's outer surface (0.0 to 1.0)."
247
+ )
248
+ colour = st.text_input(
249
+ "Colour",
250
+ value=editor_state.get("colour", colour),
251
+ help="Colour of the component's outer surface."
252
+ )
253
+
254
  else: # Windows, doors, skylights
255
  # For fenestrations, we need to select a parent component
256
  parent_components = get_parent_components(comp_type)
 
278
  index=fenestration_options.index(editor_state.get("fenestration", "")) if editor_state.get("fenestration", "") in fenestration_options else 0,
279
  help="Select the fenestration type for this component."
280
  )
281
+
282
+ if comp_type == "doors":
283
+ # Extract material properties from the first layer
284
+ absorptivity = 0.7
285
+ emissivity = 0.9
286
+ colour = "Unknown"
287
+ if fenestration in available_items and "layers" in available_items[fenestration]:
288
+ first_layer = available_items[fenestration]["layers"][0]
289
+ if "material" in first_layer:
290
+ material_data = get_material_data(first_layer["material"])
291
+ if material_data:
292
+ absorptivity = material_data.get("absorptivity", 0.7)
293
+ emissivity = material_data.get("emissivity", 0.9)
294
+ colour = material_data.get("colour", "Unknown")
295
+
296
+ # Material property inputs
297
+ absorptivity = st.number_input(
298
+ "Absorptivity",
299
+ min_value=0.0,
300
+ max_value=1.0,
301
+ value=float(editor_state.get("absorptivity", absorptivity)),
302
+ format="%.2f",
303
+ help="Solar absorptivity of the door's outer surface (0.0 to 1.0)."
304
+ )
305
+ emissivity = st.number_input(
306
+ "Emissivity",
307
+ min_value=0.0,
308
+ max_value=1.0,
309
+ value=float(editor_state.get("emissivity", emissivity)),
310
+ format="%.2f",
311
+ help="Thermal emissivity of the door's outer surface (0.0 to 1.0)."
312
+ )
313
+ colour = st.text_input(
314
+ "Colour",
315
+ value=editor_state.get("colour", colour),
316
+ help="Colour of the door's outer surface."
317
+ )
318
+
319
+ if comp_type in ["windows", "skylights"]:
320
+ # Shading Coefficient
321
+ shading_coefficient = st.number_input(
322
+ "Shading Coefficient (SC)",
323
+ min_value=0.0,
324
+ max_value=1.0,
325
+ value=float(editor_state.get("shading_coefficient", 1.0)),
326
+ format="%.2f",
327
+ help="Shading coefficient for external or internal shading devices (0.0 to 1.0)."
328
+ )
329
 
330
  # Submit buttons
331
  col1, col2 = st.columns(2)
 
345
  "area": area
346
  }
347
 
348
+ if comp_type in ["walls", "windows", "doors", "roofs", "skylights"]:
349
  component_data["elevation"] = elevation
350
  component_data["rotation"] = rotation
351
+ component_data["surface_azimuth"] = surface_azimuth
352
  component_data["tilt"] = tilt
353
 
354
+ if comp_type == "floors":
355
+ component_data["contact_with_ground_percentage"] = contact_with_ground_percentage
 
356
 
357
  if comp_type in ["walls", "roofs", "floors"]:
358
  component_data["construction"] = construction
359
+ component_data["absorptivity"] = absorptivity
360
+ component_data["emissivity"] = emissivity
361
+ component_data["colour"] = colour
362
  # Get U-value from construction if available
363
  if construction in available_items:
364
  component_data["u_value"] = available_items[construction].get("u_value", 0.0)
365
  else:
366
  component_data["u_value"] = 0.0
367
+ else: # Windows, doors, skylights
368
  component_data["fenestration"] = fenestration
369
  component_data["parent_component"] = parent_component
370
  # Get U-value and other properties from fenestration if available
371
  if fenestration in available_items:
372
  component_data["u_value"] = available_items[fenestration].get("u_value", 0.0)
373
  if comp_type == "doors":
374
+ component_data["absorptivity"] = absorptivity
375
+ component_data["emissivity"] = emissivity
376
+ component_data["colour"] = colour
377
  else:
378
  component_data["shgc"] = available_items[fenestration].get("shgc", 0.7)
379
  component_data["visible_transmittance"] = available_items[fenestration].get("visible_transmittance", 0.7)
380
+ component_data["shading_coefficient"] = shading_coefficient
381
  else:
382
  component_data["u_value"] = 0.0
383
  if comp_type == "doors":
384
+ component_data["absorptivity"] = absorptivity
385
+ component_data["emissivity"] = emissivity
386
+ component_data["colour"] = colour
387
  else:
388
  component_data["shgc"] = 0.7
389
  component_data["visible_transmittance"] = 0.7
390
+ component_data["shading_coefficient"] = shading_coefficient
391
 
392
  # Handle edit mode
393
  if is_edit:
 
412
  def display_wall_window_table(comp_type: str, components: List[Dict[str, Any]]):
413
  """Display a table of walls or windows with appropriate columns."""
414
  # Create column headers
415
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1] if comp_type == "walls" else [2, 1, 1, 1, 1, 1, 1, 1])
416
  cols[0].write("**Name**")
417
  cols[1].write("**U-Value**")
418
  cols[2].write("**Area (m²)**")
419
+ cols[3].write("**Surface Azimuth (°)**")
420
+ cols[4].write("**Tilt (°)**")
421
+ if comp_type == "walls":
422
+ cols[5].write("**Edit**")
423
+ cols[6].write("**Delete**")
424
+ else: # windows
425
+ cols[5].write("**SC**")
426
+ cols[6].write("**Edit**")
427
+ cols[7].write("**Delete**")
428
 
429
  # Display each component
430
  for idx, comp in enumerate(components):
431
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1] if comp_type == "walls" else [2, 1, 1, 1, 1, 1, 1, 1])
432
  cols[0].write(comp["name"])
433
  cols[1].write(f"{comp.get('u_value', 0.0):.3f}")
434
  cols[2].write(f"{comp['area']:.2f}")
435
+ cols[3].write(f"{comp.get('surface_azimuth', 0.0):.1f}")
436
+ cols[4].write(f"{comp.get('tilt', 90.0):.1f}")
437
+ if comp_type == "windows":
438
+ cols[5].write(f"{comp.get('shading_coefficient', 1.0):.2f}")
439
 
440
  # Edit button
441
  edit_key = f"edit_{comp_type}_{comp['name']}_{idx}"
442
+ edit_col = 5 if comp_type == "walls" else 6
443
+ with cols[edit_col].container():
444
  if st.button("Edit", key=edit_key):
445
  editor_data = {
446
  "index": idx,
 
452
  "is_edit": True
453
  }
454
 
455
+ if comp_type == "walls":
456
  editor_data["construction"] = comp.get("construction", "")
457
+ editor_data["absorptivity"] = comp.get("absorptivity", 0.7)
458
+ editor_data["emissivity"] = comp.get("emissivity", 0.9)
459
+ editor_data["colour"] = comp.get("colour", "Unknown")
460
+ else: # windows
461
  editor_data["fenestration"] = comp.get("fenestration", "")
462
  editor_data["parent_component"] = comp.get("parent_component", "")
463
+ editor_data["shading_coefficient"] = comp.get("shading_coefficient", 1.0)
464
 
465
  st.session_state[f"{comp_type}_editor"] = editor_data
466
  st.session_state[f"{comp_type}_action"] = {"action": "edit", "id": str(uuid.uuid4())}
 
468
 
469
  # Delete button
470
  delete_key = f"delete_{comp_type}_{comp['name']}_{idx}"
471
+ delete_col = 6 if comp_type == "walls" else 7
472
+ with cols[delete_col].container():
473
  if st.button("Delete", key=delete_key):
474
  st.session_state.project_data["components"][comp_type].pop(idx)
475
  st.success(f"{comp_type[:-1].capitalize()} '{comp['name']}' deleted!")
 
479
  def display_roof_skylight_table(comp_type: str, components: List[Dict[str, Any]]):
480
  """Display a table of roofs or skylights with appropriate columns."""
481
  # Create column headers
482
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1] if comp_type == "roofs" else [2, 1, 1, 1, 1, 1, 1, 1])
483
  cols[0].write("**Name**")
484
  cols[1].write("**U-Value**")
485
  cols[2].write("**Area (m²)**")
486
+ cols[3].write("**Surface Azimuth (°)**")
487
  cols[4].write("**Tilt (°)**")
488
+ if comp_type == "roofs":
489
+ cols[5].write("**Edit**")
490
+ cols[6].write("**Delete**")
491
+ else: # skylights
492
+ cols[5].write("**SC**")
493
+ cols[6].write("**Edit**")
494
+ cols[7].write("**Delete**")
495
 
496
  # Display each component
497
  for idx, comp in enumerate(components):
498
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1] if comp_type == "roofs" else [2, 1, 1, 1, 1, 1, 1, 1])
499
  cols[0].write(comp["name"])
500
  cols[1].write(f"{comp.get('u_value', 0.0):.3f}")
501
  cols[2].write(f"{comp['area']:.2f}")
502
+ cols[3].write(f"{comp.get('surface_azimuth', 0.0):.1f}")
503
  cols[4].write(f"{comp.get('tilt', 0.0):.1f}")
504
+ if comp_type == "skylights":
505
+ cols[5].write(f"{comp.get('shading_coefficient', 1.0):.2f}")
506
 
507
  # Edit button
508
  edit_key = f"edit_{comp_type}_{comp['name']}_{idx}"
509
+ edit_col = 5 if comp_type == "roofs" else 6
510
+ with cols[edit_col].container():
511
  if st.button("Edit", key=edit_key):
512
  editor_data = {
513
  "index": idx,
514
  "name": comp["name"],
515
  "area": comp["area"],
516
+ "elevation": comp.get("elevation", ORIENTATION_OPTIONS[0]),
517
+ "rotation": comp.get("rotation", 0.0),
518
  "tilt": comp.get("tilt", 0.0),
519
  "is_edit": True
520
  }
521
 
522
+ if comp_type == "roofs":
523
  editor_data["construction"] = comp.get("construction", "")
524
+ editor_data["absorptivity"] = comp.get("absorptivity", 0.7)
525
+ editor_data["emissivity"] = comp.get("emissivity", 0.9)
526
+ editor_data["colour"] = comp.get("colour", "Unknown")
527
  else: # skylights
528
  editor_data["fenestration"] = comp.get("fenestration", "")
529
  editor_data["parent_component"] = comp.get("parent_component", "")
530
+ editor_data["shading_coefficient"] = comp.get("shading_coefficient", 1.0)
531
 
532
  st.session_state[f"{comp_type}_editor"] = editor_data
533
  st.session_state[f"{comp_type}_action"] = {"action": "edit", "id": str(uuid.uuid4())}
 
535
 
536
  # Delete button
537
  delete_key = f"delete_{comp_type}_{comp['name']}_{idx}"
538
+ delete_col = 6 if comp_type == "roofs" else 7
539
+ with cols[delete_col].container():
540
  if st.button("Delete", key=delete_key):
541
  st.session_state.project_data["components"][comp_type].pop(idx)
542
  st.success(f"{comp_type[:-1].capitalize()} '{comp['name']}' deleted!")
 
546
  def display_floor_table(comp_type: str, components: List[Dict[str, Any]]):
547
  """Display a table of floors with appropriate columns."""
548
  # Create column headers
549
+ cols = st.columns([2, 1, 1, 1, 1, 1])
550
  cols[0].write("**Name**")
551
  cols[1].write("**U-Value**")
552
  cols[2].write("**Area (m²)**")
553
+ cols[3].write("**Contact with Ground (%)**")
554
+ cols[4].write("**Edit**")
555
+ cols[5].write("**Delete**")
556
 
557
  # Display each component
558
  for idx, comp in enumerate(components):
559
+ cols = st.columns([2, 1, 1, 1, 1, 1])
560
  cols[0].write(comp["name"])
561
  cols[1].write(f"{comp.get('u_value', 0.0):.3f}")
562
  cols[2].write(f"{comp['area']:.2f}")
563
+ cols[3].write(f"{comp.get('contact_with_ground_percentage', 0.0):.1f}")
564
 
565
  # Edit button
566
  edit_key = f"edit_{comp_type}_{comp['name']}_{idx}"
567
+ with cols[4].container():
568
  if st.button("Edit", key=edit_key):
569
  editor_data = {
570
  "index": idx,
571
  "name": comp["name"],
572
  "area": comp["area"],
573
  "construction": comp.get("construction", ""),
574
+ "contact_with_ground_percentage": comp.get("contact_with_ground_percentage", 0.0),
575
+ "absorptivity": comp.get("absorptivity", 0.7),
576
+ "emissivity": comp.get("emissivity", 0.9),
577
+ "colour": comp.get("colour", "Unknown"),
578
  "is_edit": True
579
  }
580
 
 
584
 
585
  # Delete button
586
  delete_key = f"delete_{comp_type}_{comp['name']}_{idx}"
587
+ with cols[5].container():
588
  if st.button("Delete", key=delete_key):
589
  st.session_state.project_data["components"][comp_type].pop(idx)
590
  st.success(f"{comp_type[:-1].capitalize()} '{comp['name']}' deleted!")
 
597
  cols = st.columns([2, 1, 1, 1, 1, 1, 1, 1])
598
  cols[0].write("**Name**")
599
  cols[1].write("**U-Value**")
600
+ cols[2].write("**Absorptivity**")
601
+ cols[3].write("**Surface Azimuth (°)**")
602
+ cols[4].write("**Tilt (°)**")
603
+ cols[5].write("**Edit**")
604
+ cols[6].write("**Delete**")
 
605
 
606
  # Display each component
607
  for idx, comp in enumerate(components):
608
  cols = st.columns([2, 1, 1, 1, 1, 1, 1, 1])
609
  cols[0].write(comp["name"])
610
  cols[1].write(f"{comp.get('u_value', 0.0):.3f}")
611
+ cols[2].write(f"{comp.get('absorptivity', 0.7):.2f}")
612
+ cols[3].write(f"{comp.get('surface_azimuth', 0.0):.1f}")
613
+ cols[4].write(f"{comp.get('tilt', 90.0):.1f}")
 
614
 
615
  # Edit button
616
  edit_key = f"edit_{comp_type}_{comp['name']}_{idx}"
617
+ with cols[5].container():
618
  if st.button("Edit", key=edit_key):
619
  editor_data = {
620
  "index": idx,
 
625
  "tilt": comp.get("tilt", 90.0),
626
  "fenestration": comp.get("fenestration", ""),
627
  "parent_component": comp.get("parent_component", ""),
628
+ "absorptivity": comp.get("absorptivity", 0.7),
629
+ "emissivity": comp.get("emissivity", 0.9),
630
+ "colour": comp.get("colour", "Unknown"),
631
  "is_edit": True
632
  }
633
 
 
637
 
638
  # Delete button
639
  delete_key = f"delete_{comp_type}_{comp['name']}_{idx}"
640
+ with cols[6].container():
641
  if st.button("Delete", key=delete_key):
642
  st.session_state.project_data["components"][comp_type].pop(idx)
643
  st.success(f"{comp_type[:-1].capitalize()} '{comp['name']}' deleted!")
 
714
 
715
  return parent_components
716
 
717
+ def get_material_data(material_name: str) -> Optional[Dict[str, Any]]:
718
+ """
719
+ Get material data from session state.
720
+
721
+ Args:
722
+ material_name: Name of the material
723
+
724
+ Returns:
725
+ Dict of material properties or None if not found
726
+ """
727
+ if "materials" in st.session_state.project_data:
728
+ if "library" in st.session_state.project_data["materials"]:
729
+ if material_name in st.session_state.project_data["materials"]["library"]:
730
+ return st.session_state.project_data["materials"]["library"][material_name]
731
+ if "project" in st.session_state.project_data["materials"]:
732
+ if material_name in st.session_state.project_data["materials"]["project"]:
733
+ return st.session_state.project_data["materials"]["project"][material_name]
734
+ return None
735
+
736
  def display_components_help():
737
  """
738
  Display help information for the building components page.
 
746
 
747
  * **Walls, Roofs, Floors**: Opaque components that use constructions from the Construction section.
748
  * **Windows, Doors, Skylights**: Transparent components that use fenestrations from the Material Library section.
749
+ * **Surface Azimuth**: Direction the component faces, calculated as elevation plus rotation (= North, 90° = East, 180° = South, 270° = West).
750
  * **Tilt**: Angle of the component relative to horizontal (0° = horizontal, 90° = vertical).
751
  * **Parent Component**: The wall or roof that contains a window, door, or skylight.
752
+ * **Absorptivity/Emissivity**: Thermal properties of the outer surface material.
753
+ * **Shading Coefficient (SC)**: Factor for shading devices on windows/skylights (0.0 to 1.0).
754
 
755
  **Workflow:**
756
 
 
760
 
761
  **Tips:**
762
 
763
+ * Use elevation (A, B, C, D) and rotation to set the surface azimuth.
764
+ * For roofs and skylights, set tilt to for flat surfaces.
765
  * Ensure the total area of windows and doors doesn't exceed the area of their parent walls.
766
  * Ensure the total area of skylights doesn't exceed the area of their parent roofs.
767
+ """)