Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,185 +1,184 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
if not os.path.isdir(UNIRIG_REPO_DIR):
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
print(f"Running {step_name}: {' '.join(cmd)}")
|
| 34 |
-
|
| 35 |
-
process_env = os.environ.copy()
|
| 36 |
-
# It's generally better to ensure UniRig's internal scripts handle PYTHONPATH if needed,
|
| 37 |
-
# or that it's installed in a way that `python -m` works correctly from its root.
|
| 38 |
-
# Setting cwd=UNIRIG_REPO_DIR is often the key for Hydra.
|
| 39 |
-
|
| 40 |
-
try:
|
| 41 |
-
# Execute the command from the UniRig directory for Hydra to find configs
|
| 42 |
-
result = subprocess.run(cmd, cwd=UNIRIG_REPO_DIR, capture_output=True, text=True, check=True, env=process_env)
|
| 43 |
-
print(f"{step_name} STDOUT:\n{result.stdout}")
|
| 44 |
-
if result.stderr:
|
| 45 |
-
print(f"{step_name} STDERR (non-fatal or warnings):\n{result.stderr}")
|
| 46 |
-
except subprocess.CalledProcessError as e:
|
| 47 |
-
print(f"ERROR during {step_name}:")
|
| 48 |
-
print(f"Command: {' '.join(e.cmd)}")
|
| 49 |
-
print(f"Return code: {e.returncode}")
|
| 50 |
-
print(f"Stdout: {e.stdout}")
|
| 51 |
-
print(f"Stderr: {e.stderr}")
|
| 52 |
-
# Provide a more user-friendly error, potentially masking long tracebacks
|
| 53 |
-
error_summary = e.stderr.splitlines()[-5:] # Last 5 lines of stderr
|
| 54 |
-
raise gr.Error(f"Error in UniRig {step_name}. Details: {' '.join(error_summary)}")
|
| 55 |
-
except FileNotFoundError:
|
| 56 |
-
print(f"ERROR: Could not find executable or script for {step_name}. Is UniRig cloned correctly in {UNIRIG_REPO_DIR} and Python environment setup?")
|
| 57 |
-
raise gr.Error(f"Setup error for UniRig {step_name}. Check server logs and UniRig directory structure.")
|
| 58 |
-
except Exception as e_general:
|
| 59 |
-
print(f"An unexpected Python exception occurred in run_unirig_command for {step_name}: {e_general}")
|
| 60 |
-
raise gr.Error(f"Unexpected Python error during {step_name}: {str(e_general)[:500]}")
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
# If you are using @spaces.GPU, you would import it:
|
| 64 |
-
# import spaces
|
| 65 |
-
# @spaces.GPU # You can specify type like @spaces.GPU(type="t4") or count
|
| 66 |
-
def rig_glb_mesh_multistep(input_glb_file_obj):
|
| 67 |
-
"""
|
| 68 |
-
Takes an input GLB file object (from gr.File with type="filepath"),
|
| 69 |
-
rigs it using the new UniRig multi-step process,
|
| 70 |
-
and returns the path to the final rigged GLB file.
|
| 71 |
-
"""
|
| 72 |
-
if not os.path.isdir(UNIRIG_REPO_DIR):
|
| 73 |
-
raise gr.Error(f"UniRig repository not found at {UNIRIG_REPO_DIR}. Cannot proceed. Please check Space setup.")
|
| 74 |
-
|
| 75 |
-
if input_glb_file_obj is None:
|
| 76 |
-
# This case should ideally be handled by Gradio's input validation if `allow_none=False` (default)
|
| 77 |
-
raise gr.Error("No input file provided. Please upload a .glb mesh.")
|
| 78 |
-
|
| 79 |
-
# When type="filepath", input_glb_file_obj is the path string directly
|
| 80 |
-
input_glb_path = input_glb_file_obj
|
| 81 |
-
print(f"Input GLB path received: {input_glb_path}")
|
| 82 |
-
|
| 83 |
-
# Create a dedicated temporary directory for all intermediate and final files
|
| 84 |
-
processing_temp_dir = tempfile.mkdtemp(prefix="unirig_processing_")
|
| 85 |
-
print(f"Using temporary processing directory: {processing_temp_dir}")
|
| 86 |
-
|
| 87 |
-
try:
|
| 88 |
-
base_name = os.path.splitext(os.path.basename(input_glb_path))[0]
|
| 89 |
-
|
| 90 |
-
# Step 1: Skeleton Prediction
|
| 91 |
-
temp_skeleton_path = os.path.join(processing_temp_dir, f"{base_name}_skeleton.fbx")
|
| 92 |
-
print("Step 1: Predicting Skeleton...")
|
| 93 |
-
run_unirig_command([
|
| 94 |
-
"unirig.predict_skeleton",
|
| 95 |
-
f"input.path={os.path.abspath(input_glb_path)}", # Use absolute path for robustness
|
| 96 |
-
f"output.path={os.path.abspath(temp_skeleton_path)}",
|
| 97 |
-
# f"device={str(DEVICE)}" # If UniRig's script accepts this override and handles it
|
| 98 |
-
], "Skeleton Prediction")
|
| 99 |
-
if not os.path.exists(temp_skeleton_path):
|
| 100 |
-
raise gr.Error("Skeleton prediction failed to produce an output file. Check logs for UniRig errors.")
|
| 101 |
-
|
| 102 |
-
# Step 2: Skinning Weight Prediction
|
| 103 |
-
temp_skin_path = os.path.join(processing_temp_dir, f"{base_name}_skin.fbx")
|
| 104 |
-
print("Step 2: Predicting Skinning Weights...")
|
| 105 |
-
run_unirig_command([
|
| 106 |
-
"unirig.predict_skin",
|
| 107 |
-
f"input.skeleton_path={os.path.abspath(temp_skeleton_path)}",
|
| 108 |
-
f"input.source_mesh_path={os.path.abspath(input_glb_path)}",
|
| 109 |
-
f"output.path={os.path.abspath(temp_skin_path)}",
|
| 110 |
-
], "Skinning Prediction")
|
| 111 |
-
if not os.path.exists(temp_skin_path):
|
| 112 |
-
raise gr.Error("Skinning prediction failed to produce an output file. Check logs for UniRig errors.")
|
| 113 |
-
|
| 114 |
-
# Step 3: Merge Skeleton/Skin with Original Mesh
|
| 115 |
-
final_rigged_glb_path = os.path.join(processing_temp_dir, f"{base_name}_rigged_final.glb")
|
| 116 |
-
print("Step 3: Merging Results...")
|
| 117 |
-
run_unirig_command([
|
| 118 |
-
"unirig.merge_skeleton_skin",
|
| 119 |
-
f"input.source_rig_path={os.path.abspath(temp_skin_path)}",
|
| 120 |
-
f"input.target_mesh_path={os.path.abspath(input_glb_path)}",
|
| 121 |
-
f"output.path={os.path.abspath(final_rigged_glb_path)}",
|
| 122 |
-
], "Merging")
|
| 123 |
-
if not os.path.exists(final_rigged_glb_path):
|
| 124 |
-
raise gr.Error("Merging process failed to produce the final rigged GLB file. Check logs for UniRig errors.")
|
| 125 |
-
|
| 126 |
-
# final_rigged_glb_path is in processing_temp_dir.
|
| 127 |
-
# Gradio's gr.Model3D output component will handle serving this file.
|
| 128 |
-
return final_rigged_glb_path
|
| 129 |
-
|
| 130 |
-
except gr.Error: # Re-raise Gradio errors directly
|
| 131 |
-
if os.path.exists(processing_temp_dir): # Clean up on known Gradio error
|
| 132 |
-
shutil.rmtree(processing_temp_dir)
|
| 133 |
-
print(f"Cleaned up temporary directory: {processing_temp_dir}")
|
| 134 |
-
raise
|
| 135 |
-
except Exception as e:
|
| 136 |
-
print(f"An unexpected error occurred in rig_glb_mesh_multistep: {e}")
|
| 137 |
-
if os.path.exists(processing_temp_dir): # Clean up on unexpected error
|
| 138 |
-
shutil.rmtree(processing_temp_dir)
|
| 139 |
-
print(f"Cleaned up temporary directory: {processing_temp_dir}")
|
| 140 |
-
raise gr.Error(f"An unexpected error occurred during processing: {str(e)[:500]}")
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
# --- Gradio Interface ---
|
| 144 |
-
theme = gr.themes.Soft(
|
| 145 |
-
primary_hue=gr.themes.colors.sky,
|
| 146 |
-
secondary_hue=gr.themes.colors.blue,
|
| 147 |
-
neutral_hue=gr.themes.colors.slate,
|
| 148 |
-
font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
|
| 149 |
-
)
|
| 150 |
-
|
| 151 |
-
# Ensure UNIRIG_REPO_DIR check happens before interface is built if it's critical
|
| 152 |
-
if not os.path.isdir(UNIRIG_REPO_DIR) and __name__ == "__main__": # Check only if running as main script
|
| 153 |
-
print(f"CRITICAL STARTUP ERROR: UniRig repository not found at {UNIRIG_REPO_DIR}. The application will not work.")
|
| 154 |
-
|
| 155 |
-
# Define the interface
|
| 156 |
-
# Note: The @spaces.GPU decorator would go above the function `rig_glb_mesh_multistep`
|
| 157 |
-
iface = gr.Interface(
|
| 158 |
-
fn=rig_glb_mesh_multistep,
|
| 159 |
-
inputs=gr.File(
|
| 160 |
-
label="Upload .glb Mesh File",
|
| 161 |
-
type="filepath" # Corrected type for Gradio 5.x.x
|
| 162 |
-
),
|
| 163 |
-
outputs=gr.Model3D(
|
| 164 |
-
label="Rigged 3D Model (.glb)",
|
| 165 |
-
clear_color=[0.8, 0.8, 0.8, 1.0],
|
| 166 |
-
),
|
| 167 |
-
title="UniRig Auto-Rigger (Python 3.11 / PyTorch 2.3+)",
|
| 168 |
-
description=(
|
| 169 |
-
"Upload a 3D mesh in `.glb` format. This application uses the latest UniRig to automatically rig the mesh.\n"
|
| 170 |
-
"The process involves: 1. Skeleton Prediction, 2. Skinning Weight Prediction, 3. Merging.\n"
|
| 171 |
-
"This may take several minutes. Ensure your GLB has clean geometry.\n"
|
| 172 |
-
f"Running on: {str(DEVICE).upper()}. UniRig repo expected at: '{os.path.basename(UNIRIG_REPO_DIR)}'.\n"
|
| 173 |
-
f"UniRig Source: https://github.com/VAST-AI-Research/UniRig"
|
| 174 |
-
),
|
| 175 |
-
cache_examples=False,
|
| 176 |
-
theme=theme,
|
| 177 |
-
allow_flagging="never"
|
| 178 |
-
)
|
| 179 |
-
|
| 180 |
-
if __name__ == "__main__":
|
| 181 |
-
if not os.path.isdir(UNIRIG_REPO_DIR):
|
| 182 |
-
print(f"CRITICAL: UniRig repository not found at {UNIRIG_REPO_DIR}. Ensure it's cloned in the Space's root.")
|
| 183 |
|
| 184 |
-
|
| 185 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import torch
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
import tempfile
|
| 6 |
+
import shutil
|
| 7 |
+
import subprocess
|
| 8 |
+
# from huggingface_hub import HfApi, snapshot_download # For future model management if needed
|
| 9 |
+
# import spaces # For @spaces.GPU decorator if you add it
|
| 10 |
+
|
| 11 |
+
# --- Configuration ---
|
| 12 |
+
# Path to the cloned UniRig repository directory within the Space
|
| 13 |
+
UNIRIG_REPO_DIR = os.path.join(os.path.dirname(__file__), "UniRig")
|
| 14 |
+
|
| 15 |
+
if not os.path.isdir(UNIRIG_REPO_DIR):
|
| 16 |
+
print(f"ERROR: UniRig repository not found at {UNIRIG_REPO_DIR}. Please clone it there.")
|
| 17 |
+
# Consider raising an error or displaying it in the UI if UniRig is critical for startup
|
| 18 |
+
|
| 19 |
+
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 20 |
+
print(f"Using device: {DEVICE}")
|
| 21 |
+
if DEVICE.type == 'cuda':
|
| 22 |
+
print(f"CUDA Device Name: {torch.cuda.get_device_name(0)}")
|
| 23 |
+
print(f"CUDA Version: {torch.version.cuda}")
|
| 24 |
+
else:
|
| 25 |
+
print("Warning: CUDA not available or not detected by PyTorch. UniRig performance will be severely impacted.")
|
| 26 |
+
|
| 27 |
+
def run_unirig_command(command_args, step_name):
|
| 28 |
+
"""Helper function to run UniRig commands using subprocess."""
|
| 29 |
+
python_exe = sys.executable
|
| 30 |
+
# Ensure the command starts with the python executable and '-m' for module execution
|
| 31 |
+
cmd = [python_exe, "-m"] + command_args
|
| 32 |
+
|
| 33 |
+
print(f"Running {step_name}: {' '.join(cmd)}")
|
| 34 |
+
|
| 35 |
+
process_env = os.environ.copy()
|
| 36 |
+
# It's generally better to ensure UniRig's internal scripts handle PYTHONPATH if needed,
|
| 37 |
+
# or that it's installed in a way that `python -m` works correctly from its root.
|
| 38 |
+
# Setting cwd=UNIRIG_REPO_DIR is often the key for Hydra.
|
| 39 |
+
|
| 40 |
+
try:
|
| 41 |
+
# Execute the command from the UniRig directory for Hydra to find configs
|
| 42 |
+
result = subprocess.run(cmd, cwd=UNIRIG_REPO_DIR, capture_output=True, text=True, check=True, env=process_env)
|
| 43 |
+
print(f"{step_name} STDOUT:\n{result.stdout}")
|
| 44 |
+
if result.stderr:
|
| 45 |
+
print(f"{step_name} STDERR (non-fatal or warnings):\n{result.stderr}")
|
| 46 |
+
except subprocess.CalledProcessError as e:
|
| 47 |
+
print(f"ERROR during {step_name}:")
|
| 48 |
+
print(f"Command: {' '.join(e.cmd)}")
|
| 49 |
+
print(f"Return code: {e.returncode}")
|
| 50 |
+
print(f"Stdout: {e.stdout}")
|
| 51 |
+
print(f"Stderr: {e.stderr}")
|
| 52 |
+
# Provide a more user-friendly error, potentially masking long tracebacks
|
| 53 |
+
error_summary = e.stderr.splitlines()[-5:] # Last 5 lines of stderr
|
| 54 |
+
raise gr.Error(f"Error in UniRig {step_name}. Details: {' '.join(error_summary)}")
|
| 55 |
+
except FileNotFoundError:
|
| 56 |
+
print(f"ERROR: Could not find executable or script for {step_name}. Is UniRig cloned correctly in {UNIRIG_REPO_DIR} and Python environment setup?")
|
| 57 |
+
raise gr.Error(f"Setup error for UniRig {step_name}. Check server logs and UniRig directory structure.")
|
| 58 |
+
except Exception as e_general:
|
| 59 |
+
print(f"An unexpected Python exception occurred in run_unirig_command for {step_name}: {e_general}")
|
| 60 |
+
raise gr.Error(f"Unexpected Python error during {step_name}: {str(e_general)[:500]}")
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
# If you are using @spaces.GPU, you would import it:
|
| 64 |
+
# import spaces
|
| 65 |
+
# @spaces.GPU # You can specify type like @spaces.GPU(type="t4") or count
|
| 66 |
+
def rig_glb_mesh_multistep(input_glb_file_obj):
|
| 67 |
+
"""
|
| 68 |
+
Takes an input GLB file object (from gr.File with type="filepath"),
|
| 69 |
+
rigs it using the new UniRig multi-step process,
|
| 70 |
+
and returns the path to the final rigged GLB file.
|
| 71 |
+
"""
|
| 72 |
if not os.path.isdir(UNIRIG_REPO_DIR):
|
| 73 |
+
raise gr.Error(f"UniRig repository not found at {UNIRIG_REPO_DIR}. Cannot proceed. Please check Space setup.")
|
| 74 |
+
|
| 75 |
+
if input_glb_file_obj is None:
|
| 76 |
+
# This case should ideally be handled by Gradio's input validation if `allow_none=False` (default)
|
| 77 |
+
raise gr.Error("No input file provided. Please upload a .glb mesh.")
|
| 78 |
+
|
| 79 |
+
# When type="filepath", input_glb_file_obj is the path string directly
|
| 80 |
+
input_glb_path = input_glb_file_obj
|
| 81 |
+
print(f"Input GLB path received: {input_glb_path}")
|
| 82 |
+
|
| 83 |
+
# Create a dedicated temporary directory for all intermediate and final files
|
| 84 |
+
processing_temp_dir = tempfile.mkdtemp(prefix="unirig_processing_")
|
| 85 |
+
print(f"Using temporary processing directory: {processing_temp_dir}")
|
| 86 |
+
|
| 87 |
+
try:
|
| 88 |
+
base_name = os.path.splitext(os.path.basename(input_glb_path))[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
+
# Step 1: Skeleton Prediction
|
| 91 |
+
temp_skeleton_path = os.path.join(processing_temp_dir, f"{base_name}_skeleton.fbx")
|
| 92 |
+
print("Step 1: Predicting Skeleton...")
|
| 93 |
+
run_unirig_command([
|
| 94 |
+
"unirig.predict_skeleton",
|
| 95 |
+
f"input.path={os.path.abspath(input_glb_path)}", # Use absolute path for robustness
|
| 96 |
+
f"output.path={os.path.abspath(temp_skeleton_path)}",
|
| 97 |
+
# f"device={str(DEVICE)}" # If UniRig's script accepts this override and handles it
|
| 98 |
+
], "Skeleton Prediction")
|
| 99 |
+
if not os.path.exists(temp_skeleton_path):
|
| 100 |
+
raise gr.Error("Skeleton prediction failed to produce an output file. Check logs for UniRig errors.")
|
| 101 |
+
|
| 102 |
+
# Step 2: Skinning Weight Prediction
|
| 103 |
+
temp_skin_path = os.path.join(processing_temp_dir, f"{base_name}_skin.fbx")
|
| 104 |
+
print("Step 2: Predicting Skinning Weights...")
|
| 105 |
+
run_unirig_command([
|
| 106 |
+
"unirig.predict_skin",
|
| 107 |
+
f"input.skeleton_path={os.path.abspath(temp_skeleton_path)}",
|
| 108 |
+
f"input.source_mesh_path={os.path.abspath(input_glb_path)}",
|
| 109 |
+
f"output.path={os.path.abspath(temp_skin_path)}",
|
| 110 |
+
], "Skinning Prediction")
|
| 111 |
+
if not os.path.exists(temp_skin_path):
|
| 112 |
+
raise gr.Error("Skinning prediction failed to produce an output file. Check logs for UniRig errors.")
|
| 113 |
+
|
| 114 |
+
# Step 3: Merge Skeleton/Skin with Original Mesh
|
| 115 |
+
final_rigged_glb_path = os.path.join(processing_temp_dir, f"{base_name}_rigged_final.glb")
|
| 116 |
+
print("Step 3: Merging Results...")
|
| 117 |
+
run_unirig_command([
|
| 118 |
+
"unirig.merge_skeleton_skin",
|
| 119 |
+
f"input.source_rig_path={os.path.abspath(temp_skin_path)}",
|
| 120 |
+
f"input.target_mesh_path={os.path.abspath(input_glb_path)}",
|
| 121 |
+
f"output.path={os.path.abspath(final_rigged_glb_path)}",
|
| 122 |
+
], "Merging")
|
| 123 |
+
if not os.path.exists(final_rigged_glb_path):
|
| 124 |
+
raise gr.Error("Merging process failed to produce the final rigged GLB file. Check logs for UniRig errors.")
|
| 125 |
+
|
| 126 |
+
# final_rigged_glb_path is in processing_temp_dir.
|
| 127 |
+
# Gradio's gr.Model3D output component will handle serving this file.
|
| 128 |
+
return final_rigged_glb_path
|
| 129 |
+
|
| 130 |
+
except gr.Error: # Re-raise Gradio errors directly
|
| 131 |
+
if os.path.exists(processing_temp_dir): # Clean up on known Gradio error
|
| 132 |
+
shutil.rmtree(processing_temp_dir)
|
| 133 |
+
print(f"Cleaned up temporary directory: {processing_temp_dir}")
|
| 134 |
+
raise
|
| 135 |
+
except Exception as e:
|
| 136 |
+
print(f"An unexpected error occurred in rig_glb_mesh_multistep: {e}")
|
| 137 |
+
if os.path.exists(processing_temp_dir): # Clean up on unexpected error
|
| 138 |
+
shutil.rmtree(processing_temp_dir)
|
| 139 |
+
print(f"Cleaned up temporary directory: {processing_temp_dir}")
|
| 140 |
+
raise gr.Error(f"An unexpected error occurred during processing: {str(e)[:500]}")
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
# --- Gradio Interface ---
|
| 144 |
+
theme = gr.themes.Soft(
|
| 145 |
+
primary_hue=gr.themes.colors.sky,
|
| 146 |
+
secondary_hue=gr.themes.colors.blue,
|
| 147 |
+
neutral_hue=gr.themes.colors.slate,
|
| 148 |
+
font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
# Ensure UNIRIG_REPO_DIR check happens before interface is built if it's critical
|
| 152 |
+
if not os.path.isdir(UNIRIG_REPO_DIR) and __name__ == "__main__": # Check only if running as main script
|
| 153 |
+
print(f"CRITICAL STARTUP ERROR: UniRig repository not found at {UNIRIG_REPO_DIR}. The application will not work.")
|
| 154 |
+
|
| 155 |
+
# Define the interface
|
| 156 |
+
# Note: The @spaces.GPU decorator would go above the function `rig_glb_mesh_multistep`
|
| 157 |
+
iface = gr.Interface(
|
| 158 |
+
fn=rig_glb_mesh_multistep,
|
| 159 |
+
inputs=gr.File(
|
| 160 |
+
label="Upload .glb Mesh File",
|
| 161 |
+
type="filepath" # Corrected type for Gradio 5.x.x
|
| 162 |
+
),
|
| 163 |
+
outputs=gr.Model3D(
|
| 164 |
+
label="Rigged 3D Model (.glb)",
|
| 165 |
+
clear_color=[0.8, 0.8, 0.8, 1.0],
|
| 166 |
+
),
|
| 167 |
+
title="UniRig Auto-Rigger (Python 3.11 / PyTorch 2.3+)",
|
| 168 |
+
description=(
|
| 169 |
+
"Upload a 3D mesh in `.glb` format. This application uses the latest UniRig to automatically rig the mesh.\n"
|
| 170 |
+
"The process involves: 1. Skeleton Prediction, 2. Skinning Weight Prediction, 3. Merging.\n"
|
| 171 |
+
"This may take several minutes. Ensure your GLB has clean geometry.\n"
|
| 172 |
+
f"Running on: {str(DEVICE).upper()}. UniRig repo expected at: '{os.path.basename(UNIRIG_REPO_DIR)}'.\n"
|
| 173 |
+
f"UniRig Source: https://github.com/VAST-AI-Research/UniRig"
|
| 174 |
+
),
|
| 175 |
+
cache_examples=False,
|
| 176 |
+
theme=theme,
|
| 177 |
+
allow_flagging="never"
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
if __name__ == "__main__":
|
| 181 |
+
if not os.path.isdir(UNIRIG_REPO_DIR):
|
| 182 |
+
print(f"CRITICAL: UniRig repository not found at {UNIRIG_REPO_DIR}. Ensure it's cloned in the Space's root.")
|
| 183 |
+
|
| 184 |
+
iface.launch()
|