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