gm-mesh / app.py
vivekchakraverty's picture
Upload 2 files
8d3e11e verified
Raw
History Blame Contribute Delete
5.81 kB
"""
Hunyuan3D-2 — Shape-only HuggingFace Space
Uses Hunyuan3D-2mini-Turbo (0.6 B, step-distilled) for fast shape generation
within standard ZeroGPU quota. No texture pipeline — mesh only.
Background removal is skipped — designed for clean character art.
"""
import os
import tempfile
import gradio as gr
import spaces # ZeroGPU decorator
import torch
from PIL import Image
# ---------------------------------------------------------------------------
# Lazy global pipeline — loaded once on first GPU call
# ---------------------------------------------------------------------------
_pipeline = None
def _get_pipeline():
"""Load the shape pipeline once and cache it globally."""
global _pipeline
if _pipeline is None:
from hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline
_pipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(
"tencent/Hunyuan3D-2mini",
subfolder="hunyuan3d-dit-v2-mini-turbo",
use_safetensors=True,
torch_dtype=torch.float16,
)
return _pipeline
# ---------------------------------------------------------------------------
# Simple image pre-processing — resize only, no background removal
# ---------------------------------------------------------------------------
def preprocess_image(pil_image: Image.Image) -> Image.Image:
"""Resize to 512x512 RGB — the model's native conditioning resolution."""
return pil_image.convert("RGB").resize((512, 512), Image.LANCZOS)
# ---------------------------------------------------------------------------
# Core generation — wrapped in @spaces.GPU for ZeroGPU
# ---------------------------------------------------------------------------
@spaces.GPU(duration=60)
def generate_shape(image: Image.Image, seed: int, steps: int, octree_res: int):
"""
Run Hunyuan3D-DiT shape generation and return a GLB file path.
NOTE: pipeline.to("cuda") must NOT be reassigned — some custom pipelines
return None from .to(), which would make the pipeline uncallable.
"""
pipeline = _get_pipeline()
pipeline.to("cuda") # move in-place; do not reassign the return value
generator = torch.Generator(device="cuda").manual_seed(seed)
meshes = pipeline(
image=image,
num_inference_steps=steps,
octree_resolution=octree_res,
num_chunks=8000,
generator=generator,
output_type="trimesh",
)
mesh = meshes[0]
tmp_dir = tempfile.mkdtemp()
out_path = os.path.join(tmp_dir, "shape.glb")
mesh.export(out_path)
return out_path
# ---------------------------------------------------------------------------
# Gradio UI
# ---------------------------------------------------------------------------
def run(image, seed, steps, octree_res, progress=gr.Progress(track_tqdm=True)):
if image is None:
raise gr.Error("Please upload an image first.")
progress(0.1, desc="Preprocessing image ...")
pil = image if isinstance(image, Image.Image) else Image.fromarray(image)
processed = preprocess_image(pil)
progress(0.3, desc="Running shape diffusion ...")
glb_path = generate_shape(processed, int(seed), int(steps), int(octree_res))
progress(1.0, desc="Done!")
return glb_path, processed, glb_path
with gr.Blocks(title="Hunyuan3D-2 Shape Generator", theme=gr.themes.Soft()) as demo:
gr.Markdown(
"""
# Hunyuan3D-2 Shape Generator
Upload character art to generate an **untextured 3-D mesh** using
[Hunyuan3D-2mini-Turbo](https://huggingface.co/tencent/Hunyuan3D-2mini).
Shape only - no texture - stays well within the ZeroGPU free quota.
"""
)
with gr.Row():
with gr.Column(scale=1):
input_image = gr.Image(
label="Input Image",
type="pil",
sources=["upload", "clipboard"],
height=340,
)
with gr.Accordion("Advanced settings", open=False):
seed = gr.Slider(
label="Seed",
minimum=0, maximum=2**31 - 1,
value=42, step=1,
)
steps = gr.Slider(
label="Diffusion steps",
minimum=5, maximum=50,
value=5, step=1,
info="5-15 works well with the turbo model.",
)
octree_res = gr.Slider(
label="Octree resolution",
minimum=128, maximum=512,
value=192, step=64,
info="Higher = finer mesh detail but more VRAM & time.",
)
generate_btn = gr.Button("Generate Shape", variant="primary")
with gr.Column(scale=1):
preview_img = gr.Image(
label="Image sent to model (512x512)",
type="pil",
interactive=False,
height=200,
)
output_3d = gr.Model3D(
label="3-D Shape (GLB)",
height=400,
clear_color=[0.9, 0.9, 0.9, 1.0],
)
download_file = gr.File(label="Download GLB")
gr.Markdown(
"""
---
**Tips**
- Works best with clean character art on a plain or transparent background.
- Lower octree resolution (128-192) is faster and still looks great for most art.
- Model: Hunyuan3D-DiT-v2-mini-Turbo - 0.6B parameters, step-distilled.
"""
)
generate_btn.click(
fn=run,
inputs=[input_image, seed, steps, octree_res],
outputs=[output_3d, preview_img, download_file],
)
if __name__ == "__main__":
demo.queue(max_size=5).launch()