Spaces:
Running
on
Zero
Running
on
Zero
Commit
·
38a30bb
1
Parent(s):
75d9a0f
update
Browse files
app.py
CHANGED
@@ -1,83 +1,72 @@
|
|
1 |
-
import os
|
2 |
-
import subprocess
|
3 |
-
import sys
|
4 |
try:
|
5 |
import spaces
|
6 |
-
except:
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
if not os.path.exists(setup_marker):
|
12 |
-
print("First run detected, installing dependencies...")
|
13 |
-
try:
|
14 |
-
subprocess.check_call(["bash", "setup.sh"])
|
15 |
-
# Create marker file to indicate setup is complete
|
16 |
-
with open(setup_marker, "w") as f:
|
17 |
-
f.write("Setup completed")
|
18 |
-
print("Setup completed successfully!")
|
19 |
-
except subprocess.CalledProcessError as e:
|
20 |
-
print(f"Setup failed with error: {e}")
|
21 |
-
sys.exit(1)
|
22 |
|
|
|
23 |
import torch
|
24 |
-
import
|
25 |
-
from
|
26 |
from diffusers import StableDiffusionPipeline
|
|
|
27 |
|
28 |
-
from triplaneturbo_executable import TriplaneTurboTextTo3DPipeline
|
29 |
from triplaneturbo_executable.utils.mesh_exporter import export_obj
|
|
|
|
|
30 |
|
31 |
-
# Initialize global variables
|
32 |
-
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
33 |
-
ADAPTER_PATH = "pretrained/triplane_turbo_sd_v1.pth" #"/home/user/app/pretrained/triplane_turbo_sd_v1.pth"
|
34 |
-
PIPELINE = None # Will hold our pipeline instance
|
35 |
-
OBJ_FILE_QUEUE = deque(maxlen=100) # Queue to store OBJ file paths
|
36 |
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
)
|
47 |
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
print("Initializing pipeline...")
|
53 |
-
PIPELINE = TriplaneTurboTextTo3DPipeline.from_pretrained(ADAPTER_PATH)
|
54 |
-
PIPELINE.to(DEVICE)
|
55 |
-
print("Pipeline initialized!")
|
56 |
-
return PIPELINE
|
57 |
|
58 |
@spaces.GPU
|
59 |
-
def
|
60 |
-
"""
|
61 |
-
|
62 |
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
|
|
71 |
prompt=prompt,
|
72 |
-
num_results_per_prompt=
|
73 |
-
generator=torch.Generator(device=
|
|
|
74 |
)
|
75 |
-
|
|
|
|
|
76 |
# Save mesh
|
77 |
-
output_dir = "outputs"
|
78 |
os.makedirs(output_dir, exist_ok=True)
|
79 |
-
|
80 |
-
mesh_path = None
|
81 |
for i, mesh in enumerate(output["mesh"]):
|
82 |
vertices = mesh.v_pos
|
83 |
|
@@ -114,96 +103,27 @@ def generate_3d_mesh(prompt):
|
|
114 |
], dim=1)
|
115 |
mesh._v_nrm = normals
|
116 |
|
117 |
-
|
|
|
118 |
save_paths = export_obj(mesh, f"{output_dir}/{name}.obj")
|
119 |
-
|
120 |
-
|
121 |
-
# Add new file path to queue
|
122 |
-
OBJ_FILE_QUEUE.append(mesh_path)
|
123 |
-
|
124 |
-
# If queue is at max length, remove oldest file
|
125 |
-
if len(OBJ_FILE_QUEUE) == OBJ_FILE_QUEUE.maxlen:
|
126 |
-
old_file = OBJ_FILE_QUEUE[0] # Get oldest file (will be automatically removed from queue)
|
127 |
-
if os.path.exists(old_file):
|
128 |
-
try:
|
129 |
-
os.remove(old_file)
|
130 |
-
except OSError as e:
|
131 |
-
print(f"Error deleting file {old_file}: {e}")
|
132 |
|
133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
|
135 |
-
with gr.Blocks(css=".output-image, .input-image, .image-preview {height: 512px !important}") as demo:
|
136 |
-
# Download model if needed
|
137 |
-
download_model()
|
138 |
-
|
139 |
-
# Initialize pipeline at startup
|
140 |
-
initialize_pipeline()
|
141 |
-
|
142 |
-
gr.Markdown(
|
143 |
-
"""
|
144 |
-
# 🌟 Text to 3D Mesh Generation with TriplaneTurbo
|
145 |
-
|
146 |
-
Demo of the paper "Progressive Rendering Distillation: Adapting Stable Diffusion for Instant Text-to-Mesh Generation beyond 3D Training Data" [CVPR 2025]
|
147 |
-
|
148 |
-
[GitHub Repository](https://github.com/theEricMa/TriplaneTurbo)
|
149 |
-
|
150 |
-
## Instructions
|
151 |
-
1. Enter a text prompt describing what 3D object you want to generate
|
152 |
-
2. Click "Generate" and wait for the model to create your 3D mesh
|
153 |
-
3. View the result in the 3D viewer or download the OBJ file
|
154 |
-
"""
|
155 |
-
)
|
156 |
-
|
157 |
-
with gr.Row():
|
158 |
-
with gr.Column(scale=1):
|
159 |
-
prompt = gr.Textbox(
|
160 |
-
label="Text Prompt",
|
161 |
-
placeholder="Enter your text description...",
|
162 |
-
value="Armor dress style of outsiderzone fantasy helmet",
|
163 |
-
lines=2
|
164 |
-
)
|
165 |
-
|
166 |
-
generate_btn = gr.Button("Generate", variant="primary")
|
167 |
-
|
168 |
-
examples = gr.Examples(
|
169 |
-
examples=[
|
170 |
-
["Armor dress style of outsiderzone fantasy helmet"],
|
171 |
-
["Gandalf the grey riding a camel in a rock concert, victorian newspaper article, hyperrealistic"],
|
172 |
-
["A DSLR photo of a bald eagle"],
|
173 |
-
["A goblin riding a lawnmower in a hospital, victorian newspaper article, 4k hd"],
|
174 |
-
["An imperial stormtrooper, highly detailed"],
|
175 |
-
],
|
176 |
-
inputs=[prompt],
|
177 |
-
label="Example Prompts"
|
178 |
-
)
|
179 |
-
|
180 |
-
with gr.Column(scale=1):
|
181 |
-
output_model = gr.Model3D(
|
182 |
-
label="Generated 3D Mesh",
|
183 |
-
camera_position=(90, 90, 3),
|
184 |
-
clear_color=(0.5, 0.5, 0.5, 1),
|
185 |
-
)
|
186 |
-
output_file = gr.File(label="Download OBJ file")
|
187 |
-
|
188 |
-
generate_btn.click(
|
189 |
-
fn=generate_3d_mesh,
|
190 |
-
inputs=[prompt],
|
191 |
-
outputs=[output_model, output_file]
|
192 |
-
)
|
193 |
-
|
194 |
-
gr.Markdown(
|
195 |
-
"""
|
196 |
-
## About
|
197 |
-
|
198 |
-
This demo uses TriplaneTurbo, which adapts Stable Diffusion for instant text-to-mesh generation.
|
199 |
-
The model can generate high-quality 3D meshes from text descriptions without requiring 3D training data.
|
200 |
-
|
201 |
-
### Limitations
|
202 |
-
- Generation is deterministic with a fixed seed
|
203 |
-
- Complex prompts may produce unpredictable results
|
204 |
-
- Generated meshes may require clean-up for professional use
|
205 |
-
"""
|
206 |
-
)
|
207 |
|
208 |
-
|
209 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
try:
|
2 |
import spaces
|
3 |
+
except ImportError:
|
4 |
+
# Define a dummy decorator if spaces is not available
|
5 |
+
def GPU(func):
|
6 |
+
return func
|
7 |
+
spaces = type('spaces', (), {'GPU': GPU})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
+
import os
|
10 |
import torch
|
11 |
+
import argparse
|
12 |
+
from typing import *
|
13 |
from diffusers import StableDiffusionPipeline
|
14 |
+
from collections import deque
|
15 |
|
|
|
16 |
from triplaneturbo_executable.utils.mesh_exporter import export_obj
|
17 |
+
from triplaneturbo_executable import TriplaneTurboTextTo3DPipeline, TriplaneTurboTextTo3DPipelineConfig
|
18 |
+
|
19 |
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
+
# Initialize configuration and parameters
|
22 |
+
prompt = "a beautiful girl"
|
23 |
+
output_dir = "output"
|
24 |
+
adapter_name_or_path = "pretrained/triplane_turbo_sd_v1.pth"
|
25 |
+
num_results_per_prompt = 1
|
26 |
+
seed = 42
|
27 |
+
device = "cuda"
|
28 |
+
max_obj_files = 100
|
29 |
+
|
30 |
+
# download pretrained models if not exist
|
31 |
+
if not os.path.exists(adapter_name_or_path):
|
32 |
+
print(f"Downloading pretrained models from huggingface")
|
33 |
+
os.system(
|
34 |
+
f"huggingface-cli download --resume-download ZhiyuanthePony/TriplaneTurbo \
|
35 |
+
--include \"triplane_turbo_sd_v1.pth\" \
|
36 |
+
--local-dir ./pretrained \
|
37 |
+
--local-dir-use-symlinks False"
|
38 |
)
|
39 |
|
40 |
+
|
41 |
+
# Initialize the TriplaneTurbo pipeline
|
42 |
+
triplane_turbo_pipeline = TriplaneTurboTextTo3DPipeline.from_pretrained(adapter_name_or_path)
|
43 |
+
triplane_turbo_pipeline.to(device)
|
|
|
|
|
|
|
|
|
|
|
44 |
|
45 |
@spaces.GPU
|
46 |
+
def generate_3d_model(prompt, num_results_per_prompt=1, seed=42, device="cuda"):
|
47 |
+
"""
|
48 |
+
Generate 3D models using TriplaneTurbo pipeline.
|
49 |
|
50 |
+
Args:
|
51 |
+
prompt (str): Text prompt for the 3D model
|
52 |
+
num_results_per_prompt (int): Number of results to generate
|
53 |
+
seed (int): Random seed for generation
|
54 |
+
device (str): Device to use for computation
|
55 |
+
|
56 |
+
Returns:
|
57 |
+
dict: Output from the pipeline
|
58 |
+
"""
|
59 |
+
output = triplane_turbo_pipeline(
|
60 |
prompt=prompt,
|
61 |
+
num_results_per_prompt=num_results_per_prompt,
|
62 |
+
generator=torch.Generator(device=device).manual_seed(seed),
|
63 |
+
device=device,
|
64 |
)
|
65 |
+
# Initialize a deque with maximum length of 100 to store obj file paths
|
66 |
+
obj_file_queue = deque(maxlen=max_obj_files)
|
67 |
+
|
68 |
# Save mesh
|
|
|
69 |
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
|
70 |
for i, mesh in enumerate(output["mesh"]):
|
71 |
vertices = mesh.v_pos
|
72 |
|
|
|
103 |
], dim=1)
|
104 |
mesh._v_nrm = normals
|
105 |
|
106 |
+
# Save obj file and add its path to the queue
|
107 |
+
name = f"{prompt.replace(' ', '_')}_{seed}_{i}"
|
108 |
save_paths = export_obj(mesh, f"{output_dir}/{name}.obj")
|
109 |
+
obj_file_queue.append(save_paths[0])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
|
111 |
+
# If an old file needs to be removed (queue is at max length)
|
112 |
+
# and the file exists, delete it
|
113 |
+
if len(obj_file_queue) == max_obj_files and os.path.exists(obj_file_queue[0]):
|
114 |
+
old_file = obj_file_queue[0]
|
115 |
+
try:
|
116 |
+
os.remove(old_file)
|
117 |
+
except OSError as e:
|
118 |
+
print(f"Error deleting file {old_file}: {e}")
|
119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
|
121 |
+
|
122 |
+
# Run the pipeline
|
123 |
+
output = generate_3d_model(
|
124 |
+
prompt=prompt,
|
125 |
+
num_results_per_prompt=num_results_per_prompt,
|
126 |
+
seed=seed,
|
127 |
+
device=device
|
128 |
+
)
|
129 |
+
|