Spaces:
Running
on
Zero
Running
on
Zero
File size: 10,658 Bytes
da47f4c 3561fbf 04fa405 da47f4c 3561fbf da47f4c 3561fbf da47f4c 3561fbf 04fa405 3561fbf da47f4c 04fa405 3561fbf da47f4c 3561fbf da47f4c 3561fbf da47f4c 3561fbf da47f4c 3561fbf da47f4c 3561fbf da47f4c 3561fbf da47f4c 3561fbf da47f4c 3561fbf da47f4c 3561fbf da47f4c 3561fbf da47f4c 3561fbf da47f4c 3561fbf da47f4c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
import gradio as gr
import torch
import os
import sys
import tempfile
import shutil
import subprocess
import spaces
# --- Configuration ---
# Path to the cloned UniRig repository directory within the Space
# IMPORTANT: You must clone the UniRig repository into this directory in your Hugging Face Space.
UNIRIG_REPO_DIR = os.path.join(os.path.dirname(__file__), "UniRig")
# Check if UniRig directory exists
if not os.path.isdir(UNIRIG_REPO_DIR):
# This message will appear in logs, Gradio app might fail to start fully.
print(f"ERROR: UniRig repository not found at {UNIRIG_REPO_DIR}. Please clone it there.")
# Optionally, raise an error to make it more visible if the app starts
# raise RuntimeError(f"UniRig repository not found at {UNIRIG_REPO_DIR}. Please clone it there.")
# Determine processing device (CUDA if available, otherwise CPU)
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {DEVICE}")
if DEVICE.type == 'cuda':
print(f"CUDA Device Name: {torch.cuda.get_device_name(0)}")
print(f"CUDA Version: {torch.version.cuda}")
# Note: UniRig scripts might manage device internally or via Hydra configs.
else:
print("Warning: CUDA not available or not detected by PyTorch. UniRig performance will be severely impacted.")
@spaces.GPU
def run_unirig_command(command_args, step_name):
"""Helper function to run UniRig commands using subprocess."""
python_exe = sys.executable # Use the current python interpreter
cmd = [python_exe, "-m"] + command_args
print(f"Running {step_name}: {' '.join(cmd)}")
# UniRig scripts often expect to be run from the root of the UniRig repository
# because they use Hydra and relative paths for configs (e.g., conf/config.yaml)
process_env = os.environ.copy()
# Add UniRig's parent directory to PYTHONPATH so `import unirig` works if needed,
# and the UniRig directory itself so its internal imports work.
# However, `python -m` typically handles package discovery well if CWD is correct.
# process_env["PYTHONPATH"] = f"{UNIRIG_REPO_DIR}{os.pathsep}{process_env.get('PYTHONPATH', '')}"
try:
# Execute the command
# It's crucial to set `cwd=UNIRIG_REPO_DIR` for Hydra to find its configs.
result = subprocess.run(cmd, cwd=UNIRIG_REPO_DIR, capture_output=True, text=True, check=True, env=process_env)
print(f"{step_name} output:\n{result.stdout}")
if result.stderr:
print(f"{step_name} errors (non-fatal if check=True passed):\n{result.stderr}")
except subprocess.CalledProcessError as e:
print(f"ERROR during {step_name}:")
print(f"Command: {' '.join(e.cmd)}")
print(f"Return code: {e.returncode}")
print(f"Stdout: {e.stdout}")
print(f"Stderr: {e.stderr}")
raise gr.Error(f"Error in UniRig {step_name}: {e.stderr[:500]}") # Show first 500 chars of error
except FileNotFoundError:
# This can happen if UNIRIG_REPO_DIR is not populated correctly or python_exe is wrong
print(f"ERROR: Could not find executable or script for {step_name}. Is UniRig cloned correctly in {UNIRIG_REPO_DIR}?")
raise gr.Error(f"Setup error for UniRig {step_name}. Check server logs.")
# --- Core Rigging Function ---
@spaces.GPU
def rig_glb_mesh_multistep(input_glb_file_obj):
"""
Takes an input GLB file object, rigs it using the new UniRig multi-step process,
and returns the path to the final rigged GLB file.
"""
if not os.path.isdir(UNIRIG_REPO_DIR):
raise gr.Error(f"UniRig repository not found at {UNIRIG_REPO_DIR}. Cannot proceed.")
if input_glb_file_obj is None:
raise gr.Error("No input file provided. Please upload a .glb mesh.")
input_glb_path = input_glb_file_obj.name # Path to the temporary uploaded file
# Create a dedicated temporary directory for all intermediate and final files for this run
# This helps in organization and cleanup.
processing_temp_dir = tempfile.mkdtemp(prefix="unirig_processing_")
print(f"Using temporary processing directory: {processing_temp_dir}")
try:
# Define paths for intermediate and final files within the processing_temp_dir
# UniRig scripts expect output paths.
base_name = os.path.splitext(os.path.basename(input_glb_path))[0]
# Step 1: Skeleton Prediction
# Output is typically an FBX file for the skeleton
temp_skeleton_path = os.path.join(processing_temp_dir, f"{base_name}_skeleton.fbx")
print("Step 1: Predicting Skeleton...")
# Command: python -m unirig.predict_skeleton +input_path=<input_glb_path> +output_path=<temp_skeleton_path>
# Note: UniRig's scripts might have default output locations or require specific Hydra overrides.
# The `+` syntax is for Hydra overrides.
# Check UniRig's `conf/predict_skeleton.yaml` for default config values.
run_unirig_command([
"unirig.predict_skeleton",
f"input.path={input_glb_path}", # Use dot notation for Hydra parameters
f"output.path={temp_skeleton_path}",
# Add other necessary overrides, e.g., for device if not auto-detected well
# f"device={str(DEVICE)}" # If UniRig's script accepts this override
], "Skeleton Prediction")
print(f"Skeleton predicted at: {temp_skeleton_path}")
if not os.path.exists(temp_skeleton_path):
raise gr.Error("Skeleton prediction failed to produce an output file.")
# Step 2: Skinning Weight Prediction
# Input: skeleton FBX and original GLB. Output: skinned FBX (or other format)
temp_skin_path = os.path.join(processing_temp_dir, f"{base_name}_skin.fbx")
print("Step 2: Predicting Skinning Weights...")
# Command: python -m unirig.predict_skin +input_path=<temp_skeleton_path> +output_path=<temp_skin_path> +source_mesh_path=<input_glb_path>
run_unirig_command([
"unirig.predict_skin",
f"input.skeleton_path={temp_skeleton_path}", # Check exact Hydra param name in UniRig
f"input.source_mesh_path={input_glb_path}", # Check exact Hydra param name
f"output.path={temp_skin_path}",
], "Skinning Prediction")
print(f"Skinning predicted at: {temp_skin_path}")
if not os.path.exists(temp_skin_path):
raise gr.Error("Skinning prediction failed to produce an output file.")
# Step 3: Merge Skeleton/Skin with Original Mesh
# Input: original GLB and the skin FBX (which contains skeleton + weights). Output: final rigged GLB
final_rigged_glb_path = os.path.join(processing_temp_dir, f"{base_name}_rigged_final.glb")
print("Step 3: Merging Results...")
# Command: python -m unirig.merge_skeleton_skin +source_path=<temp_skin_path> +target_path=<input_glb_path> +output_path=<final_rigged_glb_path>
run_unirig_command([
"unirig.merge_skeleton_skin",
f"input.source_rig_path={temp_skin_path}", # Path to the file with skeleton and skin weights
f"input.target_mesh_path={input_glb_path}", # Path to the original mesh
f"output.path={final_rigged_glb_path}",
], "Merging")
print(f"Final rigged mesh at: {final_rigged_glb_path}")
if not os.path.exists(final_rigged_glb_path):
raise gr.Error("Merging process failed to produce the final rigged GLB file.")
# The final_rigged_glb_path needs to be accessible by Gradio to serve it.
# Gradio usually copies temp files it creates, but here we created it.
# We return the path, and Gradio should handle it.
# The processing_temp_dir will be cleaned up by Gradio if input_glb_file_obj is from gr.File
# or we can clean it up if we copy the final file to a Gradio managed temp location.
# For gr.Model3D, returning a path is fine.
return final_rigged_glb_path
except gr.Error: # Re-raise Gradio errors directly
raise
except Exception as e:
print(f"An unexpected error occurred: {e}")
# Clean up the processing directory in case of an unhandled error
if os.path.exists(processing_temp_dir):
shutil.rmtree(processing_temp_dir)
raise gr.Error(f"An unexpected error occurred during processing: {str(e)}")
# Note: Do not clean up processing_temp_dir in a `finally` block here if returning path from it,
# as Gradio needs the file to exist to serve it. Gradio's gr.File output type handles temp file cleanup.
# If outputting gr.File, copy the final file to a new tempfile managed by Gradio.
# For gr.Model3D, path is fine.
# --- Gradio Interface ---
theme = gr.themes.Soft(
primary_hue=gr.themes.colors.sky,
secondary_hue=gr.themes.colors.blue,
neutral_hue=gr.themes.colors.slate,
font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
)
iface = gr.Interface(
fn=rig_glb_mesh_multistep,
inputs=gr.File(label="Upload .glb Mesh File", type="file"),
outputs=gr.Model3D(
label="Rigged 3D Model (.glb)",
clear_color=[0.8, 0.8, 0.8, 1.0],
# Note: Model3D might have issues with complex GLBs or certain rigging structures.
# A gr.File output for download might be a safer fallback.
# outputs=[gr.Model3D(...), gr.File(label="Download Rigged GLB")]
),
title="UniRig Auto-Rigger (Python 3.11 / PyTorch 2.3+)",
description=(
"Upload a 3D mesh in `.glb` format. This application uses the latest UniRig to automatically rig the mesh.\n"
"The process involves: 1. Skeleton Prediction, 2. Skinning Weight Prediction, 3. Merging.\n"
"This may take several minutes. Ensure your GLB has clean geometry.\n"
f"Running on: {str(DEVICE).upper()}. UniRig repo expected at: '{UNIRIG_REPO_DIR}'.\n"
f"UniRig Source: https://github.com/VAST-AI-Research/UniRig"
),
cache_examples=False,
theme=theme,
allow_flagging="never"
)
if __name__ == "__main__":
# Perform a quick check for UniRig directory on launch
if not os.path.isdir(UNIRIG_REPO_DIR):
print(f"CRITICAL: UniRig repository not found at {UNIRIG_REPO_DIR}. The application will likely fail.")
# You could display this error in the Gradio interface itself using a dummy function or Markdown.
# For local testing, you might need to set PYTHONPATH or ensure UniRig is installed.
# Example: os.environ["PYTHONPATH"] = f"{UNIRIG_REPO_DIR}{os.pathsep}{os.environ.get('PYTHONPATH', '')}"
iface.launch()
|