import gradio as gr from gradio_client import Client, handle_file import spaces import os os.environ["OPENCV_IO_ENABLE_OPENEXR"] = '1' os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True" os.environ["ATTN_BACKEND"] = "flash_attn_3" os.environ["FLEX_GEMM_AUTOTUNE_CACHE_PATH"] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'autotune_cache.json') os.environ["FLEX_GEMM_AUTOTUNER_VERBOSE"] = '1' from datetime import datetime import shutil import cv2 from typing import * import torch import numpy as np from PIL import Image import base64 import io import tempfile from trellis2.modules.sparse import SparseTensor from trellis2.pipelines import Trellis2ImageTo3DPipeline from trellis2.renderers import EnvMap from trellis2.utils import render_utils import o_voxel MAX_SEED = np.iinfo(np.int32).max TMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp') MODES = [ {"name": "Normal", "icon": "assets/app/normal.png", "render_key": "normal"}, {"name": "Clay", "icon": "assets/app/clay.png", "render_key": "clay"}, {"name": "Color", "icon": "assets/app/basecolor.png", "render_key": "base_color"}, {"name": "Forest", "icon": "assets/app/hdri_forest.png", "render_key": "shaded_forest"}, {"name": "Sunset", "icon": "assets/app/hdri_sunset.png", "render_key": "shaded_sunset"}, {"name": "Courtyard", "icon": "assets/app/hdri_courtyard.png", "render_key": "shaded_courtyard"}, ] STEPS = 8 DEFAULT_MODE = 3 DEFAULT_STEP = 3 css = """ /* ═══════════════════════════════════════════════════════════════ TRELLIS.2 — Modern Dark Theme ═══════════════════════════════════════════════════════════════ */ :root { --accent: #6366f1; --accent-hover: #818cf8; --accent-glow: rgba(99, 102, 241, 0.3); --surface-0: #0a0a0b; --surface-1: #111113; --surface-2: #1a1a1d; --surface-3: #242428; --border: rgba(255, 255, 255, 0.06); --text-primary: #fafafa; --text-secondary: rgba(255, 255, 255, 0.5); --radius: 16px; --radius-sm: 10px; } /* Global Overrides */ .gradio-container { background: var(--surface-0) !important; width: 100% !important; min-width: 800px !important; max-width: 1800px !important; margin: 0 auto !important; padding: 0 40px !important; box-sizing: border-box !important; } .gradio-container > .main { gap: 0 !important; width: 100% !important; max-width: none !important; } .contain { display: flex !important; flex-direction: column !important; max-width: none !important; } .dark { --block-background-fill: var(--surface-1) !important; --block-border-color: var(--border) !important; --body-background-fill: var(--surface-0) !important; --color-accent: var(--accent) !important; } /* Header */ .app-header { text-align: center; padding: 48px 20px 36px; border-bottom: 1px solid var(--border); margin-bottom: 32px; width: 100%; } .app-header h1 { font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif; font-size: 2.5rem; font-weight: 600; letter-spacing: -0.03em; background: linear-gradient(135deg, #fff 0%, rgba(255,255,255,0.7) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin: 0 0 8px 0; } .app-header p { color: var(--text-secondary); font-size: 1rem; margin: 0; font-weight: 400; } /* Panels */ .panel { background: var(--surface-1) !important; border: 1px solid var(--border) !important; border-radius: var(--radius) !important; overflow: hidden; } .panel-title { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-secondary); padding: 16px 20px 8px; font-weight: 600; } /* Upload Area */ .upload-zone { min-height: 280px !important; border: 2px dashed var(--border) !important; border-radius: var(--radius) !important; background: var(--surface-2) !important; transition: all 0.3s ease; } .upload-zone:hover { border-color: var(--accent) !important; background: rgba(99, 102, 241, 0.05) !important; } /* Buttons */ .primary-btn { background: var(--accent) !important; border: none !important; border-radius: var(--radius-sm) !important; color: white !important; font-weight: 600 !important; padding: 14px 28px !important; font-size: 0.95rem !important; transition: all 0.2s ease !important; box-shadow: 0 4px 20px var(--accent-glow) !important; } .primary-btn:hover { background: var(--accent-hover) !important; transform: translateY(-1px); box-shadow: 0 6px 30px var(--accent-glow) !important; } .secondary-btn { background: var(--surface-3) !important; border: 1px solid var(--border) !important; border-radius: var(--radius-sm) !important; color: var(--text-primary) !important; font-weight: 500 !important; transition: all 0.2s ease !important; } .secondary-btn:hover { background: var(--surface-2) !important; border-color: var(--accent) !important; } /* Sliders & Inputs */ input[type="range"] { accent-color: var(--accent) !important; } .wrap input, .wrap textarea { background: var(--surface-2) !important; border: 1px solid var(--border) !important; border-radius: var(--radius-sm) !important; color: var(--text-primary) !important; } /* Radio Buttons */ .gr-radio-row { gap: 8px !important; } .gr-radio-row label { background: var(--surface-2) !important; border: 1px solid var(--border) !important; border-radius: var(--radius-sm) !important; padding: 10px 18px !important; transition: all 0.2s ease !important; } .gr-radio-row label:hover { border-color: var(--accent) !important; } .gr-radio-row label.selected { background: var(--accent) !important; border-color: var(--accent) !important; } /* Accordion */ .gr-accordion { border: 1px solid var(--border) !important; border-radius: var(--radius-sm) !important; background: var(--surface-2) !important; } /* Walkthrough/Stepper */ .stepper-wrapper { padding: 0; } .stepper-container { padding: 0; align-items: center; } .step-button { flex-direction: row; } .step-connector { transform: none; } .step-number { width: 16px; height: 16px; } .step-label { position: relative; bottom: 0; } /* Loading States */ .wrap.center.full { inset: 0; height: 100%; } .wrap.center.full.translucent { background: var(--surface-1); } /* ═══════════════════════════════════════════════════════════════ 3D PREVIEWER COMPONENT ═══════════════════════════════════════════════════════════════ */ .previewer-container { position: relative; width: 100%; height: 720px; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 24px; background: radial-gradient(ellipse at center, var(--surface-2) 0%, var(--surface-1) 100%); border-radius: var(--radius); } /* Viewport */ .previewer-container .display-row { flex: 1; width: 100%; display: flex; justify-content: center; align-items: center; min-height: 0; } .previewer-container .previewer-main-image { max-width: 100%; max-height: 100%; object-fit: contain; display: none; border-radius: var(--radius-sm); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4); } .previewer-container .previewer-main-image.visible { display: block; animation: fadeIn 0.3s ease; } @keyframes fadeIn { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } } /* Mode Selector */ .previewer-container .mode-row { display: flex; gap: 10px; margin-top: 20px; padding: 8px; background: var(--surface-0); border-radius: 50px; border: 1px solid var(--border); } .previewer-container .mode-btn { width: 32px; height: 32px; border-radius: 50%; cursor: pointer; opacity: 0.4; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); border: 2px solid transparent; object-fit: cover; } .previewer-container .mode-btn:hover { opacity: 0.8; transform: scale(1.1); } .previewer-container .mode-btn.active { opacity: 1; border-color: var(--accent); transform: scale(1.15); box-shadow: 0 0 20px var(--accent-glow); } /* Rotation Slider */ .previewer-container .slider-row { width: 100%; max-width: 320px; margin-top: 16px; } .previewer-container input[type=range] { -webkit-appearance: none; width: 100%; background: transparent; cursor: pointer; } .previewer-container input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 6px; background: var(--surface-0); border-radius: 3px; border: 1px solid var(--border); } .previewer-container input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; height: 18px; width: 18px; border-radius: 50%; background: var(--accent); margin-top: -7px; box-shadow: 0 2px 10px var(--accent-glow); transition: transform 0.15s ease; } .previewer-container input[type=range]::-webkit-slider-thumb:hover { transform: scale(1.2); } /* Empty State */ .empty-state { display: flex; flex-direction: column; align-items: center; gap: 16px; color: var(--text-secondary); } .empty-state svg { opacity: 0.3; } .empty-state p { font-size: 0.9rem; margin: 0; } /* Block Label Override */ .gradio-container .padded:has(.previewer-container) { padding: 0 !important; } .gradio-container:has(.previewer-container) [data-testid="block-label"] { position: absolute; top: 0; left: 0; } /* GLB Viewer */ .model3d-container { background: var(--surface-2) !important; border-radius: var(--radius) !important; } /* Footer Note */ .footer-note { text-align: center; color: var(--text-secondary); font-size: 0.8rem; padding: 20px; border-top: 1px solid var(--border); margin-top: 32px; width: 100%; } /* Main Layout - Force side by side */ #main-row { width: 100% !important; max-width: none !important; margin: 0 !important; display: flex !important; flex-direction: row !important; flex-wrap: nowrap !important; gap: 32px !important; align-items: flex-start !important; } #main-row.row { flex-wrap: nowrap !important; max-width: none !important; } #input-col { flex: 0 0 400px !important; width: 400px !important; min-width: 350px !important; max-width: 450px !important; } #preview-col { flex: 1 1 auto !important; min-width: 500px !important; } @media (max-width: 900px) { #main-row { flex-direction: column !important; } #input-col, #preview-col { flex: 1 1 auto !important; width: 100% !important; max-width: 100% !important; min-width: 0 !important; } } """ head = """ """ empty_html = """
Upload an image to generate 3D
Transform any image into a high-quality 3D asset