jkorstad commited on
Commit
498b8bb
·
verified ·
1 Parent(s): ce90a76

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +182 -183
app.py CHANGED
@@ -1,185 +1,184 @@
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()
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()