Spaces:
Running
Running
| 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 | |
| ) |