mabuseif commited on
Commit
aa4e0fb
·
verified ·
1 Parent(s): 266fe45

Update app/materials_library.py

Browse files
Files changed (1) hide show
  1. app/materials_library.py +594 -1041
app/materials_library.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- BuildSustain - Material Library Module
3
 
4
  This module handles the material library functionality of the BuildSustain application,
5
  allowing users to manage building materials and fenestrations (windows, doors, skylights).
@@ -17,16 +17,20 @@ import logging
17
  import uuid
18
  from typing import Dict, List, Any, Optional, Tuple, Union
19
 
 
 
 
20
  # Configure logging
21
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
22
  logger = logging.getLogger(__name__)
23
 
24
  # Define constants
25
  MATERIAL_CATEGORIES = [
26
- "Finishing Materials",
27
- "Structural Materials",
28
- "Sub-Structural Materials",
29
  "Insulation",
 
 
 
30
  "Custom"
31
  ]
32
 
@@ -37,265 +41,29 @@ FENESTRATION_TYPES = [
37
  "Custom"
38
  ]
39
 
40
- # Default library materials
41
- DEFAULT_MATERIALS = {
42
- "Brick": {
43
- "category": "Structural Materials",
44
- "thermal_conductivity": 0.72,
45
- "density": 1920.0,
46
- "specific_heat": 840.0,
47
- "thickness_range": [0.05, 0.3],
48
- "default_thickness": 0.1,
49
- "embodied_carbon": 240.0, # kg CO2e/m³
50
- "cost": 180.0 # USD/m³
51
- },
52
- "Concrete": {
53
- "category": "Structural Materials",
54
- "thermal_conductivity": 1.4,
55
- "density": 2300.0,
56
- "specific_heat": 880.0,
57
- "thickness_range": [0.05, 0.5],
58
- "default_thickness": 0.15,
59
- "embodied_carbon": 320.0,
60
- "cost": 120.0
61
- },
62
- "Gypsum Board": {
63
- "category": "Finishing Materials",
64
- "thermal_conductivity": 0.25,
65
- "density": 900.0,
66
- "specific_heat": 1000.0,
67
- "thickness_range": [0.01, 0.05],
68
- "default_thickness": 0.0125,
69
- "embodied_carbon": 120.0,
70
- "cost": 90.0
71
- },
72
- "Mineral Wool": {
73
- "category": "Insulation",
74
- "thermal_conductivity": 0.04,
75
- "density": 30.0,
76
- "specific_heat": 840.0,
77
- "thickness_range": [0.025, 0.3],
78
- "default_thickness": 0.1,
79
- "embodied_carbon": 45.0,
80
- "cost": 70.0
81
- },
82
- "EPS Insulation": {
83
- "category": "Insulation",
84
- "thermal_conductivity": 0.035,
85
- "density": 25.0,
86
- "specific_heat": 1400.0,
87
- "thickness_range": [0.025, 0.3],
88
- "default_thickness": 0.1,
89
- "embodied_carbon": 88.0,
90
- "cost": 65.0
91
- },
92
- "Wood (Pine)": {
93
- "category": "Structural Materials",
94
- "thermal_conductivity": 0.14,
95
- "density": 550.0,
96
- "specific_heat": 1600.0,
97
- "thickness_range": [0.01, 0.3],
98
- "default_thickness": 0.05,
99
- "embodied_carbon": 110.0,
100
- "cost": 250.0
101
- },
102
- "Steel": {
103
- "category": "Structural Materials",
104
- "thermal_conductivity": 50.0,
105
- "density": 7800.0,
106
- "specific_heat": 450.0,
107
- "thickness_range": [0.001, 0.05],
108
- "default_thickness": 0.005,
109
- "embodied_carbon": 1750.0,
110
- "cost": 800.0
111
- },
112
- "Aluminum": {
113
- "category": "Structural Materials",
114
- "thermal_conductivity": 230.0,
115
- "density": 2700.0,
116
- "specific_heat": 880.0,
117
- "thickness_range": [0.001, 0.05],
118
- "default_thickness": 0.003,
119
- "embodied_carbon": 8500.0,
120
- "cost": 1200.0
121
- },
122
- "Glass Fiber Insulation": {
123
- "category": "Insulation",
124
- "thermal_conductivity": 0.035,
125
- "density": 12.0,
126
- "specific_heat": 840.0,
127
- "thickness_range": [0.025, 0.3],
128
- "default_thickness": 0.1,
129
- "embodied_carbon": 28.0,
130
- "cost": 45.0
131
- },
132
- "Ceramic Tile": {
133
- "category": "Finishing Materials",
134
- "thermal_conductivity": 1.3,
135
- "density": 2300.0,
136
- "specific_heat": 840.0,
137
- "thickness_range": [0.005, 0.025],
138
- "default_thickness": 0.01,
139
- "embodied_carbon": 650.0,
140
- "cost": 280.0
141
- },
142
- "Carpet": {
143
- "category": "Finishing Materials",
144
- "thermal_conductivity": 0.06,
145
- "density": 200.0,
146
- "specific_heat": 1300.0,
147
- "thickness_range": [0.005, 0.02],
148
- "default_thickness": 0.01,
149
- "embodied_carbon": 40.0,
150
- "cost": 150.0
151
- },
152
- "Plywood": {
153
- "category": "Sub-Structural Materials",
154
- "thermal_conductivity": 0.13,
155
- "density": 560.0,
156
- "specific_heat": 1400.0,
157
- "thickness_range": [0.006, 0.025],
158
- "default_thickness": 0.012,
159
- "embodied_carbon": 350.0,
160
- "cost": 180.0
161
- },
162
- "Concrete Block": {
163
- "category": "Structural Materials",
164
- "thermal_conductivity": 0.51,
165
- "density": 1400.0,
166
- "specific_heat": 1000.0,
167
- "thickness_range": [0.1, 0.3],
168
- "default_thickness": 0.2,
169
- "embodied_carbon": 260.0,
170
- "cost": 140.0
171
- },
172
- "Stone (Granite)": {
173
- "category": "Finishing Materials",
174
- "thermal_conductivity": 2.8,
175
- "density": 2600.0,
176
- "specific_heat": 790.0,
177
- "thickness_range": [0.01, 0.1],
178
- "default_thickness": 0.03,
179
- "embodied_carbon": 170.0,
180
- "cost": 450.0
181
- },
182
- "Polyurethane Insulation": {
183
- "category": "Insulation",
184
- "thermal_conductivity": 0.025,
185
- "density": 30.0,
186
- "specific_heat": 1400.0,
187
- "thickness_range": [0.025, 0.2],
188
- "default_thickness": 0.075,
189
- "embodied_carbon": 102.0,
190
- "cost": 95.0
191
- }
192
- }
193
-
194
- # Default library fenestrations
195
- DEFAULT_FENESTRATIONS = {
196
- "Single Glazing": {
197
- "type": "Window",
198
- "u_value": 5.8,
199
- "shgc": 0.86,
200
- "visible_transmittance": 0.9,
201
- "thickness": 0.006,
202
- "embodied_carbon": 800.0, # kg CO2e/m²
203
- "cost": 120.0 # USD/m²
204
- },
205
- "Double Glazing (Air)": {
206
- "type": "Window",
207
- "u_value": 2.8,
208
- "shgc": 0.72,
209
- "visible_transmittance": 0.78,
210
- "thickness": 0.024,
211
- "embodied_carbon": 950.0,
212
- "cost": 180.0
213
- },
214
- "Double Glazing (Argon)": {
215
- "type": "Window",
216
- "u_value": 1.4,
217
- "shgc": 0.7,
218
- "visible_transmittance": 0.76,
219
- "thickness": 0.024,
220
- "embodied_carbon": 980.0,
221
- "cost": 220.0
222
- },
223
- "Triple Glazing": {
224
- "type": "Window",
225
- "u_value": 0.8,
226
- "shgc": 0.5,
227
- "visible_transmittance": 0.68,
228
- "thickness": 0.036,
229
- "embodied_carbon": 1100.0,
230
- "cost": 320.0
231
- },
232
- "Low-E Double Glazing": {
233
- "type": "Window",
234
- "u_value": 1.8,
235
- "shgc": 0.4,
236
- "visible_transmittance": 0.74,
237
- "thickness": 0.024,
238
- "embodied_carbon": 1050.0,
239
- "cost": 250.0
240
- },
241
- "Wooden Door": {
242
- "type": "Door",
243
- "u_value": 2.2,
244
- "shgc": 0.0,
245
- "visible_transmittance": 0.0,
246
- "thickness": 0.04,
247
- "embodied_carbon": 400.0,
248
- "cost": 280.0
249
- },
250
- "Steel Door": {
251
- "type": "Door",
252
- "u_value": 3.1,
253
- "shgc": 0.0,
254
- "visible_transmittance": 0.0,
255
- "thickness": 0.035,
256
- "embodied_carbon": 1200.0,
257
- "cost": 350.0
258
- },
259
- "Glass Door": {
260
- "type": "Door",
261
- "u_value": 3.8,
262
- "shgc": 0.6,
263
- "visible_transmittance": 0.7,
264
- "thickness": 0.01,
265
- "embodied_carbon": 900.0,
266
- "cost": 420.0
267
- },
268
- "Skylight (Double Glazed)": {
269
- "type": "Skylight",
270
- "u_value": 3.0,
271
- "shgc": 0.65,
272
- "visible_transmittance": 0.75,
273
- "thickness": 0.024,
274
- "embodied_carbon": 1050.0,
275
- "cost": 380.0
276
- },
277
- "Skylight (Low-E)": {
278
- "type": "Skylight",
279
- "u_value": 2.0,
280
- "shgc": 0.35,
281
- "visible_transmittance": 0.7,
282
- "thickness": 0.024,
283
- "embodied_carbon": 1150.0,
284
- "cost": 450.0
285
- }
286
- }
287
-
288
  def display_materials_page():
289
  """
290
  Display the material library page.
291
  This is the main function called by main.py when the Material Library page is selected.
292
  """
293
  st.title("Material Library")
 
294
 
295
  # Display help information in an expandable section
296
  with st.expander("Help & Information"):
297
  display_materials_help()
298
 
 
 
 
 
 
 
 
 
 
 
 
299
  # Create tabs for materials and fenestrations
300
  tab1, tab2 = st.tabs(["Materials", "Fenestrations"])
301
 
@@ -320,896 +88,681 @@ def display_materials_page():
320
  st.session_state.current_page = "Construction"
321
  st.rerun()
322
 
323
- def display_materials_tab():
324
- """Display the materials tab content."""
325
- # Initialize materials in session state if not present
326
- initialize_materials()
327
-
328
- # Create columns for library and project materials
329
- col1, col2 = st.columns(2)
330
-
331
- # Library Materials
332
- with col1:
333
- st.subheader("Library Materials")
334
- display_library_materials()
335
-
336
- # Project Materials
337
- with col2:
338
- st.subheader("Project Materials")
339
- display_project_materials()
340
-
341
- # Material Editor
342
- st.markdown("---")
343
- st.subheader("Material Editor")
344
- display_material_editor()
345
-
346
- def display_fenestrations_tab():
347
- """Display the fenestrations tab content."""
348
- # Initialize fenestrations in session state if not present
349
- initialize_fenestrations()
350
-
351
- # Create columns for library and project fenestrations
352
- col1, col2 = st.columns(2)
353
-
354
- # Library Fenestrations
355
- with col1:
356
- st.subheader("Library Fenestrations")
357
- display_library_fenestrations()
358
-
359
- # Project Fenestrations
360
- with col2:
361
- st.subheader("Project Fenestrations")
362
- display_project_fenestrations()
363
-
364
- # Fenestration Editor
365
- st.markdown("---")
366
- st.subheader("Fenestration Editor")
367
- display_fenestration_editor()
368
-
369
- def initialize_materials():
370
- """Initialize materials in session state if not present."""
371
  if "materials" not in st.session_state.project_data:
372
  st.session_state.project_data["materials"] = {
373
- "library": {},
374
  "project": {}
375
  }
376
 
377
- # Initialize library materials if empty
378
- if not st.session_state.project_data["materials"]["library"]:
379
- st.session_state.project_data["materials"]["library"] = DEFAULT_MATERIALS.copy()
380
-
381
- # Initialize material editor state
382
- if "material_editor" not in st.session_state:
383
- st.session_state.material_editor = {
384
- "name": "",
385
- "category": MATERIAL_CATEGORIES[0],
386
- "thermal_conductivity": 0.5,
387
- "density": 1000.0,
388
- "specific_heat": 1000.0,
389
- "thickness_range": [0.01, 0.2],
390
- "default_thickness": 0.05,
391
- "embodied_carbon": 100.0,
392
- "cost": 100.0,
393
- "edit_mode": False,
394
- "original_name": ""
395
- }
396
-
397
- def initialize_fenestrations():
398
- """Initialize fenestrations in session state if not present."""
399
  if "fenestrations" not in st.session_state.project_data:
400
  st.session_state.project_data["fenestrations"] = {
401
- "library": {},
402
  "project": {}
403
  }
404
-
405
- # Initialize library fenestrations if empty
406
- if not st.session_state.project_data["fenestrations"]["library"]:
407
- st.session_state.project_data["fenestrations"]["library"] = DEFAULT_FENESTRATIONS.copy()
408
-
409
- # Initialize fenestration editor state
410
- if "fenestration_editor" not in st.session_state:
411
- st.session_state.fenestration_editor = {
412
- "name": "",
413
- "type": FENESTRATION_TYPES[0],
414
- "u_value": 2.8,
415
- "shgc": 0.7,
416
- "visible_transmittance": 0.8,
417
- "thickness": 0.024,
418
- "embodied_carbon": 900.0,
419
- "cost": 200.0,
420
- "edit_mode": False,
421
- "original_name": ""
422
- }
423
 
424
- def display_library_materials():
425
- """Display the library materials section."""
426
- # Filter options
427
- category_filter = st.selectbox(
428
- "Filter by Category",
429
- ["All"] + MATERIAL_CATEGORIES,
430
- key="library_material_category_filter"
431
- )
432
-
433
- # Get library materials
434
- library_materials = st.session_state.project_data["materials"]["library"]
435
-
436
- # Apply filter
437
- if category_filter != "All":
438
- filtered_materials = {
439
- name: props for name, props in library_materials.items()
440
- if props["category"] == category_filter
441
- }
442
- else:
443
- filtered_materials = library_materials
444
 
445
- # Display materials in a table
446
- if filtered_materials:
447
- # Create a DataFrame for display
448
- data = []
449
- for name, props in filtered_materials.items():
450
- data.append({
451
- "Name": name,
452
- "Category": props["category"],
453
- "Thermal Conductivity (W/m·K)": props["thermal_conductivity"],
454
- "Density (kg/m³)": props["density"],
455
- "Specific Heat (J/kg·K)": props["specific_heat"]
456
- })
457
 
458
- df = pd.DataFrame(data)
459
- st.dataframe(df, use_container_width=True, hide_index=True)
 
460
 
461
- # Add to project button
462
- selected_material = st.selectbox(
463
- "Select Material to Add to Project",
464
- list(filtered_materials.keys()),
465
- key="library_material_selector"
466
- )
467
 
468
- if st.button("Add to Project", key="add_library_material_to_project"):
469
- # Check if material already exists in project
470
- if selected_material in st.session_state.project_data["materials"]["project"]:
471
- st.warning(f"Material '{selected_material}' already exists in your project.")
472
- else:
473
- # Add to project materials
474
- st.session_state.project_data["materials"]["project"][selected_material] = \
475
- st.session_state.project_data["materials"]["library"][selected_material].copy()
476
- st.success(f"Material '{selected_material}' added to your project.")
477
- logger.info(f"Added library material '{selected_material}' to project")
478
- else:
479
- st.info("No materials found in the selected category.")
480
-
481
- def display_project_materials():
482
- """Display the project materials section."""
483
- # Get project materials
484
- project_materials = st.session_state.project_data["materials"]["project"]
485
 
486
- if project_materials:
487
- # Create a DataFrame for display
488
- data = []
489
- for name, props in project_materials.items():
490
- data.append({
491
- "Name": name,
492
- "Category": props["category"],
493
- "Thermal Conductivity (W/m·K)": props["thermal_conductivity"],
494
- "Density (kg/m³)": props["density"],
495
- "Specific Heat (J/kg·K)": props["specific_heat"]
496
- })
497
 
498
- df = pd.DataFrame(data)
499
- st.dataframe(df, use_container_width=True, hide_index=True)
 
500
 
501
- # Edit and delete options
502
- col1, col2 = st.columns(2)
 
503
 
504
- with col1:
505
- selected_material = st.selectbox(
506
- "Select Material to Edit",
507
- list(project_materials.keys()),
508
- key="project_material_edit_selector"
509
- )
510
 
511
- if st.button("Edit Material", key="edit_project_material"):
512
- # Load material data into editor
513
- material_data = project_materials[selected_material]
514
- st.session_state.material_editor = {
515
- "name": selected_material,
516
- "category": material_data["category"],
517
- "thermal_conductivity": material_data["thermal_conductivity"],
518
- "density": material_data["density"],
519
- "specific_heat": material_data["specific_heat"],
520
- "thickness_range": material_data["thickness_range"],
521
- "default_thickness": material_data["default_thickness"],
522
- "embodied_carbon": material_data["embodied_carbon"],
523
- "cost": material_data["cost"],
524
- "edit_mode": True,
525
- "original_name": selected_material
526
- }
527
- st.success(f"Material '{selected_material}' loaded for editing.")
528
-
529
- with col2:
530
- selected_material_delete = st.selectbox(
531
- "Select Material to Delete",
532
- list(project_materials.keys()),
533
- key="project_material_delete_selector"
534
  )
535
 
536
- if st.button("Delete Material", key="delete_project_material"):
537
- # Check if material is in use
538
- is_in_use = check_material_in_use(selected_material_delete)
539
-
540
- if is_in_use:
541
- st.error(f"Cannot delete material '{selected_material_delete}' because it is in use in constructions.")
542
- else:
543
- # Delete material
544
- del st.session_state.project_data["materials"]["project"][selected_material_delete]
545
- st.success(f"Material '{selected_material_delete}' deleted from your project.")
546
- logger.info(f"Deleted material '{selected_material_delete}' from project")
547
- else:
548
- st.info("No materials in your project. Add materials from the library or create custom materials.")
549
-
550
- def display_material_editor():
551
- """Display the material editor form."""
552
- with st.form("material_editor_form"):
553
- # Material name
554
- name = st.text_input(
555
- "Material Name",
556
- value=st.session_state.material_editor["name"],
557
- help="Enter a unique name for the material."
558
- )
559
-
560
- # Create two columns for layout
561
- col1, col2 = st.columns(2)
562
-
563
- with col1:
564
- # Category
565
  category = st.selectbox(
566
  "Category",
567
  MATERIAL_CATEGORIES,
568
- index=MATERIAL_CATEGORIES.index(st.session_state.material_editor["category"]) if st.session_state.material_editor["category"] in MATERIAL_CATEGORIES else 0,
569
  help="Select the material category."
570
  )
571
 
572
- # Thermal conductivity
 
573
  thermal_conductivity = st.number_input(
574
  "Thermal Conductivity (W/m·K)",
575
  min_value=0.001,
576
- max_value=1000.0,
577
- value=float(st.session_state.material_editor["thermal_conductivity"]),
578
  format="%.3f",
579
- help="Thermal conductivity in W/m·K. Lower values indicate better insulation."
580
  )
581
 
582
- # Density
583
  density = st.number_input(
584
  "Density (kg/m³)",
585
  min_value=1.0,
586
- max_value=20000.0,
587
- value=float(st.session_state.material_editor["density"]),
588
  format="%.1f",
589
- help="Material density in kg/m³."
590
  )
591
-
592
- with col2:
593
- # Specific heat
594
  specific_heat = st.number_input(
595
  "Specific Heat (J/kg·K)",
596
  min_value=100.0,
597
- max_value=10000.0,
598
- value=float(st.session_state.material_editor["specific_heat"]),
599
  format="%.1f",
600
- help="Specific heat capacity in J/kg·K. Higher values indicate better thermal mass."
601
  )
602
 
603
  # Thickness range
604
- min_thickness, max_thickness = st.session_state.material_editor["thickness_range"]
605
- thickness_range = st.slider(
606
- "Thickness Range (m)",
607
- min_value=0.001,
608
- max_value=0.5,
609
- value=(float(min_thickness), float(max_thickness)),
610
- format="%.3f",
611
- help="Minimum and maximum thickness range for this material in meters."
612
- )
613
 
614
- # Default thickness
615
- default_thickness = st.number_input(
616
- "Default Thickness (m)",
617
- min_value=float(thickness_range[0]),
618
- max_value=float(thickness_range[1]),
619
- value=min(max(float(st.session_state.material_editor["default_thickness"]), float(thickness_range[0])), float(thickness_range[1])),
620
- format="%.3f",
621
- help="Default thickness for this material in meters."
622
- )
623
-
624
- # Additional properties for embodied carbon and cost
625
- st.subheader("Additional Properties")
626
- col1, col2 = st.columns(2)
627
-
628
- with col1:
629
- # Embodied carbon
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  embodied_carbon = st.number_input(
631
- "Embodied Carbon (kg CO₂e/m³)",
632
  min_value=0.0,
633
- max_value=10000.0,
634
- value=float(st.session_state.material_editor["embodied_carbon"]),
635
- format="%.1f",
636
- help="Embodied carbon in kg CO₂e per cubic meter."
637
  )
638
-
639
- with col2:
640
- # Cost
641
- cost = st.number_input(
642
- "Cost (USD/m³)",
643
- min_value=0.0,
644
- max_value=10000.0,
645
- value=float(st.session_state.material_editor["cost"]),
646
- format="%.1f",
647
- help="Material cost in USD per cubic meter."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
648
  )
649
-
650
- # Form submission buttons
651
- col1, col2 = st.columns(2)
652
-
653
- with col1:
654
- submit_button = st.form_submit_button("Save Material")
655
-
656
- with col2:
657
- clear_button = st.form_submit_button("Clear Form")
658
-
659
- # Handle form submission
660
- if submit_button:
661
- # Validate inputs
662
- validation_errors = validate_material(
663
- name, category, thermal_conductivity, density, specific_heat,
664
- thickness_range, default_thickness, embodied_carbon, cost,
665
- st.session_state.material_editor["edit_mode"], st.session_state.material_editor["original_name"]
666
- )
667
-
668
- if validation_errors:
669
- # Display validation errors
670
- for error in validation_errors:
671
- st.error(error)
672
- else:
673
- # Create material data
674
- material_data = {
675
- "category": category,
676
- "thermal_conductivity": thermal_conductivity,
677
- "density": density,
678
- "specific_heat": specific_heat,
679
- "thickness_range": list(thickness_range),
680
- "default_thickness": default_thickness,
681
- "embodied_carbon": embodied_carbon,
682
- "cost": cost
683
- }
684
 
685
- # Handle edit mode
686
- if st.session_state.material_editor["edit_mode"]:
687
- original_name = st.session_state.material_editor["original_name"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
688
 
689
- # If name changed, delete old entry and create new one
690
- if original_name != name:
691
- del st.session_state.project_data["materials"]["project"][original_name]
 
 
 
 
 
 
 
 
 
 
 
 
 
692
 
693
- # Update material
694
- st.session_state.project_data["materials"]["project"][name] = material_data
695
- st.success(f"Material '{name}' updated successfully.")
696
- logger.info(f"Updated material '{name}' in project")
697
- else:
698
- # Add new material
699
- st.session_state.project_data["materials"]["project"][name] = material_data
700
- st.success(f"Material '{name}' added to your project.")
701
- logger.info(f"Added new material '{name}' to project")
702
-
703
- # Reset editor
704
- reset_material_editor()
705
-
706
- # Handle clear button
707
- if clear_button:
708
- reset_material_editor()
709
- st.rerun()
710
 
711
- def display_library_fenestrations():
712
- """Display the library fenestrations section."""
713
- # Filter options
714
- type_filter = st.selectbox(
715
- "Filter by Type",
716
- ["All"] + FENESTRATION_TYPES,
717
- key="library_fenestration_type_filter"
718
- )
719
-
720
- # Get library fenestrations
721
- library_fenestrations = st.session_state.project_data["fenestrations"]["library"]
722
-
723
- # Apply filter
724
- if type_filter != "All":
725
- filtered_fenestrations = {
726
- name: props for name, props in library_fenestrations.items()
727
- if props["type"] == type_filter
728
- }
729
- else:
730
- filtered_fenestrations = library_fenestrations
731
 
732
- # Display fenestrations in a table
733
- if filtered_fenestrations:
734
- # Create a DataFrame for display
735
- data = []
736
- for name, props in filtered_fenestrations.items():
737
- data.append({
738
- "Name": name,
739
- "Type": props["type"],
740
- "U-Value (W/m²·K)": props["u_value"],
741
- "SHGC": props["shgc"],
742
- "Visible Transmittance": props["visible_transmittance"]
743
- })
744
 
745
- df = pd.DataFrame(data)
746
- st.dataframe(df, use_container_width=True, hide_index=True)
 
747
 
748
- # Add to project button
749
- selected_fenestration = st.selectbox(
750
- "Select Fenestration to Add to Project",
751
- list(filtered_fenestrations.keys()),
752
- key="library_fenestration_selector"
753
- )
754
 
755
- if st.button("Add to Project", key="add_library_fenestration_to_project"):
756
- # Check if fenestration already exists in project
757
- if selected_fenestration in st.session_state.project_data["fenestrations"]["project"]:
758
- st.warning(f"Fenestration '{selected_fenestration}' already exists in your project.")
759
- else:
760
- # Add to project fenestrations
761
- st.session_state.project_data["fenestrations"]["project"][selected_fenestration] = \
762
- st.session_state.project_data["fenestrations"]["library"][selected_fenestration].copy()
763
- st.success(f"Fenestration '{selected_fenestration}' added to your project.")
764
- logger.info(f"Added library fenestration '{selected_fenestration}' to project")
765
- else:
766
- st.info("No fenestrations found in the selected type.")
767
-
768
- def display_project_fenestrations():
769
- """Display the project fenestrations section."""
770
- # Get project fenestrations
771
- project_fenestrations = st.session_state.project_data["fenestrations"]["project"]
772
 
773
- if project_fenestrations:
774
- # Create a DataFrame for display
775
- data = []
776
- for name, props in project_fenestrations.items():
777
- data.append({
778
- "Name": name,
779
- "Type": props["type"],
780
- "U-Value (W/m²·K)": props["u_value"],
781
- "SHGC": props["shgc"],
782
- "Visible Transmittance": props["visible_transmittance"]
783
- })
784
 
785
- df = pd.DataFrame(data)
786
- st.dataframe(df, use_container_width=True, hide_index=True)
 
787
 
788
- # Edit and delete options
789
- col1, col2 = st.columns(2)
 
790
 
791
- with col1:
792
- selected_fenestration = st.selectbox(
793
- "Select Fenestration to Edit",
794
- list(project_fenestrations.keys()),
795
- key="project_fenestration_edit_selector"
796
- )
797
 
798
- if st.button("Edit Fenestration", key="edit_project_fenestration"):
799
- # Load fenestration data into editor
800
- fenestration_data = project_fenestrations[selected_fenestration]
801
- st.session_state.fenestration_editor = {
802
- "name": selected_fenestration,
803
- "type": fenestration_data["type"],
804
- "u_value": fenestration_data["u_value"],
805
- "shgc": fenestration_data["shgc"],
806
- "visible_transmittance": fenestration_data["visible_transmittance"],
807
- "thickness": fenestration_data["thickness"],
808
- "embodied_carbon": fenestration_data["embodied_carbon"],
809
- "cost": fenestration_data["cost"],
810
- "edit_mode": True,
811
- "original_name": selected_fenestration
812
- }
813
- st.success(f"Fenestration '{selected_fenestration}' loaded for editing.")
814
-
815
- with col2:
816
- selected_fenestration_delete = st.selectbox(
817
- "Select Fenestration to Delete",
818
- list(project_fenestrations.keys()),
819
- key="project_fenestration_delete_selector"
820
  )
821
 
822
- if st.button("Delete Fenestration", key="delete_project_fenestration"):
823
- # Check if fenestration is in use
824
- is_in_use = check_fenestration_in_use(selected_fenestration_delete)
825
-
826
- if is_in_use:
827
- st.error(f"Cannot delete fenestration '{selected_fenestration_delete}' because it is in use in components.")
828
- else:
829
- # Delete fenestration
830
- del st.session_state.project_data["fenestrations"]["project"][selected_fenestration_delete]
831
- st.success(f"Fenestration '{selected_fenestration_delete}' deleted from your project.")
832
- logger.info(f"Deleted fenestration '{selected_fenestration_delete}' from project")
833
- else:
834
- st.info("No fenestrations in your project. Add fenestrations from the library or create custom fenestrations.")
835
-
836
- def display_fenestration_editor():
837
- """Display the fenestration editor form."""
838
- with st.form("fenestration_editor_form"):
839
- # Fenestration name
840
- name = st.text_input(
841
- "Fenestration Name",
842
- value=st.session_state.fenestration_editor["name"],
843
- help="Enter a unique name for the fenestration."
844
- )
845
-
846
- # Create two columns for layout
847
- col1, col2 = st.columns(2)
848
-
849
- with col1:
850
- # Type
851
  fenestration_type = st.selectbox(
852
  "Type",
853
  FENESTRATION_TYPES,
854
- index=FENESTRATION_TYPES.index(st.session_state.fenestration_editor["type"]) if st.session_state.fenestration_editor["type"] in FENESTRATION_TYPES else 0,
855
  help="Select the fenestration type."
856
  )
857
 
858
- # U-value
 
859
  u_value = st.number_input(
860
  "U-Value (W/m²·K)",
861
  min_value=0.1,
862
  max_value=10.0,
863
- value=float(st.session_state.fenestration_editor["u_value"]),
864
  format="%.2f",
865
- help="U-value in W/m²·K. Lower values indicate better insulation."
866
  )
867
 
868
- # SHGC
869
- shgc = st.number_input(
870
- "Solar Heat Gain Coefficient (SHGC)",
871
- min_value=0.0,
872
- max_value=1.0,
873
- value=float(st.session_state.fenestration_editor["shgc"]),
874
- format="%.2f",
875
- help="Solar Heat Gain Coefficient (0-1). Lower values indicate less solar heat transmission."
876
- )
877
-
878
- with col2:
879
- # Visible transmittance
880
- visible_transmittance = st.number_input(
881
- "Visible Transmittance",
882
- min_value=0.0,
883
- max_value=1.0,
884
- value=float(st.session_state.fenestration_editor["visible_transmittance"]),
885
- format="%.2f",
886
- help="Visible Transmittance (0-1). Higher values indicate more visible light transmission."
887
- )
 
 
 
 
 
 
 
 
 
 
 
888
 
889
- # Thickness
 
890
  thickness = st.number_input(
891
  "Thickness (m)",
892
- min_value=0.001,
893
  max_value=0.1,
894
- value=float(st.session_state.fenestration_editor["thickness"]),
895
  format="%.3f",
896
- help="Thickness in meters."
897
  )
898
-
899
- # Additional properties for embodied carbon and cost
900
- st.subheader("Additional Properties")
901
- col1, col2 = st.columns(2)
902
-
903
- with col1:
904
- # Embodied carbon
905
  embodied_carbon = st.number_input(
906
- "Embodied Carbon (kg CO₂e/m²)",
907
  min_value=0.0,
908
- max_value=5000.0,
909
- value=float(st.session_state.fenestration_editor["embodied_carbon"]),
910
  format="%.1f",
911
- help="Embodied carbon in kg CO₂e per square meter."
912
  )
913
-
914
- with col2:
915
- # Cost
916
- cost = st.number_input(
917
- "Cost (USD/m²)",
918
- min_value=0.0,
919
- max_value=5000.0,
920
- value=float(st.session_state.fenestration_editor["cost"]),
921
- format="%.1f",
922
- help="Fenestration cost in USD per square meter."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
923
  )
924
-
925
- # Form submission buttons
926
- col1, col2 = st.columns(2)
927
-
928
- with col1:
929
- submit_button = st.form_submit_button("Save Fenestration")
930
-
931
- with col2:
932
- clear_button = st.form_submit_button("Clear Form")
933
-
934
- # Handle form submission
935
- if submit_button:
936
- # Validate inputs
937
- validation_errors = validate_fenestration(
938
- name, fenestration_type, u_value, shgc, visible_transmittance, thickness,
939
- embodied_carbon, cost, st.session_state.fenestration_editor["edit_mode"],
940
- st.session_state.fenestration_editor["original_name"]
941
- )
942
-
943
- if validation_errors:
944
- # Display validation errors
945
- for error in validation_errors:
946
- st.error(error)
947
- else:
948
- # Create fenestration data
949
- fenestration_data = {
950
- "type": fenestration_type,
951
- "u_value": u_value,
952
- "shgc": shgc,
953
- "visible_transmittance": visible_transmittance,
954
- "thickness": thickness,
955
- "embodied_carbon": embodied_carbon,
956
- "cost": cost
957
- }
958
 
959
- # Handle edit mode
960
- if st.session_state.fenestration_editor["edit_mode"]:
961
- original_name = st.session_state.fenestration_editor["original_name"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
962
 
963
- # If name changed, delete old entry and create new one
964
- if original_name != name:
965
- del st.session_state.project_data["fenestrations"]["project"][original_name]
 
 
 
966
 
967
- # Update fenestration
968
- st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
969
- st.success(f"Fenestration '{name}' updated successfully.")
970
- logger.info(f"Updated fenestration '{name}' in project")
971
- else:
972
- # Add new fenestration
973
- st.session_state.project_data["fenestrations"]["project"][name] = fenestration_data
974
- st.success(f"Fenestration '{name}' added to your project.")
975
- logger.info(f"Added new fenestration '{name}' to project")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
976
 
977
- # Reset editor
978
- reset_fenestration_editor()
979
-
980
- # Handle clear button
981
- if clear_button:
982
- reset_fenestration_editor()
983
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
984
 
985
- def validate_material(
986
- name: str, category: str, thermal_conductivity: float, density: float,
987
- specific_heat: float, thickness_range: Tuple[float, float], default_thickness: float,
988
- embodied_carbon: float, cost: float, edit_mode: bool, original_name: str
989
- ) -> List[str]:
990
- """
991
- Validate material inputs.
992
-
993
- Args:
994
- name: Material name
995
- category: Material category
996
- thermal_conductivity: Thermal conductivity in W/m·K
997
- density: Density in kg/
998
- specific_heat: Specific heat in J/kg·K
999
- thickness_range: Tuple of (min_thickness, max_thickness) in meters
1000
- default_thickness: Default thickness in meters
1001
- embodied_carbon: Embodied carbon in kg CO₂e/m³
1002
- cost: Cost in USD/m³
1003
- edit_mode: Whether in edit mode
1004
- original_name: Original name if in edit mode
1005
-
1006
- Returns:
1007
- List of validation error messages, empty if all inputs are valid
1008
- """
1009
- errors = []
1010
-
1011
- # Validate name
1012
- if not name or name.strip() == "":
1013
- errors.append("Material name is required.")
1014
-
1015
- # Check for name uniqueness if not in edit mode or if name changed
1016
- if not edit_mode or (edit_mode and name != original_name):
1017
- if name in st.session_state.project_data["materials"]["project"]:
1018
- errors.append(f"Material name '{name}' already exists in your project.")
1019
-
1020
- # Validate category
1021
- if category not in MATERIAL_CATEGORIES:
1022
- errors.append("Please select a valid material category.")
1023
-
1024
- # Validate thermal conductivity
1025
- if thermal_conductivity <= 0:
1026
- errors.append("Thermal conductivity must be greater than zero.")
1027
-
1028
- # Validate density
1029
- if density <= 0:
1030
- errors.append("Density must be greater than zero.")
1031
-
1032
- # Validate specific heat
1033
- if specific_heat <= 0:
1034
- errors.append("Specific heat must be greater than zero.")
1035
-
1036
- # Validate thickness range
1037
- if thickness_range[0] <= 0:
1038
- errors.append("Minimum thickness must be greater than zero.")
1039
- if thickness_range[0] >= thickness_range[1]:
1040
- errors.append("Maximum thickness must be greater than minimum thickness.")
1041
-
1042
- # Validate default thickness
1043
- if default_thickness < thickness_range[0] or default_thickness > thickness_range[1]:
1044
- errors.append("Default thickness must be within the thickness range.")
1045
-
1046
- # Validate embodied carbon
1047
- if embodied_carbon < 0:
1048
- errors.append("Embodied carbon cannot be negative.")
1049
-
1050
- # Validate cost
1051
- if cost < 0:
1052
- errors.append("Cost cannot be negative.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1053
 
1054
- return errors
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1055
 
1056
- def validate_fenestration(
1057
- name: str, fenestration_type: str, u_value: float, shgc: float,
1058
- visible_transmittance: float, thickness: float, embodied_carbon: float,
1059
- cost: float, edit_mode: bool, original_name: str
1060
- ) -> List[str]:
1061
- """
1062
- Validate fenestration inputs.
1063
 
1064
- Args:
1065
- name: Fenestration name
1066
- fenestration_type: Fenestration type
1067
- u_value: U-value in W/m²·K
1068
- shgc: Solar Heat Gain Coefficient (0-1)
1069
- visible_transmittance: Visible Transmittance (0-1)
1070
- thickness: Thickness in meters
1071
- embodied_carbon: Embodied carbon in kg CO₂e/m²
1072
- cost: Cost in USD/m²
1073
- edit_mode: Whether in edit mode
1074
- original_name: Original name if in edit mode
1075
 
1076
- Returns:
1077
- List of validation error messages, empty if all inputs are valid
1078
- """
1079
- errors = []
1080
-
1081
- # Validate name
1082
- if not name or name.strip() == "":
1083
- errors.append("Fenestration name is required.")
1084
-
1085
- # Check for name uniqueness if not in edit mode or if name changed
1086
- if not edit_mode or (edit_mode and name != original_name):
1087
- if name in st.session_state.project_data["fenestrations"]["project"]:
1088
- errors.append(f"Fenestration name '{name}' already exists in your project.")
1089
-
1090
- # Validate type
1091
- if fenestration_type not in FENESTRATION_TYPES:
1092
- errors.append("Please select a valid fenestration type.")
1093
-
1094
- # Validate u_value
1095
- if u_value <= 0:
1096
- errors.append("U-value must be greater than zero.")
1097
-
1098
- # Validate shgc
1099
- if shgc < 0 or shgc > 1:
1100
- errors.append("SHGC must be between 0 and 1.")
1101
 
1102
- # Validate visible transmittance
1103
- if visible_transmittance < 0 or visible_transmittance > 1:
1104
- errors.append("Visible transmittance must be between 0 and 1.")
1105
-
1106
- # Validate thickness
1107
- if thickness <= 0:
1108
- errors.append("Thickness must be greater than zero.")
1109
-
1110
- # Validate embodied carbon
1111
- if embodied_carbon < 0:
1112
- errors.append("Embodied carbon cannot be negative.")
1113
-
1114
- # Validate cost
1115
- if cost < 0:
1116
- errors.append("Cost cannot be negative.")
1117
-
1118
- return errors
1119
 
1120
- def reset_material_editor():
1121
- """Reset the material editor to default values."""
1122
- st.session_state.material_editor = {
1123
- "name": "",
1124
- "category": MATERIAL_CATEGORIES[0],
1125
- "thermal_conductivity": 0.5,
1126
- "density": 1000.0,
1127
- "specific_heat": 1000.0,
1128
- "thickness_range": [0.01, 0.2],
1129
- "default_thickness": 0.05,
1130
- "embodied_carbon": 100.0,
1131
- "cost": 100.0,
1132
- "edit_mode": False,
1133
- "original_name": ""
1134
- }
1135
-
1136
- def reset_fenestration_editor():
1137
- """Reset the fenestration editor to default values."""
1138
- st.session_state.fenestration_editor = {
1139
- "name": "",
1140
- "type": FENESTRATION_TYPES[0],
1141
- "u_value": 2.8,
1142
- "shgc": 0.7,
1143
- "visible_transmittance": 0.8,
1144
- "thickness": 0.024,
1145
- "embodied_carbon": 900.0,
1146
- "cost": 200.0,
1147
- "edit_mode": False,
1148
- "original_name": ""
1149
- }
1150
-
1151
- def check_material_in_use(material_name: str) -> bool:
1152
- """
1153
- Check if a material is in use in any constructions.
1154
 
1155
- Args:
1156
- material_name: Name of the material to check
 
 
1157
 
1158
- Returns:
1159
- True if the material is in use, False otherwise
1160
- """
1161
- # This is a placeholder function that will be implemented when constructions are added
1162
- # For now, we'll assume materials are not in use
1163
- return False
1164
-
1165
- def check_fenestration_in_use(fenestration_name: str) -> bool:
1166
- """
1167
- Check if a fenestration is in use in any components.
1168
 
1169
- Args:
1170
- fenestration_name: Name of the fenestration to check
1171
-
1172
- Returns:
1173
- True if the fenestration is in use, False otherwise
1174
- """
1175
- # This is a placeholder function that will be implemented when components are added
1176
- # For now, we'll assume fenestrations are not in use
1177
- return False
1178
 
1179
- def display_materials_help():
1180
- """Display help information for the material library page."""
1181
- st.markdown("""
1182
- ### Material Library Help
1183
-
1184
- This section allows you to manage building materials and fenestrations for your project.
1185
-
1186
- **Materials Tab:**
1187
-
1188
- * **Library Materials**: Pre-defined materials with standard thermal properties.
1189
- * **Project Materials**: Materials you've added to your project from the library or created custom.
1190
- * **Material Editor**: Create new materials or edit existing ones in your project.
1191
-
1192
- **Fenestrations Tab:**
1193
-
1194
- * **Library Fenestrations**: Pre-defined windows, doors, and skylights with standard thermal properties.
1195
- * **Project Fenestrations**: Fenestrations you've added to your project from the library or created custom.
1196
- * **Fenestration Editor**: Create new fenestrations or edit existing ones in your project.
1197
-
1198
- **Key Properties:**
1199
-
1200
- * **Thermal Conductivity (W/m·K)**: Rate of heat transfer through a material. Lower values indicate better insulation.
1201
- * **Density (kg/m³)**: Mass per unit volume.
1202
- * **Specific Heat (J/kg·K)**: Energy required to raise the temperature of 1 kg by 1 K. Higher values indicate better thermal mass.
1203
- * **U-Value (W/m²·K)**: Overall heat transfer coefficient for fenestrations. Lower values indicate better insulation.
1204
- * **SHGC**: Solar Heat Gain Coefficient (0-1). Fraction of incident solar radiation that enters through a fenestration.
1205
- * **Visible Transmittance**: Fraction of visible light that passes through a fenestration.
1206
- * **Embodied Carbon**: Carbon emissions associated with material production, measured in kg CO₂e per unit volume or area.
1207
- * **Cost**: Material cost in USD per unit volume or area.
1208
-
1209
- **Workflow:**
1210
-
1211
- 1. Browse the library materials and fenestrations
1212
- 2. Add items to your project or create custom ones
1213
- 3. Edit properties as needed for your specific project
1214
- 4. Continue to the Construction page to create assemblies using these materials
1215
- """)
 
1
  """
2
+ BuildSustain - Material Library Module (Restructured)
3
 
4
  This module handles the material library functionality of the BuildSustain application,
5
  allowing users to manage building materials and fenestrations (windows, doors, skylights).
 
17
  import uuid
18
  from typing import Dict, List, Any, Optional, Tuple, Union
19
 
20
+ # Import default data from centralized module
21
+ from app.m_c_data import SAMPLE_MATERIALS, SAMPLE_FENESTRATIONS
22
+
23
  # Configure logging
24
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
25
  logger = logging.getLogger(__name__)
26
 
27
  # Define constants
28
  MATERIAL_CATEGORIES = [
29
+ "Masonry",
 
 
30
  "Insulation",
31
+ "Metal",
32
+ "Wood",
33
+ "Finishing",
34
  "Custom"
35
  ]
36
 
 
41
  "Custom"
42
  ]
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  def display_materials_page():
45
  """
46
  Display the material library page.
47
  This is the main function called by main.py when the Material Library page is selected.
48
  """
49
  st.title("Material Library")
50
+ st.write("Manage building materials and fenestrations for thermal analysis.")
51
 
52
  # Display help information in an expandable section
53
  with st.expander("Help & Information"):
54
  display_materials_help()
55
 
56
+ # Initialize materials and fenestrations in session state if not present
57
+ initialize_materials_and_fenestrations()
58
+
59
+ # Check if rerun is pending
60
+ if 'materials_rerun_pending' not in st.session_state:
61
+ st.session_state.materials_rerun_pending = False
62
+
63
+ if st.session_state.materials_rerun_pending:
64
+ st.session_state.materials_rerun_pending = False
65
+ st.rerun()
66
+
67
  # Create tabs for materials and fenestrations
68
  tab1, tab2 = st.tabs(["Materials", "Fenestrations"])
69
 
 
88
  st.session_state.current_page = "Construction"
89
  st.rerun()
90
 
91
+ def initialize_materials_and_fenestrations():
92
+ """Initialize materials and fenestrations in session state if not present."""
93
+ # Initialize materials
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  if "materials" not in st.session_state.project_data:
95
  st.session_state.project_data["materials"] = {
96
+ "library": dict(SAMPLE_MATERIALS),
97
  "project": {}
98
  }
99
 
100
+ # Initialize fenestrations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  if "fenestrations" not in st.session_state.project_data:
102
  st.session_state.project_data["fenestrations"] = {
103
+ "library": dict(SAMPLE_FENESTRATIONS),
104
  "project": {}
105
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
+ def display_materials_tab():
108
+ """Display the materials tab content with two-column layout."""
109
+ # Split the display into two columns
110
+ col1, col2 = st.columns([3, 2])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
+ with col1:
113
+ st.subheader("Saved Materials")
 
 
 
 
 
 
 
 
 
 
114
 
115
+ # Get materials from session state
116
+ materials_library = st.session_state.project_data["materials"]["library"]
117
+ materials_project = st.session_state.project_data["materials"]["project"]
118
 
119
+ # Display library materials
120
+ if materials_library:
121
+ st.write("**Library Materials:**")
122
+ display_materials_table(materials_library, "library")
 
 
123
 
124
+ # Display project materials
125
+ if materials_project:
126
+ st.write("**Project Materials:**")
127
+ display_materials_table(materials_project, "project")
128
+
129
+ if not materials_library and not materials_project:
130
+ st.write("No materials available.")
 
 
 
 
 
 
 
 
 
 
131
 
132
+ with col2:
133
+ st.subheader("Material Editor/Creator")
 
 
 
 
 
 
 
 
 
134
 
135
+ # Check if we have an editor state for materials
136
+ if "material_editor" not in st.session_state:
137
+ st.session_state.material_editor = {}
138
 
139
+ # Check if we have an action state for materials
140
+ if "material_action" not in st.session_state:
141
+ st.session_state.material_action = {"action": None, "id": None}
142
 
143
+ # Display the material editor form
144
+ with st.form("material_editor_form", clear_on_submit=True):
145
+ editor_state = st.session_state.get("material_editor", {})
146
+ is_edit = editor_state.get("is_edit", False)
 
 
147
 
148
+ # Material name
149
+ name = st.text_input(
150
+ "Material Name",
151
+ value=editor_state.get("name", ""),
152
+ help="Enter a unique name for this material."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  )
154
 
155
+ # Material category
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  category = st.selectbox(
157
  "Category",
158
  MATERIAL_CATEGORIES,
159
+ index=MATERIAL_CATEGORIES.index(editor_state.get("category", MATERIAL_CATEGORIES[0])) if editor_state.get("category") in MATERIAL_CATEGORIES else 0,
160
  help="Select the material category."
161
  )
162
 
163
+ # Thermal properties
164
+ st.write("**Thermal Properties:**")
165
  thermal_conductivity = st.number_input(
166
  "Thermal Conductivity (W/m·K)",
167
  min_value=0.001,
168
+ max_value=500.0,
169
+ value=float(editor_state.get("thermal_conductivity", 0.1)),
170
  format="%.3f",
171
+ help="Thermal conductivity of the material."
172
  )
173
 
 
174
  density = st.number_input(
175
  "Density (kg/m³)",
176
  min_value=1.0,
177
+ max_value=10000.0,
178
+ value=float(editor_state.get("density", 1000.0)),
179
  format="%.1f",
180
+ help="Density of the material."
181
  )
182
+
 
 
183
  specific_heat = st.number_input(
184
  "Specific Heat (J/kg·K)",
185
  min_value=100.0,
186
+ max_value=5000.0,
187
+ value=float(editor_state.get("specific_heat", 1000.0)),
188
  format="%.1f",
189
+ help="Specific heat capacity of the material."
190
  )
191
 
192
  # Thickness range
193
+ st.write("**Thickness Range:**")
194
+ col_min, col_max, col_default = st.columns(3)
 
 
 
 
 
 
 
195
 
196
+ with col_min:
197
+ min_thickness = st.number_input(
198
+ "Min (m)",
199
+ min_value=0.001,
200
+ max_value=1.0,
201
+ value=float(editor_state.get("min_thickness", 0.01)),
202
+ format="%.3f",
203
+ help="Minimum thickness for this material."
204
+ )
205
+
206
+ with col_max:
207
+ max_thickness = st.number_input(
208
+ "Max (m)",
209
+ min_value=0.001,
210
+ max_value=1.0,
211
+ value=float(editor_state.get("max_thickness", 0.3)),
212
+ format="%.3f",
213
+ help="Maximum thickness for this material."
214
+ )
215
+
216
+ with col_default:
217
+ default_thickness = st.number_input(
218
+ "Default (m)",
219
+ min_value=0.001,
220
+ max_value=1.0,
221
+ value=float(editor_state.get("default_thickness", 0.1)),
222
+ format="%.3f",
223
+ help="Default thickness for this material."
224
+ )
225
+
226
+ # Environmental and cost properties
227
+ st.write("**Environmental & Cost Properties:**")
228
  embodied_carbon = st.number_input(
229
+ "Embodied Carbon (kgCO2e/kg)",
230
  min_value=0.0,
231
+ max_value=50.0,
232
+ value=float(editor_state.get("embodied_carbon", 0.5)),
233
+ format="%.3f",
234
+ help="Embodied carbon per unit mass of material."
235
  )
236
+
237
+ col_mat_cost, col_lab_cost = st.columns(2)
238
+
239
+ with col_mat_cost:
240
+ material_cost = st.number_input(
241
+ "Material Cost ($/m³)",
242
+ min_value=0.0,
243
+ max_value=10000.0,
244
+ value=float(editor_state.get("material_cost", 100.0)),
245
+ format="%.2f",
246
+ help="Material cost per cubic meter."
247
+ )
248
+
249
+ with col_lab_cost:
250
+ labor_cost = st.number_input(
251
+ "Labor Cost ($/m²)",
252
+ min_value=0.0,
253
+ max_value=1000.0,
254
+ value=float(editor_state.get("labor_cost", 50.0)),
255
+ format="%.2f",
256
+ help="Labor cost per square meter."
257
+ )
258
+
259
+ replacement_years = st.number_input(
260
+ "Replacement Years",
261
+ min_value=1,
262
+ max_value=100,
263
+ value=int(editor_state.get("replacement_years", 50)),
264
+ help="Expected lifespan before replacement."
265
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
+ # Submit buttons
268
+ col1, col2 = st.columns(2)
269
+ with col1:
270
+ submit_label = "Update Material" if is_edit else "Add Material"
271
+ submit = st.form_submit_button(submit_label)
272
+
273
+ with col2:
274
+ cancel = st.form_submit_button("Cancel")
275
+
276
+ # Handle form submission
277
+ if submit:
278
+ # Validate inputs
279
+ if not name.strip():
280
+ st.error("Material name is required.")
281
+ elif min_thickness >= max_thickness:
282
+ st.error("Minimum thickness must be less than maximum thickness.")
283
+ elif default_thickness < min_thickness or default_thickness > max_thickness:
284
+ st.error("Default thickness must be between minimum and maximum thickness.")
285
+ else:
286
+ # Create material data
287
+ material_data = {
288
+ "name": name.strip(),
289
+ "type": "opaque",
290
+ "category": category,
291
+ "thermal_conductivity": thermal_conductivity,
292
+ "density": density,
293
+ "specific_heat": specific_heat,
294
+ "thickness_range": {
295
+ "min": min_thickness,
296
+ "max": max_thickness,
297
+ "default": default_thickness
298
+ },
299
+ "embodied_carbon": embodied_carbon,
300
+ "cost": {
301
+ "material": material_cost,
302
+ "labor": labor_cost,
303
+ "replacement_years": replacement_years
304
+ },
305
+ "description": f"Custom {category.lower()} material"
306
+ }
307
 
308
+ # Check if editing or adding new
309
+ if is_edit and st.session_state.material_editor.get("edit_id"):
310
+ # Update existing material
311
+ edit_id = st.session_state.material_editor["edit_id"]
312
+ edit_source = st.session_state.material_editor["edit_source"]
313
+
314
+ if edit_source == "project":
315
+ st.session_state.project_data["materials"]["project"][edit_id] = material_data
316
+ st.success(f"Material '{name}' updated successfully!")
317
+ else:
318
+ st.error("Cannot edit library materials.")
319
+ else:
320
+ # Add new material to project
321
+ material_id = str(uuid.uuid4())
322
+ st.session_state.project_data["materials"]["project"][material_id] = material_data
323
+ st.success(f"Material '{name}' added successfully!")
324
 
325
+ # Clear editor state
326
+ st.session_state.material_editor = {}
327
+ st.session_state.materials_rerun_pending = True
328
+ st.rerun()
329
+
330
+ if cancel:
331
+ # Clear editor state
332
+ st.session_state.material_editor = {}
333
+ st.session_state.materials_rerun_pending = True
334
+ st.rerun()
 
 
 
 
 
 
 
335
 
336
+ def display_fenestrations_tab():
337
+ """Display the fenestrations tab content with two-column layout."""
338
+ # Split the display into two columns
339
+ col1, col2 = st.columns([3, 2])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
 
341
+ with col1:
342
+ st.subheader("Saved Fenestrations")
 
 
 
 
 
 
 
 
 
 
343
 
344
+ # Get fenestrations from session state
345
+ fenestrations_library = st.session_state.project_data["fenestrations"]["library"]
346
+ fenestrations_project = st.session_state.project_data["fenestrations"]["project"]
347
 
348
+ # Display library fenestrations
349
+ if fenestrations_library:
350
+ st.write("**Library Fenestrations:**")
351
+ display_fenestrations_table(fenestrations_library, "library")
 
 
352
 
353
+ # Display project fenestrations
354
+ if fenestrations_project:
355
+ st.write("**Project Fenestrations:**")
356
+ display_fenestrations_table(fenestrations_project, "project")
357
+
358
+ if not fenestrations_library and not fenestrations_project:
359
+ st.write("No fenestrations available.")
 
 
 
 
 
 
 
 
 
 
360
 
361
+ with col2:
362
+ st.subheader("Fenestration Editor/Creator")
 
 
 
 
 
 
 
 
 
363
 
364
+ # Check if we have an editor state for fenestrations
365
+ if "fenestration_editor" not in st.session_state:
366
+ st.session_state.fenestration_editor = {}
367
 
368
+ # Check if we have an action state for fenestrations
369
+ if "fenestration_action" not in st.session_state:
370
+ st.session_state.fenestration_action = {"action": None, "id": None}
371
 
372
+ # Display the fenestration editor form
373
+ with st.form("fenestration_editor_form", clear_on_submit=True):
374
+ editor_state = st.session_state.get("fenestration_editor", {})
375
+ is_edit = editor_state.get("is_edit", False)
 
 
376
 
377
+ # Fenestration name
378
+ name = st.text_input(
379
+ "Fenestration Name",
380
+ value=editor_state.get("name", ""),
381
+ help="Enter a unique name for this fenestration."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  )
383
 
384
+ # Fenestration type
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  fenestration_type = st.selectbox(
386
  "Type",
387
  FENESTRATION_TYPES,
388
+ index=FENESTRATION_TYPES.index(editor_state.get("type", FENESTRATION_TYPES[0])) if editor_state.get("type") in FENESTRATION_TYPES else 0,
389
  help="Select the fenestration type."
390
  )
391
 
392
+ # Thermal properties
393
+ st.write("**Thermal Properties:**")
394
  u_value = st.number_input(
395
  "U-Value (W/m²·K)",
396
  min_value=0.1,
397
  max_value=10.0,
398
+ value=float(editor_state.get("u_value", 2.5)),
399
  format="%.2f",
400
+ help="Overall heat transfer coefficient."
401
  )
402
 
403
+ # Solar properties (only for windows and skylights)
404
+ if fenestration_type in ["Window", "Skylight"]:
405
+ st.write("**Solar Properties:**")
406
+ shgc = st.number_input(
407
+ "SHGC",
408
+ min_value=0.0,
409
+ max_value=1.0,
410
+ value=float(editor_state.get("shgc", 0.7)),
411
+ format="%.2f",
412
+ help="Solar Heat Gain Coefficient."
413
+ )
414
+
415
+ visible_transmittance = st.number_input(
416
+ "Visible Transmittance",
417
+ min_value=0.0,
418
+ max_value=1.0,
419
+ value=float(editor_state.get("visible_transmittance", 0.8)),
420
+ format="%.2f",
421
+ help="Visible light transmittance."
422
+ )
423
+ else:
424
+ # For doors, use solar absorptivity instead
425
+ st.write("**Solar Properties:**")
426
+ solar_absorptivity = st.number_input(
427
+ "Solar Absorptivity",
428
+ min_value=0.0,
429
+ max_value=1.0,
430
+ value=float(editor_state.get("solar_absorptivity", 0.7)),
431
+ format="%.2f",
432
+ help="Solar absorptivity of the door surface."
433
+ )
434
 
435
+ # Physical properties
436
+ st.write("**Physical Properties:**")
437
  thickness = st.number_input(
438
  "Thickness (m)",
439
+ min_value=0.003,
440
  max_value=0.1,
441
+ value=float(editor_state.get("thickness", 0.024)),
442
  format="%.3f",
443
+ help="Overall thickness of the fenestration."
444
  )
445
+
446
+ # Environmental and cost properties
447
+ st.write("**Environmental & Cost Properties:**")
 
 
 
 
448
  embodied_carbon = st.number_input(
449
+ "Embodied Carbon (kgCO2e/m²)",
450
  min_value=0.0,
451
+ max_value=2000.0,
452
+ value=float(editor_state.get("embodied_carbon", 500.0)),
453
  format="%.1f",
454
+ help="Embodied carbon per unit area."
455
  )
456
+
457
+ col_mat_cost, col_lab_cost = st.columns(2)
458
+
459
+ with col_mat_cost:
460
+ material_cost = st.number_input(
461
+ "Material Cost ($/m²)",
462
+ min_value=0.0,
463
+ max_value=2000.0,
464
+ value=float(editor_state.get("material_cost", 200.0)),
465
+ format="%.2f",
466
+ help="Material cost per square meter."
467
+ )
468
+
469
+ with col_lab_cost:
470
+ labor_cost = st.number_input(
471
+ "Labor Cost ($/m²)",
472
+ min_value=0.0,
473
+ max_value=500.0,
474
+ value=float(editor_state.get("labor_cost", 80.0)),
475
+ format="%.2f",
476
+ help="Labor cost per square meter."
477
+ )
478
+
479
+ replacement_years = st.number_input(
480
+ "Replacement Years",
481
+ min_value=1,
482
+ max_value=100,
483
+ value=int(editor_state.get("replacement_years", 25)),
484
+ help="Expected lifespan before replacement."
485
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
 
487
+ # Submit buttons
488
+ col1, col2 = st.columns(2)
489
+ with col1:
490
+ submit_label = "Update Fenestration" if is_edit else "Add Fenestration"
491
+ submit = st.form_submit_button(submit_label)
492
+
493
+ with col2:
494
+ cancel = st.form_submit_button("Cancel")
495
+
496
+ # Handle form submission
497
+ if submit:
498
+ # Validate inputs
499
+ if not name.strip():
500
+ st.error("Fenestration name is required.")
501
+ else:
502
+ # Create fenestration data
503
+ fenestration_data = {
504
+ "name": name.strip(),
505
+ "type": fenestration_type,
506
+ "u_value": u_value,
507
+ "thickness": thickness,
508
+ "embodied_carbon": embodied_carbon,
509
+ "cost": {
510
+ "material": material_cost,
511
+ "labor": labor_cost,
512
+ "replacement_years": replacement_years
513
+ },
514
+ "description": f"Custom {fenestration_type.lower()}"
515
+ }
516
 
517
+ # Add type-specific properties
518
+ if fenestration_type in ["Window", "Skylight"]:
519
+ fenestration_data["shgc"] = shgc
520
+ fenestration_data["visible_transmittance"] = visible_transmittance
521
+ else:
522
+ fenestration_data["solar_absorptivity"] = solar_absorptivity
523
 
524
+ # Check if editing or adding new
525
+ if is_edit and st.session_state.fenestration_editor.get("edit_id"):
526
+ # Update existing fenestration
527
+ edit_id = st.session_state.fenestration_editor["edit_id"]
528
+ edit_source = st.session_state.fenestration_editor["edit_source"]
529
+
530
+ if edit_source == "project":
531
+ st.session_state.project_data["fenestrations"]["project"][edit_id] = fenestration_data
532
+ st.success(f"Fenestration '{name}' updated successfully!")
533
+ else:
534
+ st.error("Cannot edit library fenestrations.")
535
+ else:
536
+ # Add new fenestration to project
537
+ fenestration_id = str(uuid.uuid4())
538
+ st.session_state.project_data["fenestrations"]["project"][fenestration_id] = fenestration_data
539
+ st.success(f"Fenestration '{name}' added successfully!")
540
+
541
+ # Clear editor state
542
+ st.session_state.fenestration_editor = {}
543
+ st.session_state.materials_rerun_pending = True
544
+ st.rerun()
545
+
546
+ if cancel:
547
+ # Clear editor state
548
+ st.session_state.fenestration_editor = {}
549
+ st.session_state.materials_rerun_pending = True
550
+ st.rerun()
551
+
552
+ def display_materials_table(materials: Dict[str, Any], source: str):
553
+ """Display materials in a table format with edit/delete buttons."""
554
+ if not materials:
555
+ return
556
+
557
+ # Create table data
558
+ table_data = []
559
+ for material_id, material in materials.items():
560
+ table_data.append({
561
+ "Name": material.get("name", "Unknown"),
562
+ "Category": material.get("category", "Unknown"),
563
+ "k (W/m·K)": f"{material.get('thermal_conductivity', 0):.3f}",
564
+ "ρ (kg/m³)": f"{material.get('density', 0):.0f}",
565
+ "Cp (J/kg·K)": f"{material.get('specific_heat', 0):.0f}",
566
+ "Thickness (m)": f"{material.get('thickness_range', {}).get('default', 0):.3f}",
567
+ "Actions": material_id
568
+ })
569
+
570
+ if table_data:
571
+ df = pd.DataFrame(table_data)
572
+
573
+ # Display table
574
+ st.dataframe(df.drop('Actions', axis=1), use_container_width=True)
575
+
576
+ # Display action buttons
577
+ st.write("**Actions:**")
578
+ for i, row in enumerate(table_data):
579
+ material_id = row["Actions"]
580
+ material_name = row["Name"]
581
 
582
+ col1, col2, col3 = st.columns([2, 1, 1])
583
+
584
+ with col1:
585
+ st.write(f"{i+1}. {material_name}")
586
+
587
+ with col2:
588
+ if source == "library":
589
+ if st.button("Add to Project", key=f"add_material_{material_id}_{i}"):
590
+ # Add library material to project
591
+ project_id = str(uuid.uuid4())
592
+ st.session_state.project_data["materials"]["project"][project_id] = materials[material_id].copy()
593
+ st.success(f"Material '{material_name}' added to project!")
594
+ st.session_state.materials_rerun_pending = True
595
+ st.rerun()
596
+ else:
597
+ if st.button("Edit", key=f"edit_material_{material_id}_{i}"):
598
+ # Set up editor for editing
599
+ material = materials[material_id]
600
+ st.session_state.material_editor = {
601
+ "is_edit": True,
602
+ "edit_id": material_id,
603
+ "edit_source": source,
604
+ "name": material.get("name", ""),
605
+ "category": material.get("category", MATERIAL_CATEGORIES[0]),
606
+ "thermal_conductivity": material.get("thermal_conductivity", 0.1),
607
+ "density": material.get("density", 1000.0),
608
+ "specific_heat": material.get("specific_heat", 1000.0),
609
+ "min_thickness": material.get("thickness_range", {}).get("min", 0.01),
610
+ "max_thickness": material.get("thickness_range", {}).get("max", 0.3),
611
+ "default_thickness": material.get("thickness_range", {}).get("default", 0.1),
612
+ "embodied_carbon": material.get("embodied_carbon", 0.5),
613
+ "material_cost": material.get("cost", {}).get("material", 100.0),
614
+ "labor_cost": material.get("cost", {}).get("labor", 50.0),
615
+ "replacement_years": material.get("cost", {}).get("replacement_years", 50)
616
+ }
617
+ st.session_state.materials_rerun_pending = True
618
+ st.rerun()
619
+
620
+ with col3:
621
+ if source == "project":
622
+ if st.button("Delete", key=f"delete_material_{material_id}_{i}"):
623
+ # Delete project material
624
+ del st.session_state.project_data["materials"]["project"][material_id]
625
+ st.success(f"Material '{material_name}' deleted!")
626
+ st.session_state.materials_rerun_pending = True
627
+ st.rerun()
628
 
629
+ def display_fenestrations_table(fenestrations: Dict[str, Any], source: str):
630
+ """Display fenestrations in a table format with edit/delete buttons."""
631
+ if not fenestrations:
632
+ return
633
+
634
+ # Create table data
635
+ table_data = []
636
+ for fenestration_id, fenestration in fenestrations.items():
637
+ table_data.append({
638
+ "Name": fenestration.get("name", "Unknown"),
639
+ "Type": fenestration.get("type", "Unknown"),
640
+ "U-Value (W/m²·K)": f"{fenestration.get('u_value', 0):.2f}",
641
+ "SHGC": f"{fenestration.get('shgc', 0):.2f}" if fenestration.get('shgc') is not None else "N/A",
642
+ "VT": f"{fenestration.get('visible_transmittance', 0):.2f}" if fenestration.get('visible_transmittance') is not None else "N/A",
643
+ "Thickness (m)": f"{fenestration.get('thickness', 0):.3f}",
644
+ "Actions": fenestration_id
645
+ })
646
+
647
+ if table_data:
648
+ df = pd.DataFrame(table_data)
649
+
650
+ # Display table
651
+ st.dataframe(df.drop('Actions', axis=1), use_container_width=True)
652
+
653
+ # Display action buttons
654
+ st.write("**Actions:**")
655
+ for i, row in enumerate(table_data):
656
+ fenestration_id = row["Actions"]
657
+ fenestration_name = row["Name"]
658
+
659
+ col1, col2, col3 = st.columns([2, 1, 1])
660
+
661
+ with col1:
662
+ st.write(f"{i+1}. {fenestration_name}")
663
+
664
+ with col2:
665
+ if source == "library":
666
+ if st.button("Add to Project", key=f"add_fenestration_{fenestration_id}_{i}"):
667
+ # Add library fenestration to project
668
+ project_id = str(uuid.uuid4())
669
+ st.session_state.project_data["fenestrations"]["project"][project_id] = fenestrations[fenestration_id].copy()
670
+ st.success(f"Fenestration '{fenestration_name}' added to project!")
671
+ st.session_state.materials_rerun_pending = True
672
+ st.rerun()
673
+ else:
674
+ if st.button("Edit", key=f"edit_fenestration_{fenestration_id}_{i}"):
675
+ # Set up editor for editing
676
+ fenestration = fenestrations[fenestration_id]
677
+ editor_data = {
678
+ "is_edit": True,
679
+ "edit_id": fenestration_id,
680
+ "edit_source": source,
681
+ "name": fenestration.get("name", ""),
682
+ "type": fenestration.get("type", FENESTRATION_TYPES[0]),
683
+ "u_value": fenestration.get("u_value", 2.5),
684
+ "thickness": fenestration.get("thickness", 0.024),
685
+ "embodied_carbon": fenestration.get("embodied_carbon", 500.0),
686
+ "material_cost": fenestration.get("cost", {}).get("material", 200.0),
687
+ "labor_cost": fenestration.get("cost", {}).get("labor", 80.0),
688
+ "replacement_years": fenestration.get("cost", {}).get("replacement_years", 25)
689
+ }
690
+
691
+ # Add type-specific properties
692
+ if fenestration.get("type") in ["Window", "Skylight"]:
693
+ editor_data["shgc"] = fenestration.get("shgc", 0.7)
694
+ editor_data["visible_transmittance"] = fenestration.get("visible_transmittance", 0.8)
695
+ else:
696
+ editor_data["solar_absorptivity"] = fenestration.get("solar_absorptivity", 0.7)
697
+
698
+ st.session_state.fenestration_editor = editor_data
699
+ st.session_state.materials_rerun_pending = True
700
+ st.rerun()
701
+
702
+ with col3:
703
+ if source == "project":
704
+ if st.button("Delete", key=f"delete_fenestration_{fenestration_id}_{i}"):
705
+ # Delete project fenestration
706
+ del st.session_state.project_data["fenestrations"]["project"][fenestration_id]
707
+ st.success(f"Fenestration '{fenestration_name}' deleted!")
708
+ st.session_state.materials_rerun_pending = True
709
+ st.rerun()
710
+
711
+ def display_materials_help():
712
+ """Display help information for the materials page."""
713
+ st.markdown("""
714
+ ### Material Library Help
715
 
716
+ This page allows you to manage building materials and fenestrations for thermal analysis.
717
+
718
+ #### Materials
719
+ - **Library Materials**: Pre-defined materials with standard thermal properties
720
+ - **Project Materials**: Custom materials created for your specific project
721
+ - **Thermal Properties**:
722
+ - **Thermal Conductivity (k)**: Rate of heat transfer through the material (W/m·K)
723
+ - **Density (ρ)**: Mass per unit volume (kg/m³)
724
+ - **Specific Heat (Cp)**: Heat capacity per unit mass (J/kg·K)
725
+
726
+ #### Fenestrations
727
+ - **Windows/Skylights**: Include solar heat gain coefficient (SHGC) and visible transmittance
728
+ - **Doors**: Include solar absorptivity for opaque surfaces
729
+ - **U-Value**: Overall heat transfer coefficient (W/m²·K)
730
+
731
+ #### Usage
732
+ 1. Browse library materials/fenestrations and add them to your project
733
+ 2. Create custom materials/fenestrations using the editor
734
+ 3. Edit or delete project materials/fenestrations as needed
735
+ 4. All materials will be available for use in construction assemblies
736
+ """)
737
 
738
+ # Helper functions for backward compatibility
739
+ def get_available_materials():
740
+ """Get all available materials (library + project) for use in other modules."""
741
+ materials = {}
 
 
 
742
 
743
+ if "materials" in st.session_state.project_data:
744
+ # Add library materials
745
+ if "library" in st.session_state.project_data["materials"]:
746
+ materials.update(st.session_state.project_data["materials"]["library"])
 
 
 
 
 
 
 
747
 
748
+ # Add project materials
749
+ if "project" in st.session_state.project_data["materials"]:
750
+ materials.update(st.session_state.project_data["materials"]["project"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
 
752
+ return materials
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
753
 
754
+ def get_available_fenestrations():
755
+ """Get all available fenestrations (library + project) for use in other modules."""
756
+ fenestrations = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
757
 
758
+ if "fenestrations" in st.session_state.project_data:
759
+ # Add library fenestrations
760
+ if "library" in st.session_state.project_data["fenestrations"]:
761
+ fenestrations.update(st.session_state.project_data["fenestrations"]["library"])
762
 
763
+ # Add project fenestrations
764
+ if "project" in st.session_state.project_data["fenestrations"]:
765
+ fenestrations.update(st.session_state.project_data["fenestrations"]["project"])
 
 
 
 
 
 
 
766
 
767
+ return fenestrations
 
 
 
 
 
 
 
 
768