jkorstad commited on
Commit
9c99a70
·
verified ·
1 Parent(s): 458da6e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +41 -45
app.py CHANGED
@@ -20,7 +20,8 @@ BLENDER_PYTHON_VERSION = "python3.11" # Blender 4.2 uses Python 3.11
20
 
21
  # Construct paths dynamically based on the above
22
  BLENDER_PYTHON_DIR = os.path.join(BLENDER_INSTALL_DIR, BLENDER_PYTHON_VERSION_DIR, "python")
23
- BLENDER_PYTHON_EXEC = os.path.join(BLENDER_PYTHON_DIR, "bin", BLENDER_PYTHON_VERSION) # Still needed for PYTHONPATH
 
24
  BLENDER_PYTHON_LIB_PATH = os.path.join(BLENDER_PYTHON_DIR, "lib", BLENDER_PYTHON_VERSION)
25
  BLENDER_PYTHON_SITE_PACKAGES = os.path.join(BLENDER_PYTHON_LIB_PATH, "site-packages")
26
 
@@ -61,19 +62,23 @@ else:
61
  print(f"Blender Python executable found: {BLENDER_PYTHON_EXEC}")
62
 
63
  # Verify Blender Python site-packages path and bpy module presence
 
64
  if os.path.exists(BLENDER_PYTHON_SITE_PACKAGES):
65
  print(f"Blender Python site-packages found at: {BLENDER_PYTHON_SITE_PACKAGES}")
66
- # Check for 'bpy' directory which contains __init__.py and modules
67
- bpy_module_path = os.path.join(BLENDER_PYTHON_SITE_PACKAGES, "bpy")
68
- if os.path.isdir(bpy_module_path) and os.path.exists(os.path.join(bpy_module_path, "__init__.py")):
69
- print("Blender Python 'bpy' module directory found.")
70
- # Fallback check for bpy.so if directory structure is different
 
71
  elif os.path.exists(os.path.join(BLENDER_PYTHON_SITE_PACKAGES, "bpy.so")):
72
- print("Blender Python 'bpy.so' found.")
73
- else:
74
- print("WARNING: Blender Python 'bpy' module not found in site-packages. Check Blender installation or paths.")
 
 
75
  else:
76
- print(f"WARNING: Blender Python site-packages not found at {BLENDER_PYTHON_SITE_PACKAGES}. Check paths.")
77
 
78
 
79
  # Check for UniRig repository
@@ -157,63 +162,66 @@ def run_unirig_command(script_path: str, args: List[str], step_name: str):
157
  cmd = ["bash", script_path] + args
158
 
159
  print(f"\n--- Running UniRig Step: {step_name} ---")
160
- # Use subprocess.list2cmdline for a more accurate representation if needed,
161
- # but simple join is often fine for logging.
162
  print(f"Command: {' '.join(cmd)}")
163
 
164
  # Prepare the environment for the subprocess (shell script)
165
- # The shell script will likely invoke python and needs the correct env
166
  process_env = os.environ.copy()
167
  unirig_src_dir = os.path.join(UNIRIG_REPO_DIR, "src")
168
 
169
- # Crucial: Set PYTHONPATH so the python invoked by the shell script finds Blender's
170
- # site-packages and the UniRig source code.
171
  pythonpath_parts = [
172
  BLENDER_PYTHON_SITE_PACKAGES,
173
  unirig_src_dir,
174
- UNIRIG_REPO_DIR # Include base repo dir as well
175
  ]
176
  process_env["PYTHONPATH"] = os.pathsep.join(filter(None, pythonpath_parts))
177
  print(f"Subprocess PYTHONPATH: {process_env['PYTHONPATH']}")
178
 
179
- # Add Blender's library path and potentially the python bin path to PATH
180
- # This helps the shell find the correct python if needed, and shared libs
181
  blender_lib_path = os.path.join(BLENDER_PYTHON_DIR, "lib")
182
- blender_bin_path = os.path.join(BLENDER_PYTHON_DIR, "bin")
183
- process_env["LD_LIBRARY_PATH"] = f"{blender_lib_path}{os.pathsep}{process_env.get('LD_LIBRARY_PATH', '')}"
184
- process_env["PATH"] = f"{blender_bin_path}{os.pathsep}{process_env.get('PATH', '')}" # Add blender python bin to PATH
185
  print(f"Subprocess LD_LIBRARY_PATH: {process_env['LD_LIBRARY_PATH']}")
 
 
 
 
 
186
  print(f"Subprocess PATH: {process_env['PATH']}")
187
 
188
 
189
  try:
190
  # Execute the shell script.
191
  # cwd=UNIRIG_REPO_DIR ensures the script runs from the repo's root,
192
- # which is often necessary for finding relative paths within the script.
193
  result = subprocess.run(
194
  cmd,
195
  cwd=UNIRIG_REPO_DIR,
196
  capture_output=True,
197
  text=True,
198
  check=True, # Raises CalledProcessError on non-zero exit codes
199
- env=process_env
200
  )
201
  print(f"{step_name} STDOUT:\n{result.stdout}")
202
  if result.stderr:
203
- # Treat stderr as potential warnings unless check=True failed
204
  print(f"{step_name} STDERR (Warnings/Info):\n{result.stderr}")
205
 
206
  except subprocess.CalledProcessError as e:
207
- # Error occurred within the shell script (non-zero exit code)
208
  print(f"ERROR during {step_name}: Subprocess failed!")
209
  print(f"Command: {' '.join(e.cmd)}")
210
  print(f"Return code: {e.returncode}")
211
  print(f"--- {step_name} STDOUT ---:\n{e.stdout}")
212
  print(f"--- {step_name} STDERR ---:\n{e.stderr}")
213
- # Provide a concise error message to Gradio
214
  error_summary = e.stderr.strip().splitlines()
215
  last_lines = "\n".join(error_summary[-5:]) if error_summary else "No stderr output."
216
- raise gr.Error(f"Error in UniRig '{step_name}'. Check logs. Last error lines:\n{last_lines}")
 
 
 
 
 
 
 
217
 
218
  except FileNotFoundError:
219
  # This error means 'bash' or the script_path wasn't found
@@ -222,7 +230,6 @@ def run_unirig_command(script_path: str, args: List[str], step_name: str):
222
  raise gr.Error(f"Setup error for UniRig '{step_name}'. 'bash' or script '{os.path.basename(script_path)}' not found.")
223
 
224
  except Exception as e_general:
225
- # Catch any other unexpected Python errors during subprocess handling
226
  print(f"An unexpected Python exception occurred in run_unirig_command for {step_name}: {e_general}")
227
  import traceback
228
  traceback.print_exc()
@@ -240,12 +247,9 @@ def rig_glb_mesh_multistep(input_glb_file_obj):
240
  try:
241
  patch_asset_py() # Attempt patch, might still be relevant
242
  except gr.Error as e:
243
- # If patching is critical and fails, raise the error
244
- # raise e
245
  print(f"Ignoring patch error: {e}") # Or just log it
246
  except Exception as e:
247
  print(f"Ignoring unexpected patch error: {e}")
248
- # raise gr.Error(f"Failed to prepare UniRig environment: {e}")
249
 
250
  # --- Input Validation ---
251
  if input_glb_file_obj is None:
@@ -266,13 +270,13 @@ def rig_glb_mesh_multistep(input_glb_file_obj):
266
  try:
267
  # --- Define File Paths ---
268
  base_name = os.path.splitext(os.path.basename(input_glb_path))[0]
 
269
  abs_input_glb_path = os.path.abspath(input_glb_path)
270
  abs_skeleton_output_path = os.path.join(processing_temp_dir, f"{base_name}_skeleton.fbx")
271
  abs_skin_output_path = os.path.join(processing_temp_dir, f"{base_name}_skin.fbx")
272
  abs_final_rigged_glb_path = os.path.join(processing_temp_dir, f"{base_name}_rigged_final.glb")
273
 
274
  # --- Define Absolute Paths to UniRig SHELL Scripts ---
275
- # Construct absolute paths based on UNIRIG_REPO_DIR, using .sh extension
276
  skeleton_script_path = os.path.join(UNIRIG_REPO_DIR, "launch/inference/generate_skeleton.sh")
277
  skin_script_path = os.path.join(UNIRIG_REPO_DIR, "launch/inference/generate_skin.sh")
278
  merge_script_path = os.path.join(UNIRIG_REPO_DIR, "launch/inference/merge.sh")
@@ -281,28 +285,24 @@ def rig_glb_mesh_multistep(input_glb_file_obj):
281
 
282
  # Step 1: Skeleton Prediction
283
  print("\nStarting Step 1: Predicting Skeleton...")
284
- # Arguments for the generate_skeleton.sh script
285
  skeleton_args = [
286
  "--input", abs_input_glb_path,
287
  "--output", abs_skeleton_output_path
288
- # Add any other arguments the shell script expects
289
  ]
290
- # Check if script exists before running
291
  if not os.path.exists(skeleton_script_path):
292
  raise gr.Error(f"Skeleton script not found at: {skeleton_script_path}")
293
  run_unirig_command(skeleton_script_path, skeleton_args, "Skeleton Prediction")
294
  if not os.path.exists(abs_skeleton_output_path):
 
295
  raise gr.Error("Skeleton prediction failed. Output file not created. Check logs.")
296
  print("Step 1: Skeleton Prediction completed.")
297
 
298
  # Step 2: Skinning Weight Prediction
299
  print("\nStarting Step 2: Predicting Skinning Weights...")
300
- # Arguments for the generate_skin.sh script
301
  skin_args = [
302
  "--input", abs_skeleton_output_path, # Input is the skeleton from step 1
303
  "--source", abs_input_glb_path, # Source mesh
304
  "--output", abs_skin_output_path
305
- # Add any other arguments the shell script expects
306
  ]
307
  if not os.path.exists(skin_script_path):
308
  raise gr.Error(f"Skinning script not found at: {skin_script_path}")
@@ -313,12 +313,12 @@ def rig_glb_mesh_multistep(input_glb_file_obj):
313
 
314
  # Step 3: Merge Skeleton/Skin with Original Mesh
315
  print("\nStarting Step 3: Merging Results...")
316
- # Arguments for the merge.sh script
317
  merge_args = [
318
- "--source", abs_skin_output_path, # Input is the skinned result from step 2
319
- "--target", abs_input_glb_path, # Target is the original mesh
 
 
320
  "--output", abs_final_rigged_glb_path
321
- # Add any other arguments the shell script expects
322
  ]
323
  if not os.path.exists(merge_script_path):
324
  raise gr.Error(f"Merging script not found at: {merge_script_path}")
@@ -332,7 +332,6 @@ def rig_glb_mesh_multistep(input_glb_file_obj):
332
  return abs_final_rigged_glb_path
333
 
334
  except gr.Error as e:
335
- # If a gr.Error was raised, clean up and re-raise
336
  print(f"Gradio Error occurred: {e}")
337
  if os.path.exists(processing_temp_dir):
338
  shutil.rmtree(processing_temp_dir)
@@ -340,15 +339,12 @@ def rig_glb_mesh_multistep(input_glb_file_obj):
340
  raise e
341
 
342
  except Exception as e:
343
- # Catch any other unexpected errors
344
  print(f"An unexpected error occurred in rig_glb_mesh_multistep: {e}")
345
  import traceback
346
  traceback.print_exc()
347
- # Clean up temporary files before raising the error
348
  if os.path.exists(processing_temp_dir):
349
  shutil.rmtree(processing_temp_dir)
350
  print(f"Cleaned up temporary directory: {processing_temp_dir}")
351
- # Raise a generic Gradio error
352
  raise gr.Error(f"An unexpected error occurred during processing: {str(e)[:500]}")
353
 
354
 
 
20
 
21
  # Construct paths dynamically based on the above
22
  BLENDER_PYTHON_DIR = os.path.join(BLENDER_INSTALL_DIR, BLENDER_PYTHON_VERSION_DIR, "python")
23
+ BLENDER_PYTHON_BIN_DIR = os.path.join(BLENDER_PYTHON_DIR, "bin") # Directory containing python executable
24
+ BLENDER_PYTHON_EXEC = os.path.join(BLENDER_PYTHON_BIN_DIR, BLENDER_PYTHON_VERSION) # Full path to executable
25
  BLENDER_PYTHON_LIB_PATH = os.path.join(BLENDER_PYTHON_DIR, "lib", BLENDER_PYTHON_VERSION)
26
  BLENDER_PYTHON_SITE_PACKAGES = os.path.join(BLENDER_PYTHON_LIB_PATH, "site-packages")
27
 
 
62
  print(f"Blender Python executable found: {BLENDER_PYTHON_EXEC}")
63
 
64
  # Verify Blender Python site-packages path and bpy module presence
65
+ bpy_found = False
66
  if os.path.exists(BLENDER_PYTHON_SITE_PACKAGES):
67
  print(f"Blender Python site-packages found at: {BLENDER_PYTHON_SITE_PACKAGES}")
68
+ # Check 1: 'bpy' directory with __init__.py
69
+ bpy_module_dir = os.path.join(BLENDER_PYTHON_SITE_PACKAGES, "bpy")
70
+ if os.path.isdir(bpy_module_dir) and os.path.exists(os.path.join(bpy_module_dir, "__init__.py")):
71
+ print("Found 'bpy' module directory in site-packages.")
72
+ bpy_found = True
73
+ # Check 2: 'bpy.so' file (less common structure)
74
  elif os.path.exists(os.path.join(BLENDER_PYTHON_SITE_PACKAGES, "bpy.so")):
75
+ print("Found 'bpy.so' in site-packages.")
76
+ bpy_found = True
77
+
78
+ if not bpy_found:
79
+ print("WARNING: Blender Python 'bpy' module indicator not found in site-packages. Imports might fail.")
80
  else:
81
+ print(f"WARNING: Blender Python site-packages directory not found at {BLENDER_PYTHON_SITE_PACKAGES}. Check paths.")
82
 
83
 
84
  # Check for UniRig repository
 
162
  cmd = ["bash", script_path] + args
163
 
164
  print(f"\n--- Running UniRig Step: {step_name} ---")
 
 
165
  print(f"Command: {' '.join(cmd)}")
166
 
167
  # Prepare the environment for the subprocess (shell script)
 
168
  process_env = os.environ.copy()
169
  unirig_src_dir = os.path.join(UNIRIG_REPO_DIR, "src")
170
 
171
+ # 1. Set PYTHONPATH: Blender's site-packages + UniRig source
 
172
  pythonpath_parts = [
173
  BLENDER_PYTHON_SITE_PACKAGES,
174
  unirig_src_dir,
175
+ UNIRIG_REPO_DIR
176
  ]
177
  process_env["PYTHONPATH"] = os.pathsep.join(filter(None, pythonpath_parts))
178
  print(f"Subprocess PYTHONPATH: {process_env['PYTHONPATH']}")
179
 
180
+ # 2. Set LD_LIBRARY_PATH: Include Blender's Python library directory
 
181
  blender_lib_path = os.path.join(BLENDER_PYTHON_DIR, "lib")
182
+ # Prepend Blender lib path to existing LD_LIBRARY_PATH if it exists
183
+ existing_ld_path = process_env.get('LD_LIBRARY_PATH', '')
184
+ process_env["LD_LIBRARY_PATH"] = f"{blender_lib_path}{os.pathsep}{existing_ld_path}" if existing_ld_path else blender_lib_path
185
  print(f"Subprocess LD_LIBRARY_PATH: {process_env['LD_LIBRARY_PATH']}")
186
+
187
+ # 3. Set PATH: *Prepend* Blender's Python bin directory to the system PATH.
188
+ # This makes it the *first* place the shell looks for 'python' or 'python3.11'.
189
+ existing_path = process_env.get('PATH', '')
190
+ process_env["PATH"] = f"{BLENDER_PYTHON_BIN_DIR}{os.pathsep}{existing_path}"
191
  print(f"Subprocess PATH: {process_env['PATH']}")
192
 
193
 
194
  try:
195
  # Execute the shell script.
196
  # cwd=UNIRIG_REPO_DIR ensures the script runs from the repo's root,
 
197
  result = subprocess.run(
198
  cmd,
199
  cwd=UNIRIG_REPO_DIR,
200
  capture_output=True,
201
  text=True,
202
  check=True, # Raises CalledProcessError on non-zero exit codes
203
+ env=process_env # Pass the modified environment
204
  )
205
  print(f"{step_name} STDOUT:\n{result.stdout}")
206
  if result.stderr:
 
207
  print(f"{step_name} STDERR (Warnings/Info):\n{result.stderr}")
208
 
209
  except subprocess.CalledProcessError as e:
 
210
  print(f"ERROR during {step_name}: Subprocess failed!")
211
  print(f"Command: {' '.join(e.cmd)}")
212
  print(f"Return code: {e.returncode}")
213
  print(f"--- {step_name} STDOUT ---:\n{e.stdout}")
214
  print(f"--- {step_name} STDERR ---:\n{e.stderr}")
 
215
  error_summary = e.stderr.strip().splitlines()
216
  last_lines = "\n".join(error_summary[-5:]) if error_summary else "No stderr output."
217
+ # Check specifically for bpy/torch import errors within the subprocess stderr
218
+ if "ModuleNotFoundError: No module named 'bpy'" in e.stderr:
219
+ raise gr.Error(f"Error in UniRig '{step_name}': Script failed to import Blender's 'bpy' module. Check environment setup.")
220
+ elif "ImportError: Failed to load PyTorch C extensions" in e.stderr:
221
+ raise gr.Error(f"Error in UniRig '{step_name}': Script failed to load PyTorch extensions. Check environment and PyTorch installation within Blender's Python.")
222
+ else:
223
+ raise gr.Error(f"Error in UniRig '{step_name}'. Check logs. Last error lines:\n{last_lines}")
224
+
225
 
226
  except FileNotFoundError:
227
  # This error means 'bash' or the script_path wasn't found
 
230
  raise gr.Error(f"Setup error for UniRig '{step_name}'. 'bash' or script '{os.path.basename(script_path)}' not found.")
231
 
232
  except Exception as e_general:
 
233
  print(f"An unexpected Python exception occurred in run_unirig_command for {step_name}: {e_general}")
234
  import traceback
235
  traceback.print_exc()
 
247
  try:
248
  patch_asset_py() # Attempt patch, might still be relevant
249
  except gr.Error as e:
 
 
250
  print(f"Ignoring patch error: {e}") # Or just log it
251
  except Exception as e:
252
  print(f"Ignoring unexpected patch error: {e}")
 
253
 
254
  # --- Input Validation ---
255
  if input_glb_file_obj is None:
 
270
  try:
271
  # --- Define File Paths ---
272
  base_name = os.path.splitext(os.path.basename(input_glb_path))[0]
273
+ # Ensure paths passed to scripts are absolute
274
  abs_input_glb_path = os.path.abspath(input_glb_path)
275
  abs_skeleton_output_path = os.path.join(processing_temp_dir, f"{base_name}_skeleton.fbx")
276
  abs_skin_output_path = os.path.join(processing_temp_dir, f"{base_name}_skin.fbx")
277
  abs_final_rigged_glb_path = os.path.join(processing_temp_dir, f"{base_name}_rigged_final.glb")
278
 
279
  # --- Define Absolute Paths to UniRig SHELL Scripts ---
 
280
  skeleton_script_path = os.path.join(UNIRIG_REPO_DIR, "launch/inference/generate_skeleton.sh")
281
  skin_script_path = os.path.join(UNIRIG_REPO_DIR, "launch/inference/generate_skin.sh")
282
  merge_script_path = os.path.join(UNIRIG_REPO_DIR, "launch/inference/merge.sh")
 
285
 
286
  # Step 1: Skeleton Prediction
287
  print("\nStarting Step 1: Predicting Skeleton...")
 
288
  skeleton_args = [
289
  "--input", abs_input_glb_path,
290
  "--output", abs_skeleton_output_path
 
291
  ]
 
292
  if not os.path.exists(skeleton_script_path):
293
  raise gr.Error(f"Skeleton script not found at: {skeleton_script_path}")
294
  run_unirig_command(skeleton_script_path, skeleton_args, "Skeleton Prediction")
295
  if not os.path.exists(abs_skeleton_output_path):
296
+ # Check if the error wasn't already raised by run_unirig_command
297
  raise gr.Error("Skeleton prediction failed. Output file not created. Check logs.")
298
  print("Step 1: Skeleton Prediction completed.")
299
 
300
  # Step 2: Skinning Weight Prediction
301
  print("\nStarting Step 2: Predicting Skinning Weights...")
 
302
  skin_args = [
303
  "--input", abs_skeleton_output_path, # Input is the skeleton from step 1
304
  "--source", abs_input_glb_path, # Source mesh
305
  "--output", abs_skin_output_path
 
306
  ]
307
  if not os.path.exists(skin_script_path):
308
  raise gr.Error(f"Skinning script not found at: {skin_script_path}")
 
313
 
314
  # Step 3: Merge Skeleton/Skin with Original Mesh
315
  print("\nStarting Step 3: Merging Results...")
 
316
  merge_args = [
317
+ # Determine which source to use based on what exists or a user choice?
318
+ # Assuming skin output is the desired source if it exists.
319
+ "--source", abs_skin_output_path,
320
+ "--target", abs_input_glb_path,
321
  "--output", abs_final_rigged_glb_path
 
322
  ]
323
  if not os.path.exists(merge_script_path):
324
  raise gr.Error(f"Merging script not found at: {merge_script_path}")
 
332
  return abs_final_rigged_glb_path
333
 
334
  except gr.Error as e:
 
335
  print(f"Gradio Error occurred: {e}")
336
  if os.path.exists(processing_temp_dir):
337
  shutil.rmtree(processing_temp_dir)
 
339
  raise e
340
 
341
  except Exception as e:
 
342
  print(f"An unexpected error occurred in rig_glb_mesh_multistep: {e}")
343
  import traceback
344
  traceback.print_exc()
 
345
  if os.path.exists(processing_temp_dir):
346
  shutil.rmtree(processing_temp_dir)
347
  print(f"Cleaned up temporary directory: {processing_temp_dir}")
 
348
  raise gr.Error(f"An unexpected error occurred during processing: {str(e)[:500]}")
349
 
350