import base64 import re from pathlib import Path import pandas as pd import streamlit as st from PIL import Image from data_loader import get_all_sections, load_material_data st.markdown( """ """, unsafe_allow_html=True, ) @st.cache_data def load_data(material_type: str) -> pd.DataFrame: return load_material_data(material_type) @st.cache_data def load_all_data() -> pd.DataFrame: frames = [] for material_type in ["Composites", "Polymers", "Fibers"]: frame = load_data(material_type) frame["_class"] = material_type frames.append(frame) return pd.concat(frames, ignore_index=True) def extract_matrix_fiber(abbr: str): if not isinstance(abbr, str): return None, None text = abbr.lower() matrix_map = { "epoxy": "Epoxy", "cyanate ester": "Cyanate Ester", "cynate ester": "Cyanate Ester", "polypropylene": "Polypropylene", "pp": "Polypropylene", "peek": "PEEK", "pei": "PEI", "nylon": "Nylon", "pa6": "PA6", "polyester": "Polyester", "vinyl ester": "Vinyl Ester", "phenolic": "Phenolic", } fiber_map = { "carbon": "Carbon Fiber", "glass": "Glass Fiber", "e-glass": "E-Glass Fiber", "s-glass": "S-Glass Fiber", "aramid": "Aramid Fiber", "kevlar": "Kevlar Fiber", "basalt": "Basalt Fiber", "natural": "Natural Fiber", } matrix = next((value for key, value in matrix_map.items() if key in text), None) fiber = next((value for key, value in fiber_map.items() if key in text), None) return matrix, fiber def toggle_class(material_class: str): if st.session_state.active_classes == [material_class]: st.session_state.active_classes = [] else: st.session_state.active_classes = [material_class] st.session_state.current_page = 0 def visible_page_numbers(current_page: int, total_pages: int): if total_pages <= 6: return list(range(total_pages)) pages = {0, 1, 2, current_page, total_pages - 1} if current_page - 1 > 2: pages.add(current_page - 1) if current_page + 1 < total_pages - 1: pages.add(current_page + 1) return sorted(pages) defaults = { "active_classes": [], "selected_props": [], "selected_matrix": "All", "selected_fiber": "All", "selected_row": None, "current_page": 0, "_search_term": None, "top_search_input": "", "inspect_section": None, "inspect_property": None, "_reset_prop_checks": False, } for key, value in defaults.items(): if key not in st.session_state: st.session_state[key] = value if "material_type" in st.session_state: incoming_type = st.session_state.pop("material_type") if incoming_type in ["Composites", "Polymers", "Fibers"]: st.session_state.active_classes = [incoming_type] if "selected_section" in st.session_state: st.session_state.selected_props = [st.session_state.pop("selected_section")] st.session_state._reset_prop_checks = True if "search_term" in st.session_state: st.session_state._search_term = st.session_state.pop("search_term") if st.session_state._search_term and not st.session_state.top_search_input: st.session_state.top_search_input = st.session_state._search_term all_data = load_all_data() if "user_uploaded_data" in st.session_state: uploaded = st.session_state["user_uploaded_data"].copy() uploaded["_class"] = uploaded["material_class"].map( {"Polymer": "Polymers", "Fiber": "Fibers", "Composite": "Composites"} ) all_data = pd.concat([all_data, uploaded], ignore_index=True) st.session_state["base_data"] = all_data meta = ( all_data[["material_abbreviation", "material_name", "_class"]] .fillna("") .drop_duplicates(subset=["material_abbreviation"]) .reset_index(drop=True) ) matrix_fiber = meta["material_abbreviation"].apply(extract_matrix_fiber) meta["Matrix"] = matrix_fiber.apply( lambda pair: pair[0] if isinstance(pair, tuple) and len(pair) >= 2 else None ) meta["Fiber"] = matrix_fiber.apply( lambda pair: pair[1] if isinstance(pair, tuple) and len(pair) >= 2 else None ) all_sections = get_all_sections() if st.session_state._reset_prop_checks: selected = set(st.session_state.selected_props) for index, section in enumerate(all_sections): st.session_state[f"prop_check_{index}"] = section in selected st.session_state._reset_prop_checks = False filtered_meta = meta.copy() if st.session_state.active_classes: filtered_meta = filtered_meta[filtered_meta["_class"].isin(st.session_state.active_classes)] if st.session_state.selected_matrix != "All": filtered_meta = filtered_meta[filtered_meta["Matrix"] == st.session_state.selected_matrix] if st.session_state.selected_fiber != "All": filtered_meta = filtered_meta[filtered_meta["Fiber"] == st.session_state.selected_fiber] if st.session_state._search_term: term = st.session_state._search_term try: pattern = re.compile(term, re.IGNORECASE) except re.error: pattern = re.compile(re.escape(term), re.IGNORECASE) filtered_meta = filtered_meta[ filtered_meta["material_abbreviation"].astype(str).str.contains(pattern) | filtered_meta["material_name"].astype(str).str.contains(pattern) ] if st.session_state.selected_props: valid_abbr = all_data[ all_data["section"].isin(st.session_state.selected_props) & all_data["value"].notna() ]["material_abbreviation"].unique() filtered_meta = filtered_meta[filtered_meta["material_abbreviation"].isin(valid_abbr)] filtered_meta = filtered_meta.reset_index(drop=True) PAGE_SIZE = 50 total = len(filtered_meta) total_pages = max(1, (total + PAGE_SIZE - 1) // PAGE_SIZE) st.session_state.current_page = min(st.session_state.current_page, total_pages - 1) start = st.session_state.current_page * PAGE_SIZE end = start + PAGE_SIZE page_meta = filtered_meta.iloc[start:end].reset_index(drop=True) left_col, right_col = st.columns([1.03, 3.07], gap="small") with left_col: with st.container(border=True): logo_html = "" logo_path = Path("logo.png") if logo_path.exists(): with logo_path.open("rb") as file_handle: logo_b64 = base64.b64encode(file_handle.read()).decode() logo_html = ( f"" ) st.markdown( f"", unsafe_allow_html=True, ) st.markdown("
๐Ÿงฉ Material Class
", unsafe_allow_html=True) cls_a, cls_b = st.columns(2) with cls_a: if st.button( "Composites", key="class_comp", use_container_width=True, type="primary" if "Composites" in st.session_state.active_classes else "secondary", ): toggle_class("Composites") st.rerun() with cls_b: if st.button( "Polymers", key="class_poly", use_container_width=True, type="primary" if "Polymers" in st.session_state.active_classes else "secondary", ): toggle_class("Polymers") st.rerun() if st.button( "Fibers", key="class_fib", use_container_width=True, type="primary" if "Fibers" in st.session_state.active_classes else "secondary", ): toggle_class("Fibers") st.rerun() st.markdown("
๐Ÿงช Composition
", unsafe_allow_html=True) composite_meta = meta[meta["_class"] == "Composites"] matrix_options = ["All"] + sorted([item for item in composite_meta["Matrix"].dropna().unique() if item]) fiber_options = ["All"] + sorted([item for item in composite_meta["Fiber"].dropna().unique() if item]) composition_disabled = st.session_state.active_classes != ["Composites"] st.markdown("
๐Ÿงฑ Matrix
", unsafe_allow_html=True) matrix_value = st.selectbox( "Matrix", matrix_options, index=matrix_options.index(st.session_state.selected_matrix) if st.session_state.selected_matrix in matrix_options else 0, key="matrix_select", disabled=composition_disabled, ) st.markdown("
๐Ÿงต Fiber
", unsafe_allow_html=True) fiber_value = st.selectbox( "Fiber", fiber_options, index=fiber_options.index(st.session_state.selected_fiber) if st.session_state.selected_fiber in fiber_options else 0, key="fiber_select", label_visibility="collapsed", disabled=composition_disabled, ) if matrix_value != st.session_state.selected_matrix: st.session_state.selected_matrix = matrix_value st.session_state.current_page = 0 st.rerun() if fiber_value != st.session_state.selected_fiber: st.session_state.selected_fiber = fiber_value st.session_state.current_page = 0 st.rerun() #st.markdown("
๐Ÿ“‹ Property Types
", unsafe_allow_html=True) #selected_props = [] #with st.container(height=480): # for index, section in enumerate(all_sections): # key = f"prop_check_{index}" # if key not in st.session_state: # st.session_state[key] = section in st.session_state.selected_props # if st.checkbox(section, key=key): # selected_props.append(section) #if selected_props != st.session_state.selected_props: # st.session_state.selected_props = selected_props # st.session_state.current_page = 0 # st.rerun() if st.button( "๐Ÿ”Ž Inspect", key="inspect_btn", use_container_width=True, type="primary", disabled=st.session_state.selected_row is None, ): st.info("Open the Inspect tab on the right panel.") if st.session_state.selected_row: selected_abbr, selected_name = st.session_state.selected_row st.markdown( f"
Selected
{selected_name}
{selected_abbr}
", unsafe_allow_html=True, ) with right_col: with st.container(border=True): with st.container(key="top_search_row"): input_col, btn_col = st.columns([0.82, 0.18], gap="small") with input_col: search_query = st.text_input( label="Search", placeholder="Search by material name, property, or abbreviation...", label_visibility="collapsed", key="top_search_input", ) with btn_col: search_clicked = st.button("Search", key="top_search_btn", use_container_width=True) if search_clicked: query = (search_query or "").strip() st.session_state._search_term = query if query else None st.session_state.current_page = 0 st.rerun() with st.container(border=True): st.markdown( """
INVENTORY / MATERIALS DATABASE
Materials Database
""", unsafe_allow_html=True, ) tab_materials, tab_dashboard, tab_inspect = st.tabs( ["All Materials", "Dashboard", "Inspect"] ) with tab_materials: filter_label = ( ", ".join(st.session_state.active_classes) if st.session_state.active_classes else "All Materials" ) shown_start = start + 1 if total > 0 else 0 shown_end = min(end, total) row_left, row_right = st.columns([2.2, 1.2]) with row_left: st.markdown(f"
{filter_label}
", unsafe_allow_html=True) with row_right: st.markdown( f"
Showing {shown_start}-{shown_end} of {total} materials
", unsafe_allow_html=True, ) selected_abbr = st.session_state.selected_row[0] if st.session_state.selected_row else None class_map = { "Composites": "๐Ÿ”ต COMPOSITE", "Polymers": "๐ŸŸข POLYMER", "Fibers": "๐ŸŸ  FIBER", } if not page_meta.empty: table_df = page_meta.copy() table_df["Select"] = table_df["material_abbreviation"].eq(selected_abbr) table_df["Class"] = table_df["_class"].map(class_map) table_df["Actions"] = "" table_df = table_df[ ["Select", "material_name", "material_abbreviation", "Class", "Actions"] ].rename( columns={ "material_name": "Material Name", "material_abbreviation": "Abbreviation", } ) else: table_df = pd.DataFrame( columns=["Select", "Material Name", "Abbreviation", "Class", "Actions"] ) editor_df = st.data_editor( table_df, key=f"materials_editor_{st.session_state.current_page}", use_container_width=True, hide_index=True, height=400, row_height=46, column_order=["Select", "Material Name", "Abbreviation", "Class", "Actions"], column_config={ "Select": st.column_config.CheckboxColumn("", width="small"), "Material Name": st.column_config.TextColumn("MATERIAL NAME", width="large"), "Abbreviation": st.column_config.TextColumn("ABBREVIATION", width="medium"), "Class": st.column_config.TextColumn("CLASS", width="small"), "Actions": st.column_config.TextColumn("ACTIONS", width="small"), }, disabled=["Material Name", "Abbreviation", "Class", "Actions"], ) #checked_rows = editor_df[ # editor_df["Select"] # & editor_df["Abbreviation"].astype(str).str.strip().ne("") #] #if not checked_rows.empty: # chosen = checked_rows.iloc[0] # abbr = chosen["Abbreviation"] # name = chosen["Material Name"] # if ( # st.session_state.selected_row is None # or st.session_state.selected_row[0] != abbr # ): # st.session_state.selected_row = (abbr, name) # st.rerun() checked_rows = editor_df[ editor_df["Select"] & editor_df["Abbreviation"].astype(str).str.strip().ne("") ] if not checked_rows.empty: current_abbr = st.session_state.selected_row[0] if st.session_state.selected_row else None new_rows = checked_rows[checked_rows["Abbreviation"] != current_abbr] if not new_rows.empty: chosen = new_rows.iloc[0] else: chosen = checked_rows.iloc[0] abbr = chosen["Abbreviation"] name = chosen["Material Name"] if st.session_state.selected_row is None or st.session_state.selected_row[0] != abbr: st.session_state.selected_row = (abbr, name) st.rerun() else: if st.session_state.selected_row is not None: st.session_state.selected_row = None st.rerun() info_col, nav_col = st.columns([2.4, 2.0]) with info_col: st.markdown( f"
Showing {shown_start}-{shown_end} of {total} materials | Items per page: {PAGE_SIZE}
", unsafe_allow_html=True, ) with nav_col: nav_items = [] nav_items.append(("<<", 0, st.session_state.current_page == 0, False)) nav_items.append(("<", max(0, st.session_state.current_page - 1), st.session_state.current_page == 0, False)) visible_pages = visible_page_numbers(st.session_state.current_page, total_pages) previous = -1 for number in visible_pages: if previous >= 0 and number - previous > 1: nav_items.append(("...", None, True, False)) nav_items.append( ( str(number + 1), number, False, number == st.session_state.current_page, ) ) previous = number nav_items.append( ( ">", min(total_pages - 1, st.session_state.current_page + 1), st.session_state.current_page >= total_pages - 1, False, ) ) nav_items.append( ( ">>", total_pages - 1, st.session_state.current_page >= total_pages - 1, False, ) ) nav_columns = st.columns(len(nav_items)) for idx, (column, item) in enumerate(zip(nav_columns, nav_items)): label, target_page, disabled, active = item with column: if label == "...": st.markdown("
...
", unsafe_allow_html=True) else: if st.button( label, key=f"page_btn_{idx}_{label}_{target_page}", use_container_width=True, disabled=disabled, type="primary" if active else "secondary", ): st.session_state.current_page = target_page st.rerun() with tab_dashboard: st.markdown( "
Dashboard coming soon. Analytics and visualizations will appear here.
", unsafe_allow_html=True, ) with tab_inspect: if not st.session_state.selected_row: st.warning("Select a material in All Materials first.") else: selected_abbr, selected_name = st.session_state.selected_row st.markdown(f"**Material:** {selected_name}") st.caption(selected_abbr) material_df = all_data[ (all_data["material_abbreviation"] == selected_abbr) & (all_data["value"].notna()) & (all_data["property_name"].notna()) ] section_options = sorted(material_df["section"].dropna().unique().tolist()) if not section_options: st.warning("No property data found for this material.") else: if st.session_state.inspect_section not in section_options: st.session_state.inspect_section = section_options[0] section_choice = st.selectbox( "Type of Property", section_options, index=section_options.index(st.session_state.inspect_section), key="inspect_section_select", ) st.session_state.inspect_section = section_choice properties_df = ( material_df[material_df["section"] == section_choice][ ["property_name", "section"] ] .drop_duplicates() .reset_index(drop=True) ) st.dataframe(properties_df, use_container_width=True, hide_index=True, height=240) property_options = properties_df["property_name"].dropna().tolist() if property_options: if st.session_state.inspect_property not in property_options: st.session_state.inspect_property = property_options[0] property_choice = st.selectbox( "Property", property_options, index=property_options.index(st.session_state.inspect_property), key="inspect_property_select", ) st.session_state.inspect_property = property_choice if st.button("Search", key="inspect_search", type="primary"): result = all_data[ (all_data["material_abbreviation"] == selected_abbr) & (all_data["property_name"] == property_choice) & (all_data["value"].notna()) ] if result.empty: st.warning("No data found for this material-property combination") else: st.subheader("Property Data") st.dataframe(result.T, use_container_width=True) st.subheader("Property Graph") image_path = Path("images") / f"{selected_abbr}_{property_choice}.png" if image_path.exists(): image = Image.open(image_path) st.image(image, use_container_width=True, caption="Stress strain curve") else: st.caption("No plot image available for this material-property pair.")