trellis-2-mrp-mlx/app_mlx.py

175 lines
5.9 KiB
Python

"""
Gradio app for Trellis2 with MLX backend (macOS).
Simplified from upstream app.py — no CUDA render preview, direct GLB export.
"""
import gradio as gr
import os
import time
from datetime import datetime
import shutil
import numpy as np
from PIL import Image
import torch
import o_voxel
MAX_SEED = np.iinfo(np.int32).max
TMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')
def get_seed(randomize_seed: bool, seed: int) -> int:
return np.random.randint(0, MAX_SEED) if randomize_seed else seed
def preprocess_image(image: Image.Image) -> Image.Image:
return pipeline.preprocess_image(image)
def image_to_3d(
image: Image.Image,
seed: int,
resolution: str,
ss_guidance_strength: float,
ss_sampling_steps: int,
shape_slat_guidance_strength: float,
shape_slat_sampling_steps: int,
tex_slat_guidance_strength: float,
tex_slat_sampling_steps: int,
decimation_target: int,
texture_size: int,
req: gr.Request,
progress=gr.Progress(track_tqdm=True),
):
user_dir = os.path.join(TMP_DIR, str(req.session_hash))
os.makedirs(user_dir, exist_ok=True)
pipeline_type = {"512": "512", "1024": "1024_cascade", "1536": "1536_cascade"}[resolution]
t0 = time.time()
meshes = pipeline.run(
image,
seed=seed,
sparse_structure_sampler_params={
"steps": ss_sampling_steps,
"guidance_strength": ss_guidance_strength,
},
shape_slat_sampler_params={
"steps": shape_slat_sampling_steps,
"guidance_strength": shape_slat_guidance_strength,
},
tex_slat_sampler_params={
"steps": tex_slat_sampling_steps,
"guidance_strength": tex_slat_guidance_strength,
},
pipeline_type=pipeline_type,
)
dt_gen = time.time() - t0
mesh = meshes[0]
t0 = time.time()
glb = o_voxel.postprocess.to_glb(
vertices=mesh.vertices,
faces=mesh.faces,
attr_volume=mesh.attrs,
coords=mesh.coords,
attr_layout=mesh.layout,
voxel_size=mesh.voxel_size,
aabb=[[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]],
decimation_target=decimation_target,
texture_size=texture_size,
verbose=True,
)
dt_post = time.time() - t0
now = datetime.now()
timestamp = now.strftime("%Y-%m-%dT%H%M%S") + f".{now.microsecond // 1000:03d}"
glb_path = os.path.join(user_dir, f'sample_{timestamp}.glb')
glb.export(glb_path)
info = (f"Generation: {dt_gen:.0f}s | Post-processing: {dt_post:.0f}s | "
f"Verts: {mesh.vertices.shape[0]:,} | Faces: {mesh.faces.shape[0]:,}")
return glb_path, glb_path, info
def start_session(req: gr.Request):
user_dir = os.path.join(TMP_DIR, str(req.session_hash))
os.makedirs(user_dir, exist_ok=True)
def end_session(req: gr.Request):
user_dir = os.path.join(TMP_DIR, str(req.session_hash))
if os.path.exists(user_dir):
shutil.rmtree(user_dir)
with gr.Blocks(title="Trellis2 MLX") as demo:
gr.Markdown("""
## Trellis2 (MLX Backend)
Upload an image and click Generate to create a 3D asset.
""")
with gr.Row():
with gr.Column(scale=1, min_width=360):
image_prompt = gr.Image(label="Image Prompt", format="png", image_mode="RGBA", type="pil", height=400)
resolution = gr.Radio(["512", "1024"], label="Resolution", value="1024")
seed = gr.Slider(0, MAX_SEED, label="Seed", value=42, step=1)
randomize_seed = gr.Checkbox(label="Randomize Seed", value=False)
decimation_target = gr.Slider(100000, 1000000, label="Decimation Target", value=1000000, step=10000)
texture_size = gr.Slider(1024, 4096, label="Texture Size", value=2048, step=1024)
generate_btn = gr.Button("Generate", variant="primary")
with gr.Accordion(label="Advanced Settings", open=False):
gr.Markdown("### Stage 1: Sparse Structure")
with gr.Row():
ss_guidance_strength = gr.Slider(1.0, 10.0, label="Guidance", value=7.5, step=0.1)
ss_sampling_steps = gr.Slider(1, 50, label="Steps", value=12, step=1)
gr.Markdown("### Stage 2: Shape")
with gr.Row():
shape_slat_guidance_strength = gr.Slider(1.0, 10.0, label="Guidance", value=7.5, step=0.1)
shape_slat_sampling_steps = gr.Slider(1, 50, label="Steps", value=12, step=1)
gr.Markdown("### Stage 3: Texture")
with gr.Row():
tex_slat_guidance_strength = gr.Slider(0.1, 10.0, label="Guidance", value=1.0, step=0.1)
tex_slat_sampling_steps = gr.Slider(1, 50, label="Steps", value=12, step=1)
with gr.Column(scale=2):
glb_output = gr.Model3D(label="Generated 3D Model", height=700, clear_color=(0.25, 0.25, 0.25, 1.0))
download_btn = gr.DownloadButton(label="Download GLB")
info_text = gr.Textbox(label="Info", interactive=False)
# Handlers
demo.load(start_session)
demo.unload(end_session)
image_prompt.upload(
preprocess_image,
inputs=[image_prompt],
outputs=[image_prompt],
)
generate_btn.click(
get_seed,
inputs=[randomize_seed, seed],
outputs=[seed],
).then(
image_to_3d,
inputs=[
image_prompt, seed, resolution,
ss_guidance_strength, ss_sampling_steps,
shape_slat_guidance_strength, shape_slat_sampling_steps,
tex_slat_guidance_strength, tex_slat_sampling_steps,
decimation_target, texture_size,
],
outputs=[glb_output, download_btn, info_text],
)
if __name__ == "__main__":
os.makedirs(TMP_DIR, exist_ok=True)
from mlx_backend.pipeline import create_mlx_pipeline
pipeline = create_mlx_pipeline(weights_path="weights/TRELLIS.2-4B")
demo.launch()