trellis-3d-api / app.py
jainarham's picture
Update app.py
2a8a4fd verified
import gradio as gr
import os
import tempfile
import uuid
import time
import shutil
import logging
from pathlib import Path
from PIL import Image
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("TRELLIS-3D")
TRELLIS_SPACE = "microsoft/TRELLIS.2"
TEMP_DIR = os.path.join(tempfile.gettempdir(), "trellis_3d")
os.makedirs(TEMP_DIR, exist_ok=True)
FALLBACK_3D_SPACES = [
"TencentARC/InstantMesh",
"sudo-ai/zero123plus",
]
def text_to_image(prompt, seed=0):
from gradio_client import Client
logger.info(f"Generating image from text: {prompt}")
spaces = [
{
"space": "stabilityai/stable-diffusion-3.5-large-turbo",
"args": [prompt, "", seed, True, 1024, 1024, 4, 0],
"endpoint": "/infer"
},
{
"space": "black-forest-labs/FLUX.1-schnell",
"args": [prompt, seed, True, 1024, 1024, 4],
"endpoint": "/infer"
},
]
for space_config in spaces:
try:
logger.info(f"Trying image gen: {space_config['space']}")
img_client = Client(space_config["space"])
result = img_client.predict(
*space_config["args"],
api_name=space_config["endpoint"]
)
img_path = None
if isinstance(result, str) and os.path.exists(result):
img_path = result
elif isinstance(result, tuple) and len(result) > 0:
first = result[0]
if isinstance(first, str) and os.path.exists(first):
img_path = first
elif isinstance(first, dict) and "path" in first:
img_path = first["path"]
elif isinstance(result, dict) and "path" in result:
img_path = result["path"]
elif hasattr(result, "path"):
img_path = result.path
if img_path and os.path.exists(img_path):
logger.info(f"Image generated: {img_path}")
return img_path
except Exception as e:
logger.warning(f"{space_config['space']} failed: {e}")
continue
return None
def generate_3d_from_image(
image_path,
seed=0,
randomize_seed=True,
resolution="1024",
ss_guidance_strength=7.5,
ss_guidance_rescale=0.7,
ss_sampling_steps=12,
ss_rescale_t=5.0,
shape_slat_guidance_strength=7.5,
shape_slat_guidance_rescale=0.5,
shape_slat_sampling_steps=12,
shape_slat_rescale_t=3.0,
tex_slat_guidance_strength=1.0,
tex_slat_guidance_rescale=0.0,
tex_slat_sampling_steps=12,
tex_slat_rescale_t=3.0,
decimation_target=300000,
texture_size=2048
):
from gradio_client import Client, handle_file
logger.info("Starting 3D generation pipeline")
logger.info(f"Image: {image_path}")
max_retries = 5
retry_delays = [10, 30, 60, 90, 120]
for attempt in range(max_retries):
try:
if attempt > 0:
wait_time = retry_delays[min(attempt, len(retry_delays) - 1)]
logger.info(
f"Retry {attempt + 1}/{max_retries} "
f"after {wait_time}s wait..."
)
time.sleep(wait_time)
client = Client(TRELLIS_SPACE)
logger.info("Connected to TRELLIS.2")
logger.info("Step 1/5: Starting session...")
try:
client.predict(api_name="/start_session")
logger.info("Session started")
except Exception as e:
logger.warning(f"Start session note: {e}")
logger.info("Step 2/5: Preprocessing image...")
processed_path = image_path
try:
preprocessed = client.predict(
handle_file(image_path),
api_name="/preprocess_image"
)
logger.info(f"Preprocessed: {preprocessed}")
if preprocessed:
if isinstance(preprocessed, dict) and "path" in preprocessed:
processed_path = preprocessed["path"]
elif isinstance(preprocessed, str) and os.path.exists(preprocessed):
processed_path = preprocessed
elif hasattr(preprocessed, "path"):
processed_path = preprocessed.path
except Exception as e:
logger.warning(f"Preprocess note: {e}")
logger.info("Step 3/5: Getting seed...")
actual_seed = seed
try:
actual_seed = client.predict(
randomize_seed,
seed,
api_name="/get_seed"
)
logger.info(f"Seed: {actual_seed}")
except Exception as e:
logger.warning(f"Seed note: {e}")
logger.info("Step 4/5: Generating 3D (1-3 min)...")
if isinstance(processed_path, str):
image_input = handle_file(processed_path)
else:
image_input = processed_path
preview_result = client.predict(
image_input,
actual_seed,
resolution,
ss_guidance_strength,
ss_guidance_rescale,
ss_sampling_steps,
ss_rescale_t,
shape_slat_guidance_strength,
shape_slat_guidance_rescale,
shape_slat_sampling_steps,
shape_slat_rescale_t,
tex_slat_guidance_strength,
tex_slat_guidance_rescale,
tex_slat_sampling_steps,
tex_slat_rescale_t,
api_name="/image_to_3d"
)
logger.info(f"3D result: {str(preview_result)[:300]}")
logger.info("Step 5/5: Extracting GLB...")
glb_result = client.predict(
decimation_target,
texture_size,
api_name="/extract_glb"
)
logger.info(f"GLB result: {glb_result}")
glb_path = extract_glb_path(glb_result)
if glb_path and os.path.exists(glb_path):
file_id = uuid.uuid4().hex[:8]
output_path = os.path.join(TEMP_DIR, f"model_{file_id}.glb")
shutil.copy2(glb_path, output_path)
logger.info(f"GLB saved: {output_path}")
return output_path
logger.error(f"No GLB from: {glb_result}")
return None
except Exception as e:
error_msg = str(e)
logger.error(f"Attempt {attempt + 1} failed: {error_msg}")
is_quota_error = any(
phrase in error_msg.lower()
for phrase in [
"gpu quota",
"exceeded",
"queue",
"too many",
"rate limit",
"capacity",
"busy"
]
)
if is_quota_error and attempt < max_retries - 1:
logger.info("GPU quota issue. Will retry...")
continue
elif is_quota_error:
raise Exception(
"GPU quota exceeded on TRELLIS. "
"The free GPU is busy right now. "
"Please try again in 2-5 minutes. "
"Tip: Use resolution 512 for faster processing."
)
else:
raise
return None
def extract_glb_path(glb_result):
if glb_result is None:
return None
if isinstance(glb_result, tuple):
for item in glb_result:
path = extract_single_path(item)
if path:
return path
return extract_single_path(glb_result)
def extract_single_path(item):
if item is None:
return None
if isinstance(item, str) and os.path.exists(item):
return item
if isinstance(item, dict) and "path" in item:
if os.path.exists(item["path"]):
return item["path"]
if hasattr(item, "path"):
if isinstance(item.path, str) and os.path.exists(item.path):
return item.path
return None
def handle_image_to_3d(
image,
seed,
randomize_seed,
resolution,
ss_guidance_strength,
ss_guidance_rescale,
ss_sampling_steps,
ss_rescale_t,
shape_slat_guidance_strength,
shape_slat_guidance_rescale,
shape_slat_sampling_steps,
shape_slat_rescale_t,
tex_slat_guidance_strength,
tex_slat_guidance_rescale,
tex_slat_sampling_steps,
tex_slat_rescale_t,
decimation_target,
texture_size,
progress=gr.Progress()
):
if image is None:
raise gr.Error("Please upload an image!")
start_time = time.time()
progress(0.05, desc="Preparing image...")
img_id = uuid.uuid4().hex[:8]
img_path = os.path.join(TEMP_DIR, f"input_{img_id}.png")
try:
if isinstance(image, str) and os.path.exists(image):
img_path = image
elif hasattr(image, "save"):
image.save(img_path, "PNG")
else:
raise gr.Error("Invalid image format")
except gr.Error:
raise
except Exception as e:
raise gr.Error(f"Image error: {e}")
try:
progress(0.1, desc="Connecting to TRELLIS (free GPU)...")
progress(0.15, desc="Generating 3D... This takes 1-3 min. If GPU is busy it will auto-retry...")
glb_path = generate_3d_from_image(
image_path=img_path,
seed=seed,
randomize_seed=randomize_seed,
resolution=str(int(resolution)),
ss_guidance_strength=ss_guidance_strength,
ss_guidance_rescale=ss_guidance_rescale,
ss_sampling_steps=ss_sampling_steps,
ss_rescale_t=ss_rescale_t,
shape_slat_guidance_strength=shape_slat_guidance_strength,
shape_slat_guidance_rescale=shape_slat_guidance_rescale,
shape_slat_sampling_steps=shape_slat_sampling_steps,
shape_slat_rescale_t=shape_slat_rescale_t,
tex_slat_guidance_strength=tex_slat_guidance_strength,
tex_slat_guidance_rescale=tex_slat_guidance_rescale,
tex_slat_sampling_steps=tex_slat_sampling_steps,
tex_slat_rescale_t=tex_slat_rescale_t,
decimation_target=decimation_target,
texture_size=texture_size
)
duration = time.time() - start_time
if glb_path and os.path.exists(glb_path):
file_size = os.path.getsize(glb_path) / (1024 * 1024)
status = (
f"Done! Generated in {duration:.1f}s | "
f"File size: {file_size:.1f} MB"
)
progress(1.0, desc="Done!")
return glb_path, glb_path, status
else:
raise gr.Error("Generation completed but no GLB file was produced.")
except gr.Error:
raise
except Exception as e:
logger.error(f"Error: {e}", exc_info=True)
raise gr.Error(f"Generation failed: {str(e)}")
def handle_text_to_3d(
prompt,
seed,
randomize_seed,
resolution,
ss_guidance_strength,
ss_guidance_rescale,
ss_sampling_steps,
ss_rescale_t,
shape_slat_guidance_strength,
shape_slat_guidance_rescale,
shape_slat_sampling_steps,
shape_slat_rescale_t,
tex_slat_guidance_strength,
tex_slat_guidance_rescale,
tex_slat_sampling_steps,
tex_slat_rescale_t,
decimation_target,
texture_size,
progress=gr.Progress()
):
if not prompt or not prompt.strip():
raise gr.Error("Please enter a text prompt!")
start_time = time.time()
progress(0.05, desc="Step 1: Generating image from text...")
img_path = text_to_image(prompt, seed)
if img_path is None:
raise gr.Error(
"Could not generate image from text. "
"The image AI spaces might be busy. "
"Try the Image to 3D tab instead - "
"generate an image with any AI tool and upload it."
)
progress(0.3, desc="Step 2: Image ready! Generating 3D model...")
try:
glb_path = generate_3d_from_image(
image_path=img_path,
seed=seed,
randomize_seed=randomize_seed,
resolution=str(int(resolution)),
ss_guidance_strength=ss_guidance_strength,
ss_guidance_rescale=ss_guidance_rescale,
ss_sampling_steps=ss_sampling_steps,
ss_rescale_t=ss_rescale_t,
shape_slat_guidance_strength=shape_slat_guidance_strength,
shape_slat_guidance_rescale=shape_slat_guidance_rescale,
shape_slat_sampling_steps=shape_slat_sampling_steps,
shape_slat_rescale_t=shape_slat_rescale_t,
tex_slat_guidance_strength=tex_slat_guidance_strength,
tex_slat_guidance_rescale=tex_slat_guidance_rescale,
tex_slat_sampling_steps=tex_slat_sampling_steps,
tex_slat_rescale_t=tex_slat_rescale_t,
decimation_target=decimation_target,
texture_size=texture_size
)
duration = time.time() - start_time
if glb_path and os.path.exists(glb_path):
file_size = os.path.getsize(glb_path) / (1024 * 1024)
status = (
f"Done! Generated in {duration:.1f}s | "
f"Prompt: {prompt} | "
f"File: {file_size:.1f} MB"
)
progress(1.0, desc="Done!")
return img_path, glb_path, glb_path, status
else:
raise gr.Error("3D generation completed but no GLB file produced.")
except gr.Error:
raise
except Exception as e:
logger.error(f"Text to 3D error: {e}", exc_info=True)
raise gr.Error(f"Failed: {str(e)}")
def check_status():
try:
from gradio_client import Client
start = time.time()
client = Client(TRELLIS_SPACE)
connect_time = time.time() - start
api_str = client.view_api(return_format="str")
return (
"## Connected to TRELLIS.2\n\n"
f"**Space:** `{TRELLIS_SPACE}`\n\n"
f"**Connection time:** {connect_time:.1f}s\n\n"
"**Status:** Online and Ready\n\n"
"### How it works (all FREE):\n\n"
"| Step | Endpoint | What it does |\n"
"|------|----------|-------------|\n"
"| 1 | /start_session | Initialize |\n"
"| 2 | /preprocess_image | Clean image |\n"
"| 3 | /get_seed | Prepare seed |\n"
"| 4 | /image_to_3d | Generate 3D |\n"
"| 5 | /extract_glb | Get GLB file |\n\n"
"### Tips for free GPU:\n"
"- Use resolution **512** for fastest results\n"
"- If GPU quota error, wait 2-5 min and retry\n"
"- Auto-retry is built in (up to 5 attempts)\n"
"- Off-peak hours (night/early morning) work best\n\n"
"### Raw API:\n"
f"```\n{api_str}\n```"
)
except Exception as e:
return (
"## Connection Failed\n\n"
f"**Error:** `{str(e)}`\n\n"
"**Possible reasons:**\n"
"- Space is sleeping (wait 2-3 min)\n"
"- At capacity (try again later)\n"
"- Under maintenance\n\n"
"Click Check Connection again to retry."
)
def cleanup():
try:
count = 0
for f in Path(TEMP_DIR).glob("*"):
if f.is_file():
f.unlink()
count += 1
return f"Cleaned {count} files"
except Exception as e:
return f"Error: {e}"
css = """
.gradio-container {
max-width: 1200px !important;
margin: auto !important;
}
.gen-btn {
background: linear-gradient(135deg, #6c5ce7, #00b894) !important;
color: white !important;
font-size: 16px !important;
font-weight: 600 !important;
border: none !important;
border-radius: 10px !important;
min-height: 50px !important;
}
.gen-btn:hover {
transform: translateY(-2px) !important;
box-shadow: 0 6px 20px rgba(108, 92, 231, 0.4) !important;
}
.header-area {
text-align: center;
padding: 15px 0;
}
.header-area h1 {
background: linear-gradient(135deg, #6c5ce7, #00b894);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 2.2em !important;
}
.tip-box {
background: #e8f5e9;
border: 1px solid #4caf50;
border-radius: 8px;
padding: 10px 15px;
margin: 8px 0;
color: #2e7d32;
font-size: 14px;
}
.warn-box {
background: #fff3e0;
border: 1px solid #ff9800;
border-radius: 8px;
padding: 10px 15px;
margin: 8px 0;
color: #e65100;
font-size: 14px;
}
"""
with gr.Blocks(
title="TRELLIS 3D Generator",
theme=gr.themes.Soft(
primary_hue="purple",
secondary_hue="green",
neutral_hue="slate",
font=gr.themes.GoogleFont("Inter"),
),
css=css
) as demo:
gr.HTML(
'<div class="header-area">'
"<h1>TRELLIS 3D Generator</h1>"
'<p style="color: #888; font-size: 15px;">'
"Generate 3D models from images or text | 100% Free | Powered by Microsoft TRELLIS 2"
"</p>"
"</div>"
)
with gr.Tabs():
with gr.Tab("Image to 3D"):
gr.HTML(
'<div class="tip-box">'
"<strong>Tip:</strong> Use resolution 512 for fastest results. "
"If you get a GPU quota error, the app will auto-retry up to 5 times. "
"Best results during off-peak hours."
"</div>"
)
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("### Upload an Image")
img_input = gr.Image(
label="Upload Image",
type="pil",
height=280,
sources=["upload", "clipboard"]
)
gr.Markdown(
"Single objects on white or transparent "
"backgrounds work best."
)
with gr.Row():
img_seed = gr.Number(
label="Seed",
value=0,
minimum=0,
maximum=2147483647,
precision=0
)
img_randomize = gr.Checkbox(
label="Random Seed",
value=True
)
img_resolution = gr.Radio(
choices=["512", "1024", "1536"],
value="512",
label="Resolution (512 = fastest, less GPU usage)"
)
with gr.Accordion("Advanced: Sparse Structure", open=False):
img_ss_guidance = gr.Slider(
0, 20, 7.5, step=0.1,
label="Guidance Strength"
)
img_ss_rescale = gr.Slider(
0, 1, 0.7, step=0.05,
label="Guidance Rescale"
)
img_ss_steps = gr.Slider(
1, 50, 12, step=1,
label="Sampling Steps"
)
img_ss_rescale_t = gr.Slider(
0, 10, 5.0, step=0.1,
label="Rescale T"
)
with gr.Accordion("Advanced: Shape Latent", open=False):
img_shape_guidance = gr.Slider(
0, 20, 7.5, step=0.1,
label="Guidance Strength"
)
img_shape_rescale = gr.Slider(
0, 1, 0.5, step=0.05,
label="Guidance Rescale"
)
img_shape_steps = gr.Slider(
1, 50, 12, step=1,
label="Sampling Steps"
)
img_shape_rescale_t = gr.Slider(
0, 10, 3.0, step=0.1,
label="Rescale T"
)
with gr.Accordion("Advanced: Texture Latent", open=False):
img_tex_guidance = gr.Slider(
0, 20, 1.0, step=0.1,
label="Guidance Strength"
)
img_tex_rescale = gr.Slider(
0, 1, 0.0, step=0.05,
label="Guidance Rescale"
)
img_tex_steps = gr.Slider(
1, 50, 12, step=1,
label="Sampling Steps"
)
img_tex_rescale_t = gr.Slider(
0, 10, 3.0, step=0.1,
label="Rescale T"
)
with gr.Accordion("Advanced: Export Settings", open=False):
img_decimation = gr.Slider(
10000, 1000000, 300000,
step=10000,
label="Mesh Faces"
)
img_texture_size = gr.Slider(
512, 4096, 2048,
step=256,
label="Texture Size"
)
img_btn = gr.Button(
"Generate 3D Model",
variant="primary",
size="lg",
elem_classes="gen-btn"
)
img_status = gr.Markdown(
"Upload an image and click Generate!"
)
with gr.Column(scale=3):
gr.Markdown("### 3D Model Result")
img_model = gr.Model3D(
label="3D Viewer",
height=500
)
img_download = gr.File(label="Download GLB")
gr.Markdown(
"Drag = Rotate | Scroll = Zoom | Right-drag = Pan"
)
img_btn.click(
fn=handle_image_to_3d,
inputs=[
img_input,
img_seed,
img_randomize,
img_resolution,
img_ss_guidance,
img_ss_rescale,
img_ss_steps,
img_ss_rescale_t,
img_shape_guidance,
img_shape_rescale,
img_shape_steps,
img_shape_rescale_t,
img_tex_guidance,
img_tex_rescale,
img_tex_steps,
img_tex_rescale_t,
img_decimation,
img_texture_size
],
outputs=[img_model, img_download, img_status],
show_progress="full"
)
with gr.Tab("Text to 3D"):
gr.HTML(
'<div class="warn-box">'
"<strong>Note:</strong> Text to 3D works in 2 steps: "
"First generates an image from your text (Stable Diffusion), "
"then converts that image to 3D (TRELLIS). "
"Takes 2-5 minutes. 100% free."
"</div>"
)
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("### Describe Your 3D Model")
txt_prompt = gr.Textbox(
label="Text Prompt",
placeholder="A cute cat sitting, white background, single object, 3D render...",
lines=3
)
gr.Markdown("**Quick prompts:**")
with gr.Row():
sword_btn = gr.Button("Sword", size="sm")
cat_btn = gr.Button("Cat", size="sm")
ship_btn = gr.Button("Spaceship", size="sm")
with gr.Row():
chest_btn = gr.Button("Chest", size="sm")
house_btn = gr.Button("House", size="sm")
crown_btn = gr.Button("Crown", size="sm")
sword_btn.click(
fn=lambda: "A medieval sword with ornate golden handle, white background, 3D render, single object",
outputs=txt_prompt
)
cat_btn.click(
fn=lambda: "A cute cartoon cat sitting, white background, 3D render, single object",
outputs=txt_prompt
)
ship_btn.click(
fn=lambda: "A futuristic spaceship, white background, 3D render, single object",
outputs=txt_prompt
)
chest_btn.click(
fn=lambda: "A wooden treasure chest with gold, white background, 3D render, single object",
outputs=txt_prompt
)
house_btn.click(
fn=lambda: "A tiny cozy cottage house, white background, 3D render, single object",
outputs=txt_prompt
)
crown_btn.click(
fn=lambda: "A royal golden crown with jewels, white background, 3D render, single object",
outputs=txt_prompt
)
gr.Markdown(
"**Tip:** Add 'white background, 3D render, single object' "
"for better results."
)
with gr.Row():
txt_seed = gr.Number(
label="Seed",
value=0,
minimum=0,
maximum=2147483647,
precision=0
)
txt_randomize = gr.Checkbox(
label="Random Seed",
value=True
)
txt_resolution = gr.Radio(
choices=["512", "1024", "1536"],
value="512",
label="Resolution (512 = fastest)"
)
with gr.Accordion("Advanced: Sparse Structure", open=False):
txt_ss_guidance = gr.Slider(
0, 20, 7.5, step=0.1,
label="Guidance Strength"
)
txt_ss_rescale = gr.Slider(
0, 1, 0.7, step=0.05,
label="Guidance Rescale"
)
txt_ss_steps = gr.Slider(
1, 50, 12, step=1,
label="Sampling Steps"
)
txt_ss_rescale_t = gr.Slider(
0, 10, 5.0, step=0.1,
label="Rescale T"
)
with gr.Accordion("Advanced: Shape Latent", open=False):
txt_shape_guidance = gr.Slider(
0, 20, 7.5, step=0.1,
label="Guidance Strength"
)
txt_shape_rescale = gr.Slider(
0, 1, 0.5, step=0.05,
label="Guidance Rescale"
)
txt_shape_steps = gr.Slider(
1, 50, 12, step=1,
label="Sampling Steps"
)
txt_shape_rescale_t = gr.Slider(
0, 10, 3.0, step=0.1,
label="Rescale T"
)
with gr.Accordion("Advanced: Texture Latent", open=False):
txt_tex_guidance = gr.Slider(
0, 20, 1.0, step=0.1,
label="Guidance Strength"
)
txt_tex_rescale = gr.Slider(
0, 1, 0.0, step=0.05,
label="Guidance Rescale"
)
txt_tex_steps = gr.Slider(
1, 50, 12, step=1,
label="Sampling Steps"
)
txt_tex_rescale_t = gr.Slider(
0, 10, 3.0, step=0.1,
label="Rescale T"
)
with gr.Accordion("Advanced: Export", open=False):
txt_decimation = gr.Slider(
10000, 1000000, 300000,
step=10000,
label="Mesh Faces"
)
txt_texture_size = gr.Slider(
512, 4096, 2048,
step=256,
label="Texture Size"
)
txt_btn = gr.Button(
"Generate 3D from Text",
variant="primary",
size="lg",
elem_classes="gen-btn"
)
txt_status = gr.Markdown(
"Enter a prompt and click Generate!"
)
with gr.Column(scale=3):
gr.Markdown("### Generated Image to 3D Model")
txt_gen_image = gr.Image(
label="Generated Image (Step 1)",
height=200
)
txt_model = gr.Model3D(
label="3D Model (Step 2)",
height=400
)
txt_download = gr.File(label="Download GLB")
gr.Markdown(
"Drag = Rotate | Scroll = Zoom | Right-drag = Pan"
)
txt_btn.click(
fn=handle_text_to_3d,
inputs=[
txt_prompt,
txt_seed,
txt_randomize,
txt_resolution,
txt_ss_guidance,
txt_ss_rescale,
txt_ss_steps,
txt_ss_rescale_t,
txt_shape_guidance,
txt_shape_rescale,
txt_shape_steps,
txt_shape_rescale_t,
txt_tex_guidance,
txt_tex_rescale,
txt_tex_steps,
txt_tex_rescale_t,
txt_decimation,
txt_texture_size
],
outputs=[
txt_gen_image,
txt_model,
txt_download,
txt_status
],
show_progress="full"
)
with gr.Tab("Status and API"):
with gr.Row():
with gr.Column():
gr.Markdown("### Connection Status")
status_display = gr.Markdown(
"Click Check Connection to test."
)
with gr.Row():
st_btn = gr.Button(
"Check Connection",
variant="primary"
)
cl_btn = gr.Button(
"Clear Temp Files",
variant="secondary"
)
cl_msg = gr.Markdown("")
st_btn.click(
fn=check_status,
outputs=[status_display]
)
cl_btn.click(
fn=cleanup,
outputs=[cl_msg]
)
with gr.Column():
gr.Markdown("### How It Works")
gr.Markdown(
"**Text to 3D Pipeline:**\n\n"
"Text -> Stable Diffusion (free) -> Image -> TRELLIS (free) -> GLB\n\n"
"**Image to 3D Pipeline:**\n\n"
"Image -> TRELLIS (free) -> GLB\n\n"
"---\n\n"
"### Free GPU Tips\n\n"
"- Use resolution **512** for fastest/most reliable results\n"
"- If GPU quota error appears, app retries up to 5 times\n"
"- Best time: late night or early morning (less traffic)\n"
"- Each generation uses about 60-120s of GPU time\n"
"- The free quota resets every few minutes\n\n"
"---\n\n"
"### API Usage\n\n"
"```python\n"
"from gradio_client import Client, handle_file\n"
"\n"
"client = Client('YOUR-SPACE-URL')\n"
"\n"
"result = client.predict(\n"
" handle_file('image.png'),\n"
" 0, True, '512',\n"
" 7.5, 0.7, 12, 5.0,\n"
" 7.5, 0.5, 12, 3.0,\n"
" 1.0, 0.0, 12, 3.0,\n"
" 300000, 2048,\n"
" api_name='/image_to_3d'\n"
")\n"
"```"
)
gr.Markdown(
"<div style='text-align:center; padding:15px; "
"opacity:0.5; font-size:12px;'>"
"TRELLIS 3D Generator | 100% Free | "
"Powered by Microsoft TRELLIS 2 | Built with Gradio"
"</div>"
)
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=7860,
show_error=True
)