Spaces:
Running
on
Zero
Running
on
Zero
import os | |
import subprocess | |
import sys | |
try: | |
import spaces | |
except: | |
pass | |
os.environ["PYDANTIC_STRICT_TYPE_CHECKING"] = "0" | |
# Check if setup has been run | |
setup_marker = ".setup_complete" | |
if not os.path.exists(setup_marker): | |
print("First run detected, installing dependencies...") | |
try: | |
subprocess.check_call(["bash", "setup.sh"]) | |
# Create marker file to indicate setup is complete | |
with open(setup_marker, "w") as f: | |
f.write("Setup completed") | |
print("Setup completed successfully!") | |
except subprocess.CalledProcessError as e: | |
print(f"Setup failed with error: {e}") | |
sys.exit(1) | |
import torch | |
import gradio as gr | |
from typing import Tuple, List, Dict, Any, Optional | |
from collections import deque | |
from diffusers import StableDiffusionPipeline | |
from triplaneturbo_executable import TriplaneTurboTextTo3DPipeline | |
from triplaneturbo_executable.utils.mesh_exporter import export_obj | |
# Initialize global variables | |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu" | |
ADAPTER_PATH = "pretrained/triplane_turbo_sd_v1.pth" #"/home/user/app/pretrained/triplane_turbo_sd_v1.pth" | |
PIPELINE = None # Will hold our pipeline instance | |
OBJ_FILE_QUEUE = deque(maxlen=100) # Queue to store OBJ file paths | |
def download_model(): | |
"""Download the pretrained model if not exists""" | |
if not os.path.exists(ADAPTER_PATH): | |
print("Downloading pretrained models from huggingface") | |
os.system( | |
f"huggingface-cli download --resume-download ZhiyuanthePony/TriplaneTurbo \ | |
--include \"triplane_turbo_sd_v1.pth\" \ | |
--local-dir ./pretrained \ | |
--local-dir-use-symlinks False" | |
) | |
def initialize_pipeline(): | |
"""Initialize the pipeline once and keep it in memory""" | |
global PIPELINE | |
if PIPELINE is None: | |
print("Initializing pipeline...") | |
PIPELINE = TriplaneTurboTextTo3DPipeline.from_pretrained(ADAPTER_PATH) | |
PIPELINE.to(DEVICE) | |
print("Pipeline initialized!") | |
return PIPELINE | |
def generate_3d_mesh(prompt: str) -> Tuple[Optional[str], Optional[str]]: | |
"""Generate 3D mesh from text prompt""" | |
global PIPELINE, OBJ_FILE_QUEUE | |
# Use the global pipeline instance | |
pipeline = initialize_pipeline() | |
# Use fixed seed value | |
seed = 42 | |
# Generate mesh | |
output = pipeline( | |
prompt=prompt, | |
num_results_per_prompt=1, | |
generator=torch.Generator(device=DEVICE).manual_seed(seed), | |
) | |
# Save mesh | |
output_dir = "outputs" | |
os.makedirs(output_dir, exist_ok=True) | |
mesh_path = None | |
for i, mesh in enumerate(output["mesh"]): | |
vertices = mesh.v_pos | |
# 1. First rotate -90 degrees around X-axis to make the model face up | |
vertices = torch.stack([ | |
vertices[:, 0], # x remains unchanged | |
vertices[:, 2], # y = z | |
-vertices[:, 1] # z = -y | |
], dim=1) | |
# 2. Then rotate 90 degrees around Y-axis to make the model face the observer | |
vertices = torch.stack([ | |
-vertices[:, 2], # x = -z | |
vertices[:, 1], # y remains unchanged | |
vertices[:, 0] # z = x | |
], dim=1) | |
mesh.v_pos = vertices | |
# If mesh has normals, they need to be rotated in the same way | |
if mesh.v_nrm is not None: | |
normals = mesh.v_nrm | |
# 1. Rotate -90 degrees around X-axis | |
normals = torch.stack([ | |
normals[:, 0], | |
normals[:, 2], | |
-normals[:, 1] | |
], dim=1) | |
# 2. Rotate 90 degrees around Y-axis | |
normals = torch.stack([ | |
-normals[:, 2], | |
normals[:, 1], | |
normals[:, 0] | |
], dim=1) | |
mesh._v_nrm = normals | |
name = f"{prompt.replace(' ', '_')}" | |
save_paths = export_obj(mesh, f"{output_dir}/{name}.obj") | |
mesh_path = save_paths[0] | |
# Add new file path to queue | |
OBJ_FILE_QUEUE.append(mesh_path) | |
# If queue is at max length, remove oldest file | |
if len(OBJ_FILE_QUEUE) == OBJ_FILE_QUEUE.maxlen: | |
old_file = OBJ_FILE_QUEUE[0] # Get oldest file (will be automatically removed from queue) | |
if os.path.exists(old_file): | |
try: | |
os.remove(old_file) | |
except OSError as e: | |
print(f"Error deleting file {old_file}: {e}") | |
return mesh_path, mesh_path # Return the path twice - once for 3D preview, once for download | |
with gr.Blocks(css=".output-image, .input-image, .image-preview {height: 512px !important}") as demo: | |
# Download model if needed | |
download_model() | |
# Initialize pipeline at startup | |
initialize_pipeline() | |
gr.Markdown( | |
""" | |
# π Text to 3D Mesh Generation with TriplaneTurbo | |
Demo of the paper "Progressive Rendering Distillation: Adapting Stable Diffusion for Instant Text-to-Mesh Generation beyond 3D Training Data" [CVPR 2025] | |
[GitHub Repository](https://github.com/theEricMa/TriplaneTurbo) | |
## Instructions | |
1. Enter a text prompt describing what 3D object you want to generate | |
2. Click "Generate" and wait for the model to create your 3D mesh | |
3. View the result in the 3D viewer or download the OBJ file | |
""" | |
) | |
with gr.Row(): | |
with gr.Column(scale=1): | |
prompt = gr.Textbox( | |
label="Text Prompt", | |
placeholder="Enter your text description...", | |
value="Armor dress style of outsiderzone fantasy helmet", | |
lines=2 | |
) | |
generate_btn = gr.Button("Generate", variant="primary") | |
examples = gr.Examples( | |
examples=[ | |
["Armor dress style of outsiderzone fantasy helmet"], | |
["Gandalf the grey riding a camel in a rock concert, victorian newspaper article, hyperrealistic"], | |
["A DSLR photo of a bald eagle"], | |
["A goblin riding a lawnmower in a hospital, victorian newspaper article, 4k hd"], | |
["An imperial stormtrooper, highly detailed"], | |
], | |
inputs=[prompt], | |
label="Example Prompts" | |
) | |
with gr.Column(scale=1): | |
output_model = gr.Model3D( | |
label="Generated 3D Mesh", | |
camera_position=(90, 90, 3), | |
clear_color=(0.5, 0.5, 0.5, 1), | |
) | |
output_file = gr.File(label="Download OBJ file") | |
generate_btn.click( | |
fn=generate_3d_mesh, | |
inputs=[prompt], | |
outputs=[output_model, output_file] | |
) | |
gr.Markdown( | |
""" | |
## About | |
This demo uses TriplaneTurbo, which adapts Stable Diffusion for instant text-to-mesh generation. | |
The model can generate high-quality 3D meshes from text descriptions without requiring 3D training data. | |
### Limitations | |
- Generation is deterministic with a fixed seed | |
- Complex prompts may produce unpredictable results | |
- Generated meshes may require clean-up for professional use | |
""" | |
) | |
if __name__ == "__main__": | |
demo.launch() | |