"""Blender post-processing for game-ready assets.""" import os import shutil import subprocess from pathlib import Path from typing import Optional, Tuple class BlenderProcessor: """Processes GLB files using Blender for game optimization.""" def __init__(self): self.blender_path = self._find_blender() def _find_blender(self) -> Optional[Path]: """Find Blender executable across platforms.""" # Check environment variable if blender_path := os.getenv("BLENDER_PATH"): if os.path.exists(blender_path): print(f"[Blender] Found via BLENDER_PATH: {blender_path}") return Path(blender_path) # Check common locations common_paths = [ "/usr/bin/blender", # Linux (HF Space) "/usr/local/bin/blender", "/app/blender/blender", "D:/KIRO/Projects/XStudios/Blender/blender.exe", # Local dev ] for path in common_paths: if os.path.exists(path): print(f"[Blender] Found at: {path}") return Path(path) # Try system PATH if blender_path := shutil.which("blender"): print(f"[Blender] Found in PATH: {blender_path}") return Path(blender_path) print("[Blender] WARNING: Blender not found") return None def is_available(self) -> bool: """Check if Blender is available.""" return self.blender_path is not None def optimize( self, input_path: Path, output_path: Path, script_path: Path ) -> Tuple[bool, str]: """Optimize GLB using external Blender script.""" if not self.is_available(): return False, "Blender not available" try: print(f"[Blender] Optimizing: {input_path.name}") # Run Blender in background mode cmd = [ str(self.blender_path), "--background", "--python", str(script_path), "--", str(input_path), str(output_path) ] result = subprocess.run( cmd, capture_output=True, text=True, timeout=120 # 2 minute timeout ) if result.returncode != 0: error_msg = result.stderr[-500:] if result.stderr else "Unknown error" return False, f"Blender failed: {error_msg}" if not output_path.exists(): return False, "Output file not created" # Get file sizes input_size = input_path.stat().st_size / 1e6 output_size = output_path.stat().st_size / 1e6 reduction = ((input_size - output_size) / input_size) * 100 message = f"Optimized: {input_size:.2f}MB → {output_size:.2f}MB ({reduction:.1f}% reduction)" print(f"[Blender] {message}") return True, message except subprocess.TimeoutExpired: return False, "Blender timeout (>2 minutes)" except Exception as e: return False, f"Blender error: {str(e)}"