Spaces:
Running
on
Zero
Running
on
Zero
File size: 10,265 Bytes
498b8bb e444307 498b8bb 080c217 498b8bb fd9d8fc 498b8bb fd9d8fc 498b8bb fd9d8fc 498b8bb e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 498b8bb fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc 498b8bb fd9d8fc 498b8bb e7da273 fd9d8fc 498b8bb fd9d8fc e7da273 fd9d8fc e7da273 fd9d8fc 498b8bb fd9d8fc e7da273 |
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 207 208 209 |
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
UNIRIG_REPO_DIR = os.path.join(os.path.dirname(__file__), "UniRig")
# Path to the setup script
SETUP_SCRIPT = os.path.join(os.path.dirname(__file__), "setup_blender.sh")
# Check if Blender is installed
if not os.path.exists("/usr/local/bin/blender"):
print("Blender not found. Installing...")
subprocess.run(["bash", SETUP_SCRIPT], check=True)
else:
print("Blender is already installed.")
if not os.path.isdir(UNIRIG_REPO_DIR):
print(f"ERROR: UniRig repository not found at {UNIRIG_REPO_DIR}. Please clone it there.")
# Consider raising an error or displaying it in the UI if UniRig is critical for startup
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}")
else:
print("Warning: CUDA not available or not detected by PyTorch. UniRig performance will be severely impacted.")
@spaces.GPU # Decorator for ZeroGPU
def run_unirig_command(command_list, step_name):
"""
Helper function to run UniRig commands (now expecting bash scripts) using subprocess.
command_list: The full command and its arguments, e.g., ["bash", "script.sh", "--arg", "value"]
"""
# The command_list is now expected to be the full command, e.g., starting with "bash"
cmd = command_list
print(f"Running {step_name}: {' '.join(cmd)}")
process_env = os.environ.copy()
# Determine the path to the 'src' directory within UniRig, where the 'unirig' package resides.
unirig_src_dir = os.path.join(UNIRIG_REPO_DIR, "src")
# Explicitly add UNIRIG_REPO_DIR/src to PYTHONPATH for the subprocess.
# The bash scripts will internally call Python, which needs to find the 'unirig' package.
# Also, keep UNIRIG_REPO_DIR itself in case some scripts or modules there are run directly
# or expect the project root to be in PYTHONPATH.
existing_pythonpath = process_env.get('PYTHONPATH', '')
new_pythonpath_parts = [unirig_src_dir, UNIRIG_REPO_DIR] # UniRig/src first, then UniRig/
if existing_pythonpath:
# Prepend our paths to existing PYTHONPATH
new_pythonpath_parts.extend(existing_pythonpath.split(os.pathsep))
process_env["PYTHONPATH"] = os.pathsep.join(filter(None, new_pythonpath_parts)) # filter(None,...) handles empty existing_pythonpath
print(f"Set PYTHONPATH for subprocess: {process_env['PYTHONPATH']}")
try:
# Execute the command from the UniRig directory (UNIRIG_REPO_DIR)
# This is crucial for the bash scripts to find their relative paths (e.g., to Python scripts)
# and for any underlying Python/Hydra calls to find configurations (e.g., in UniRig/configs/)
result = subprocess.run(cmd, cwd=UNIRIG_REPO_DIR, capture_output=True, text=True, check=True, env=process_env)
print(f"{step_name} STDOUT:\n{result.stdout}")
if result.stderr:
print(f"{step_name} STDERR (non-fatal or warnings):\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}")
# Provide a more user-friendly error, potentially masking long tracebacks
error_summary = e.stderr.splitlines()[-5:] # Last 5 lines of stderr
raise gr.Error(f"Error in UniRig {step_name}. Details: {' '.join(error_summary)}")
except FileNotFoundError:
# This error means the executable (e.g., "bash" or the script itself) was not found.
print(f"ERROR: Could not find executable or script for {step_name}. Command: {' '.join(cmd)}. Is UniRig cloned correctly and 'bash' available?")
raise gr.Error(f"Setup error for UniRig {step_name}. Check server logs, UniRig directory structure, and script paths.")
except Exception as e_general:
print(f"An unexpected Python exception occurred in run_unirig_command for {step_name}: {e_general}")
raise gr.Error(f"Unexpected Python error during {step_name}: {str(e_general)[:500]}")
@spaces.GPU # Decorator for ZeroGPU
def rig_glb_mesh_multistep(input_glb_file_obj):
"""
Takes an input GLB file object (from gr.File with type="filepath"),
rigs it using the new UniRig multi-step process by calling its bash scripts,
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. Please check Space setup.")
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 # This is the absolute path from gr.File(type="filepath")
print(f"Input GLB path received: {input_glb_path}")
# Create a dedicated temporary directory for all intermediate and final files
# The output paths for UniRig scripts will point into this directory.
processing_temp_dir = tempfile.mkdtemp(prefix="unirig_processing_")
print(f"Using temporary processing directory: {processing_temp_dir}")
try:
base_name = os.path.splitext(os.path.basename(input_glb_path))[0]
# Define absolute paths for intermediate files within the processing_temp_dir
abs_skeleton_output_path = os.path.join(processing_temp_dir, f"{base_name}_skeleton.fbx")
abs_skin_output_path = os.path.join(processing_temp_dir, f"{base_name}_skin.fbx")
abs_final_rigged_glb_path = os.path.join(processing_temp_dir, f"{base_name}_rigged_final.glb")
# Step 1: Skeleton Prediction using generate_skeleton.sh
print("Step 1: Predicting Skeleton...")
skeleton_cmd = [
"bash", "launch/inference/generate_skeleton.sh",
"--input", input_glb_path, # Input is the original GLB
"--output", abs_skeleton_output_path
]
run_unirig_command(skeleton_cmd, "Skeleton Prediction")
if not os.path.exists(abs_skeleton_output_path):
raise gr.Error("Skeleton prediction failed to produce an output file. Check logs for UniRig errors.")
# Step 2: Skinning Weight Prediction using generate_skin.sh
print("Step 2: Predicting Skinning Weights...")
# generate_skin.sh requires the skeleton from step 1 as --input,
# and the original mesh as --source.
skin_cmd = [
"bash", "launch/inference/generate_skin.sh",
"--input", abs_skeleton_output_path, # Input is the skeleton FBX from previous step
"--source", input_glb_path, # Source is the original GLB mesh
"--output", abs_skin_output_path
]
run_unirig_command(skin_cmd, "Skinning Prediction")
if not os.path.exists(abs_skin_output_path):
raise gr.Error("Skinning prediction failed to produce an output file. Check logs for UniRig errors.")
# Step 3: Merge Skeleton/Skin with Original Mesh using merge.sh
print("Step 3: Merging Results...")
# merge.sh requires the skinned FBX as --source (which contains skeleton and weights)
# and the original GLB as --target.
merge_cmd = [
"bash", "launch/inference/merge.sh",
"--source", abs_skin_output_path, # Source is the skinned FBX from previous step
"--target", input_glb_path, # Target is the original GLB mesh
"--output", abs_final_rigged_glb_path
]
run_unirig_command(merge_cmd, "Merging")
if not os.path.exists(abs_final_rigged_glb_path):
raise gr.Error("Merging process failed to produce the final rigged GLB file. Check logs for UniRig errors.")
return abs_final_rigged_glb_path
except gr.Error:
if os.path.exists(processing_temp_dir):
shutil.rmtree(processing_temp_dir)
print(f"Cleaned up temporary directory: {processing_temp_dir}")
raise
except Exception as e:
print(f"An unexpected error occurred in rig_glb_mesh_multistep: {e}")
if os.path.exists(processing_temp_dir):
shutil.rmtree(processing_temp_dir)
print(f"Cleaned up temporary directory: {processing_temp_dir}")
raise gr.Error(f"An unexpected error occurred during processing: {str(e)[:500]}")
# --- 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"],
)
if not os.path.isdir(UNIRIG_REPO_DIR) and __name__ == "__main__":
print(f"CRITICAL STARTUP ERROR: UniRig repository not found at {UNIRIG_REPO_DIR}. The application will not work.")
iface = gr.Interface(
fn=rig_glb_mesh_multistep,
inputs=gr.File(
label="Upload .glb Mesh File",
type="filepath"
),
outputs=gr.Model3D(
label="Rigged 3D Model (.glb)",
clear_color=[0.8, 0.8, 0.8, 1.0],
),
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 by calling its provided bash scripts.\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: '{os.path.basename(UNIRIG_REPO_DIR)}'.\n"
f"UniRig Source: https://github.com/VAST-AI-Research/UniRig"
),
cache_examples=False,
theme=theme
)
if __name__ == "__main__":
if not os.path.isdir(UNIRIG_REPO_DIR):
print(f"CRITICAL: UniRig repository not found at {UNIRIG_REPO_DIR}. Ensure it's cloned in the Space's root.")
iface.launch() |