Spaces:
Sleeping
Sleeping
Update app/materials_library.py
Browse files- app/materials_library.py +144 -130
app/materials_library.py
CHANGED
@@ -56,7 +56,7 @@ DEFAULT_MATERIALS = {
|
|
56 |
"density": 1920.0,
|
57 |
"specific_heat": 840.0,
|
58 |
"default_thickness": 0.1,
|
59 |
-
"embodied_carbon": 240
|
60 |
"cost": 180.0,
|
61 |
"absorptivity": 0.65,
|
62 |
"emissivity": 0.90,
|
@@ -345,72 +345,25 @@ def categorize_thermal_mass(thermal_mass: float) -> str:
|
|
345 |
else:
|
346 |
return "High"
|
347 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
348 |
def display_materials_page():
|
349 |
"""
|
350 |
Display the material library page.
|
351 |
This is the main function called by main.py when the Material Library page is selected.
|
352 |
"""
|
353 |
# Initialize session state flags
|
354 |
-
if "
|
355 |
-
st.session_state.
|
356 |
-
if "
|
357 |
-
st.session_state.
|
358 |
-
if "table_action" not in st.session_state:
|
359 |
-
st.session_state.table_action = None
|
360 |
-
|
361 |
-
# Process table actions before rendering UI
|
362 |
-
if st.session_state.table_action:
|
363 |
-
action = st.session_state.table_action
|
364 |
-
action_type = action.get("type")
|
365 |
-
name = action.get("name")
|
366 |
-
is_material = action.get("is_material", True)
|
367 |
-
|
368 |
-
if action_type == "preview":
|
369 |
-
editor_key = "material_editor" if is_material else "fenestration_editor"
|
370 |
-
data = (st.session_state.project_data["materials" if is_material else "fenestrations"]
|
371 |
-
["library" if action["is_library"] else "project"][name])
|
372 |
-
st.session_state[editor_key] = {
|
373 |
-
**data,
|
374 |
-
"name": name,
|
375 |
-
"edit_mode": False,
|
376 |
-
"original_name": name,
|
377 |
-
"is_library": action["is_library"]
|
378 |
-
}
|
379 |
-
st.session_state.active_tab = "Materials" if is_material else "Fenestrations"
|
380 |
-
st.session_state.table_action = None
|
381 |
-
|
382 |
-
elif action_type == "copy":
|
383 |
-
library_data = st.session_state.project_data["materials" if is_material else "fenestrations"]["library"]
|
384 |
-
project_data = st.session_state.project_data["materials" if is_material else "fenestrations"]["project"]
|
385 |
-
new_name = f"{name}_Project"
|
386 |
-
counter = 1
|
387 |
-
while new_name in project_data or new_name in library_data:
|
388 |
-
new_name = f"{name}_Project_{counter}"
|
389 |
-
counter += 1
|
390 |
-
project_data[new_name] = library_data[name].copy()
|
391 |
-
logger.info(f"Copied library {'material' if is_material else 'fenestration'} '{name}' as '{new_name}' to project")
|
392 |
-
st.session_state.table_action = None
|
393 |
-
|
394 |
-
elif action_type == "edit":
|
395 |
-
st.session_state.material_editor = {
|
396 |
-
**st.session_state.project_data["materials"]["project"][name],
|
397 |
-
"name": name,
|
398 |
-
"edit_mode": True,
|
399 |
-
"original_name": name,
|
400 |
-
"is_library": False
|
401 |
-
}
|
402 |
-
st.session_state.active_tab = "Materials" if is_material else "Fenestrations"
|
403 |
-
st.session_state.table_action = None
|
404 |
-
|
405 |
-
elif action_type == "delete":
|
406 |
-
check_fn = check_material_in_use if is_material else check_fenestration_in_use
|
407 |
-
project_data = st.session_state.project_data["materials" if is_material else "fenestrations"]["project"]
|
408 |
-
if check_fn(name):
|
409 |
-
st.error(f"Cannot delete {'material' if is_material else 'fenestration'} '{name}' because it is in use.")
|
410 |
-
else:
|
411 |
-
del project_data[name]
|
412 |
-
logger.info(f"Deleted {'material' if is_material else 'fenestration'} '{name}' from project")
|
413 |
-
st.session_state.table_action = None
|
414 |
|
415 |
st.title("Material Library")
|
416 |
|
@@ -435,24 +388,17 @@ def display_materials_page():
|
|
435 |
with col1:
|
436 |
if st.button("Back to Climate Data", key="back_to_climate"):
|
437 |
st.session_state.current_page = "Climate Data"
|
438 |
-
st.session_state.material_form_action = None
|
439 |
-
st.session_state.fenestration_form_action = None
|
440 |
-
st.session_state.table_action = None
|
441 |
st.rerun()
|
442 |
|
443 |
with col2:
|
444 |
if st.button("Continue to Construction", key="continue_to_construction"):
|
445 |
st.session_state.current_page = "Construction"
|
446 |
-
st.session_state.material_form_action = None
|
447 |
-
st.session_state.fenestration_form_action = None
|
448 |
-
st.session_state.table_action = None
|
449 |
st.rerun()
|
450 |
|
451 |
-
#
|
452 |
-
if st.session_state.
|
453 |
-
st.session_state.
|
454 |
-
st.session_state.
|
455 |
-
st.session_state.table_action = None
|
456 |
st.rerun()
|
457 |
|
458 |
def display_materials_tab():
|
@@ -494,22 +440,41 @@ def display_materials_tab():
|
|
494 |
cols[0].write(name)
|
495 |
cols[1].write(thermal_mass_category)
|
496 |
cols[2].write(f"{u_value:.3f}")
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
}
|
505 |
-
unique_id = str(uuid.uuid4())
|
506 |
-
if cols[4].button("Copy", key=f"copy_lib_mat_{name}_{unique_id}"):
|
507 |
-
st.session_state.table_action = {
|
508 |
-
"type": "copy",
|
509 |
"name": name,
|
510 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
511 |
"is_library": True
|
512 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
513 |
else:
|
514 |
st.info("No materials found in the selected category.")
|
515 |
|
@@ -541,22 +506,40 @@ def display_materials_tab():
|
|
541 |
cols[0].write(name)
|
542 |
cols[1].write(thermal_mass_category)
|
543 |
cols[2].write(f"{u_value:.3f}")
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
|
|
|
|
|
|
548 |
"name": name,
|
549 |
-
"
|
550 |
-
"
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
"
|
556 |
-
"
|
557 |
-
"
|
|
|
|
|
|
|
558 |
"is_library": False
|
559 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
560 |
else:
|
561 |
st.info("No project materials in the selected category.")
|
562 |
|
@@ -631,22 +614,38 @@ def display_fenestrations_tab():
|
|
631 |
cols[0].write(name)
|
632 |
cols[1].write(fenestration["type"])
|
633 |
cols[2].write(f"{fenestration['u_value']:.2f}")
|
634 |
-
|
635 |
-
|
636 |
-
|
637 |
-
|
|
|
|
|
|
|
638 |
"name": name,
|
639 |
-
"
|
640 |
-
"
|
641 |
-
|
642 |
-
|
643 |
-
|
644 |
-
|
645 |
-
"
|
646 |
-
"
|
647 |
-
"
|
648 |
"is_library": True
|
649 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
650 |
else:
|
651 |
st.info("No fenestrations found in the selected type.")
|
652 |
|
@@ -675,22 +674,37 @@ def display_fenestrations_tab():
|
|
675 |
cols[0].write(name)
|
676 |
cols[1].write(fenestration["type"])
|
677 |
cols[2].write(f"{fenestration['u_value']:.2f}")
|
678 |
-
|
679 |
-
|
680 |
-
|
681 |
-
|
682 |
-
|
683 |
-
|
684 |
-
|
685 |
-
}
|
686 |
-
unique_id = str(uuid.uuid4())
|
687 |
-
if cols[4].button("Delete", key=f"delete_proj_fen_{name}_{unique_id}"):
|
688 |
-
st.session_state.table_action = {
|
689 |
-
"type": "delete",
|
690 |
"name": name,
|
691 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
692 |
"is_library": False
|
693 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
694 |
else:
|
695 |
st.info("No project fenestrations in the selected type.")
|
696 |
|
@@ -967,14 +981,14 @@ def display_material_editor():
|
|
967 |
st.success(f"Material '{name}' added to your project.")
|
968 |
logger.info(f"Added new material '{name}' to project")
|
969 |
|
970 |
-
# Reset editor and flag
|
971 |
reset_material_editor()
|
972 |
-
st.session_state.
|
973 |
|
974 |
# Handle clear button
|
975 |
if clear_button:
|
976 |
reset_material_editor()
|
977 |
-
st.session_state.
|
978 |
|
979 |
def display_fenestration_editor():
|
980 |
"""Display the fenestration editor form."""
|
@@ -1121,14 +1135,14 @@ def display_fenestration_editor():
|
|
1121 |
st.success(f"Fenestration '{name}' added to your project.")
|
1122 |
logger.info(f"Added new fenestration '{name}' to project")
|
1123 |
|
1124 |
-
# Reset editor and flag
|
1125 |
reset_fenestration_editor()
|
1126 |
-
st.session_state.
|
1127 |
|
1128 |
# Handle clear button
|
1129 |
if clear_button:
|
1130 |
reset_fenestration_editor()
|
1131 |
-
st.session_state.
|
1132 |
|
1133 |
def validate_material(
|
1134 |
name: str, category: str, thermal_conductivity: float, density: float,
|
|
|
56 |
"density": 1920.0,
|
57 |
"specific_heat": 840.0,
|
58 |
"default_thickness": 0.1,
|
59 |
+
"embodied_carbon": 240,
|
60 |
"cost": 180.0,
|
61 |
"absorptivity": 0.65,
|
62 |
"emissivity": 0.90,
|
|
|
345 |
else:
|
346 |
return "High"
|
347 |
|
348 |
+
def get_stable_button_key(prefix: str, name: str, action: str) -> str:
|
349 |
+
"""Generate a stable button key based on prefix, name, and action."""
|
350 |
+
# Create a stable hash-based key that won't change across reruns
|
351 |
+
import hashlib
|
352 |
+
key_string = f"{prefix}_{name}_{action}"
|
353 |
+
# Use first 8 characters of hash for shorter keys
|
354 |
+
hash_key = hashlib.md5(key_string.encode()).hexdigest()[:8]
|
355 |
+
return f"{prefix}_{action}_{hash_key}"
|
356 |
+
|
357 |
def display_materials_page():
|
358 |
"""
|
359 |
Display the material library page.
|
360 |
This is the main function called by main.py when the Material Library page is selected.
|
361 |
"""
|
362 |
# Initialize session state flags
|
363 |
+
if "material_saved" not in st.session_state:
|
364 |
+
st.session_state.material_saved = False
|
365 |
+
if "fenestration_saved" not in st.session_state:
|
366 |
+
st.session_state.fenestration_saved = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
367 |
|
368 |
st.title("Material Library")
|
369 |
|
|
|
388 |
with col1:
|
389 |
if st.button("Back to Climate Data", key="back_to_climate"):
|
390 |
st.session_state.current_page = "Climate Data"
|
|
|
|
|
|
|
391 |
st.rerun()
|
392 |
|
393 |
with col2:
|
394 |
if st.button("Continue to Construction", key="continue_to_construction"):
|
395 |
st.session_state.current_page = "Construction"
|
|
|
|
|
|
|
396 |
st.rerun()
|
397 |
|
398 |
+
# Handle material/fenestration saved flags - trigger rerun only once
|
399 |
+
if st.session_state.material_saved or st.session_state.fenestration_saved:
|
400 |
+
st.session_state.material_saved = False
|
401 |
+
st.session_state.fenestration_saved = False
|
|
|
402 |
st.rerun()
|
403 |
|
404 |
def display_materials_tab():
|
|
|
440 |
cols[0].write(name)
|
441 |
cols[1].write(thermal_mass_category)
|
442 |
cols[2].write(f"{u_value:.3f}")
|
443 |
+
|
444 |
+
# Use stable keys for buttons
|
445 |
+
preview_key = get_stable_button_key("lib_mat", name, "preview")
|
446 |
+
copy_key = get_stable_button_key("lib_mat", name, "copy")
|
447 |
+
|
448 |
+
if cols[3].button("Preview", key=preview_key):
|
449 |
+
st.session_state.material_editor = {
|
|
|
|
|
|
|
|
|
|
|
450 |
"name": name,
|
451 |
+
"category": material["category"],
|
452 |
+
"thermal_conductivity": material["thermal_conductivity"],
|
453 |
+
"density": material["density"],
|
454 |
+
"specific_heat": material["specific_heat"],
|
455 |
+
"default_thickness": material["default_thickness"],
|
456 |
+
"embodied_carbon": material["embodied_carbon"],
|
457 |
+
"cost": material["cost"],
|
458 |
+
"absorptivity": material["absorptivity"],
|
459 |
+
"emissivity": material["emissivity"],
|
460 |
+
"colour": material["colour"],
|
461 |
+
"edit_mode": False,
|
462 |
+
"original_name": name,
|
463 |
"is_library": True
|
464 |
}
|
465 |
+
st.session_state.active_tab = "Materials"
|
466 |
+
st.success(f"Previewing material '{name}'")
|
467 |
+
|
468 |
+
if cols[4].button("Copy", key=copy_key):
|
469 |
+
new_name = f"{name}_Project"
|
470 |
+
counter = 1
|
471 |
+
while new_name in st.session_state.project_data["materials"]["project"] or new_name in library_materials:
|
472 |
+
new_name = f"{name}_Project_{counter}"
|
473 |
+
counter += 1
|
474 |
+
st.session_state.project_data["materials"]["project"][new_name] = material.copy()
|
475 |
+
st.success(f"Material '{new_name}' copied to project.")
|
476 |
+
logger.info(f"Copied library material '{name}' as '{new_name}' to project")
|
477 |
+
st.rerun()
|
478 |
else:
|
479 |
st.info("No materials found in the selected category.")
|
480 |
|
|
|
506 |
cols[0].write(name)
|
507 |
cols[1].write(thermal_mass_category)
|
508 |
cols[2].write(f"{u_value:.3f}")
|
509 |
+
|
510 |
+
# Use stable keys for buttons
|
511 |
+
edit_key = get_stable_button_key("proj_mat", name, "edit")
|
512 |
+
delete_key = get_stable_button_key("proj_mat", name, "delete")
|
513 |
+
|
514 |
+
if cols[3].button("Edit", key=edit_key):
|
515 |
+
st.session_state.material_editor = {
|
516 |
"name": name,
|
517 |
+
"category": material["category"],
|
518 |
+
"thermal_conductivity": material["thermal_conductivity"],
|
519 |
+
"density": material["density"],
|
520 |
+
"specific_heat": material["specific_heat"],
|
521 |
+
"default_thickness": material["default_thickness"],
|
522 |
+
"embodied_carbon": material["embodied_carbon"],
|
523 |
+
"cost": material["cost"],
|
524 |
+
"absorptivity": material["absorptivity"],
|
525 |
+
"emissivity": material["emissivity"],
|
526 |
+
"colour": material["colour"],
|
527 |
+
"edit_mode": True,
|
528 |
+
"original_name": name,
|
529 |
"is_library": False
|
530 |
}
|
531 |
+
st.session_state.active_tab = "Materials"
|
532 |
+
st.success(f"Editing material '{name}'")
|
533 |
+
|
534 |
+
if cols[4].button("Delete", key=delete_key):
|
535 |
+
is_in_use = check_material_in_use(name)
|
536 |
+
if is_in_use:
|
537 |
+
st.error(f"Cannot delete material '{name}' because it is in use in constructions.")
|
538 |
+
else:
|
539 |
+
del st.session_state.project_data["materials"]["project"][name]
|
540 |
+
st.success(f"Material '{name}' deleted from project.")
|
541 |
+
logger.info(f"Deleted material '{name}' from project")
|
542 |
+
st.rerun()
|
543 |
else:
|
544 |
st.info("No project materials in the selected category.")
|
545 |
|
|
|
614 |
cols[0].write(name)
|
615 |
cols[1].write(fenestration["type"])
|
616 |
cols[2].write(f"{fenestration['u_value']:.2f}")
|
617 |
+
|
618 |
+
# Use stable keys for buttons
|
619 |
+
preview_key = get_stable_button_key("lib_fen", name, "preview")
|
620 |
+
copy_key = get_stable_button_key("lib_fen", name, "copy")
|
621 |
+
|
622 |
+
if cols[3].button("Preview", key=preview_key):
|
623 |
+
st.session_state.fenestration_editor = {
|
624 |
"name": name,
|
625 |
+
"type": fenestration["type"],
|
626 |
+
"u_value": fenestration["u_value"],
|
627 |
+
"shgc": fenestration["shgc"],
|
628 |
+
"visible_transmittance": fenestration["visible_transmittance"],
|
629 |
+
"thickness": fenestration["thickness"],
|
630 |
+
"embodied_carbon": fenestration["embodied_carbon"],
|
631 |
+
"cost": fenestration["cost"],
|
632 |
+
"edit_mode": False,
|
633 |
+
"original_name": name,
|
634 |
"is_library": True
|
635 |
}
|
636 |
+
st.session_state.active_tab = "Fenestrations"
|
637 |
+
st.success(f"Previewing fenestration '{name}'")
|
638 |
+
|
639 |
+
if cols[4].button("Copy", key=copy_key):
|
640 |
+
new_name = f"{name}_Project"
|
641 |
+
counter = 1
|
642 |
+
while new_name in st.session_state.project_data["fenestrations"]["project"] or new_name in library_fenestrations:
|
643 |
+
new_name = f"{name}_Project_{counter}"
|
644 |
+
counter += 1
|
645 |
+
st.session_state.project_data["fenestrations"]["project"][new_name] = fenestration.copy()
|
646 |
+
st.success(f"Fenestration '{new_name}' copied to project.")
|
647 |
+
logger.info(f"Copied library fenestration '{name}' as '{new_name}' to project")
|
648 |
+
st.rerun()
|
649 |
else:
|
650 |
st.info("No fenestrations found in the selected type.")
|
651 |
|
|
|
674 |
cols[0].write(name)
|
675 |
cols[1].write(fenestration["type"])
|
676 |
cols[2].write(f"{fenestration['u_value']:.2f}")
|
677 |
+
|
678 |
+
# Use stable keys for buttons
|
679 |
+
edit_key = get_stable_button_key("proj_fen", name, "edit")
|
680 |
+
delete_key = get_stable_button_key("proj_fen", name, "delete")
|
681 |
+
|
682 |
+
if cols[3].button("Edit", key=edit_key):
|
683 |
+
st.session_state.fenestration_editor = {
|
|
|
|
|
|
|
|
|
|
|
684 |
"name": name,
|
685 |
+
"type": fenestration["type"],
|
686 |
+
"u_value": fenestration["u_value"],
|
687 |
+
"shgc": fenestration["shgc"],
|
688 |
+
"visible_transmittance": fenestration["visible_transmittance"],
|
689 |
+
"thickness": fenestration["thickness"],
|
690 |
+
"embodied_carbon": fenestration["embodied_carbon"],
|
691 |
+
"cost": fenestration["cost"],
|
692 |
+
"edit_mode": True,
|
693 |
+
"original_name": name,
|
694 |
"is_library": False
|
695 |
}
|
696 |
+
st.session_state.active_tab = "Fenestrations"
|
697 |
+
st.success(f"Editing fenestration '{name}'")
|
698 |
+
|
699 |
+
if cols[4].button("Delete", key=delete_key):
|
700 |
+
is_in_use = check_fenestration_in_use(name)
|
701 |
+
if is_in_use:
|
702 |
+
st.error(f"Cannot delete fenestration '{name}' because it is in use in components.")
|
703 |
+
else:
|
704 |
+
del st.session_state.project_data["fenestrations"]["project"][name]
|
705 |
+
st.success(f"Fenestration '{name}' deleted from project.")
|
706 |
+
logger.info(f"Deleted fenestration '{name}' from project")
|
707 |
+
st.rerun()
|
708 |
else:
|
709 |
st.info("No project fenestrations in the selected type.")
|
710 |
|
|
|
981 |
st.success(f"Material '{name}' added to your project.")
|
982 |
logger.info(f"Added new material '{name}' to project")
|
983 |
|
984 |
+
# Reset editor and flag for rerun
|
985 |
reset_material_editor()
|
986 |
+
st.session_state.material_saved = True
|
987 |
|
988 |
# Handle clear button
|
989 |
if clear_button:
|
990 |
reset_material_editor()
|
991 |
+
st.session_state.material_saved = True
|
992 |
|
993 |
def display_fenestration_editor():
|
994 |
"""Display the fenestration editor form."""
|
|
|
1135 |
st.success(f"Fenestration '{name}' added to your project.")
|
1136 |
logger.info(f"Added new fenestration '{name}' to project")
|
1137 |
|
1138 |
+
# Reset editor and flag for rerun
|
1139 |
reset_fenestration_editor()
|
1140 |
+
st.session_state.fenestration_saved = True
|
1141 |
|
1142 |
# Handle clear button
|
1143 |
if clear_button:
|
1144 |
reset_fenestration_editor()
|
1145 |
+
st.session_state.fenestration_saved = True
|
1146 |
|
1147 |
def validate_material(
|
1148 |
name: str, category: str, thermal_conductivity: float, density: float,
|