| """ |
| Evoneural MVP - Local 3D Mesh + Skybox Generation |
| Run: streamlit run app.py |
| Open: http://localhost:8501 |
| """ |
|
|
| import os |
| import sys |
| from pathlib import Path |
|
|
| |
| ROOT = Path(__file__).resolve().parent |
| if str(ROOT) not in sys.path: |
| sys.path.insert(0, str(ROOT)) |
|
|
| import streamlit as st |
|
|
| OUTPUTS = ROOT / "outputs" |
| OUTPUTS.mkdir(exist_ok=True) |
|
|
|
|
| def main() -> None: |
| st.set_page_config( |
| page_title="Evoneural MVP - Mesh & Skybox", |
| page_icon="🎮", |
| layout="wide", |
| ) |
| st.title("Evoneural MVP – Local Mesh & Skybox") |
| st.caption("Text → 3D mesh (TripoSR) and Text → 360° skybox (Stable Diffusion). Runs on localhost.") |
|
|
| |
| with st.sidebar: |
| st.subheader("Stable Diffusion model") |
| from scripts.skybox_generator import _default_local_weights_dir |
| local_model = _default_local_weights_dir() |
| if local_model: |
| st.success(f"Local model: found") |
| st.caption(os.path.basename(local_model)) |
| else: |
| st.warning("No local model. Download below or need internet on first generate.") |
| hf_token = st.text_input( |
| "Hugging Face token (optional, if behind firewall)", |
| type="password", |
| key="hf_token", |
| placeholder="hf_...", |
| help="Get a token at huggingface.co/settings/tokens", |
| ) |
| if hf_token: |
| os.environ["HF_TOKEN"] = hf_token |
| if st.button("Download model (~4GB to ./weights/sd-v1-5)", key="btn_download"): |
| with st.spinner("Downloading model... (may take several minutes)"): |
| try: |
| from scripts.download_sd_model import download_sd_model |
| path = download_sd_model(token=hf_token or os.environ.get("HF_TOKEN")) |
| st.success(f"Model saved. Try generating a skybox.") |
| st.rerun() |
| except Exception as e: |
| st.error(str(e)) |
| st.caption("Set a Hugging Face token above if your network blocks Hugging Face.") |
|
|
| tab_mesh, tab_skybox = st.tabs(["🟦 Text → 3D Mesh", "🌅 Text → Skybox"]) |
|
|
| with tab_mesh: |
| st.subheader("Generate 3D mesh from text") |
| st.markdown( |
| "Uses **Stable Diffusion** for text→image, then **TripoSR** for image→mesh. " |
| "TripoSR repo must be cloned into `./TripoSR` (see README)." |
| ) |
| prompt_mesh = st.text_input( |
| "Prompt (e.g. for mesh)", |
| value="A highly detailed, sci-fi mechanical drone with glowing blue accents.", |
| key="mesh_prompt", |
| ) |
| col1, col2 = st.columns(2) |
| with col1: |
| mesh_format = st.selectbox("Mesh format", ["glb", "obj"], key="mesh_fmt") |
| seed_mesh = st.number_input("Seed (optional)", value=42, min_value=0, key="mesh_seed") |
| with col2: |
| use_image = st.checkbox("Use uploaded image instead of text", value=False, key="use_img") |
| uploaded = st.file_uploader("Upload image for mesh", type=["png", "jpg"], key="mesh_upload") if use_image else None |
|
|
| if st.button("Generate mesh", key="btn_mesh"): |
| if not prompt_mesh.strip() and not use_image: |
| st.warning("Enter a prompt or upload an image.") |
| else: |
| with st.spinner("Running pipeline..."): |
| try: |
| from scripts.mesh_generator import ( |
| generate_mesh_from_image, |
| generate_mesh_from_text, |
| find_triposr_root, |
| ) |
| triposr_root = find_triposr_root(str(ROOT)) |
| if not triposr_root: |
| st.error( |
| "TripoSR not found. Clone it: " |
| "`git clone https://github.com/VAST-AI-Research/TripoSR.git TripoSR`" |
| ) |
| elif use_image and uploaded: |
| path = os.path.join(OUTPUTS, "uploaded_mesh_input.png") |
| with open(path, "wb") as f: |
| f.write(uploaded.getvalue()) |
| mesh_path, elapsed, msg = generate_mesh_from_image( |
| path, |
| output_dir=str(OUTPUTS / "mesh_run"), |
| mesh_format=mesh_format, |
| ) |
| if mesh_path: |
| st.success(f"Done in {elapsed:.1f}s. {msg}") |
| with open(mesh_path, "rb") as f: |
| st.download_button("Download mesh", f, file_name=os.path.basename(mesh_path), key="dl_mesh_upload") |
| else: |
| st.error(msg) |
| else: |
| mesh_path, elapsed, msg = generate_mesh_from_text( |
| prompt_mesh, |
| output_dir=str(OUTPUTS), |
| mesh_format=mesh_format, |
| seed=seed_mesh, |
| ) |
| if mesh_path: |
| st.success(f"Done in {elapsed:.1f}s. {msg}") |
| with open(mesh_path, "rb") as f: |
| st.download_button("Download mesh", f, file_name=os.path.basename(mesh_path), key="dl_mesh") |
| else: |
| st.error(msg) |
| except Exception as e: |
| st.exception(e) |
|
|
| with tab_skybox: |
| st.subheader("Generate 2:1 equirectangular skybox") |
| st.markdown( |
| "Uses **Stable Diffusion 2.1** at 2:1 aspect (e.g. 1024×512). " |
| "Optional seamless check compares left/right edges." |
| ) |
| prompt_sky = st.text_input( |
| "Prompt (e.g. for skybox)", |
| value="Cyberpunk city skyline at dusk, neon reflections, cinematic lighting.", |
| key="sky_prompt", |
| ) |
| col1, col2 = st.columns(2) |
| with col1: |
| width = st.selectbox("Width", [1024, 2048], key="sky_w") |
| height = width // 2 |
| seed_sky = st.number_input("Seed (optional)", value=42, min_value=0, key="sky_seed") |
| with col2: |
| check_seamless = st.checkbox("Run seamless edge check", value=True, key="seamless") |
|
|
| if st.button("Generate skybox", key="btn_sky"): |
| if not prompt_sky.strip(): |
| st.warning("Enter a prompt.") |
| else: |
| with st.spinner("Generating skybox..."): |
| try: |
| from scripts.skybox_generator import generate_skybox |
| from scripts.check_seamless import check_seamless as run_seamless |
|
|
| out_path, elapsed, vram_mb = generate_skybox( |
| prompt_sky, |
| output_dir=str(OUTPUTS), |
| width=width, |
| height=height, |
| seed=seed_sky, |
| ) |
| st.success(f"Done in {elapsed:.1f}s. Peak VRAM: {vram_mb:.0f} MB") |
| st.image(out_path, use_container_width=True) |
| with open(out_path, "rb") as f: |
| st.download_button("Download skybox", f, file_name=os.path.basename(out_path), key="dl_sky") |
|
|
| if check_seamless: |
| result = run_seamless(out_path) |
| st.info(result["message"]) |
| except Exception as e: |
| st.exception(e) |
|
|
| st.divider() |
| st.caption("Evoneural AI – Local ML Deployment MVP. Models run locally (no API).") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|