Xernive's picture
fix: revert to API client with better error handling (Hunyuan3D not pip-installable)
26f8b9a
"""Main asset generation pipeline orchestration."""
import time
from pathlib import Path
from typing import Optional
from core.config import QUALITY_PRESETS
from core.types import GenerationResult, AssetMetadata
from generators import FluxGenerator, HunyuanGenerator
from processors import BlenderProcessor, AssetValidator
from utils import CacheManager, SecurityManager
class AssetPipeline:
"""Orchestrates the complete asset generation pipeline."""
def __init__(self):
self.flux = FluxGenerator()
self.hunyuan = HunyuanGenerator() # API client with better error handling
self.blender = BlenderProcessor()
self.validator = AssetValidator()
self.cache = CacheManager()
self.security = SecurityManager()
self.output_dir = Path("outputs")
self.temp_dir = Path("temp")
self.script_dir = Path("scripts")
# Create directories
self.output_dir.mkdir(exist_ok=True)
self.temp_dir.mkdir(exist_ok=True)
def generate(
self,
prompt: str,
quality: str = "High",
progress_callback: Optional[callable] = None
) -> GenerationResult:
"""
Generate 3D asset from text prompt.
Args:
prompt: Text description of asset
quality: Quality preset (Fast/Balanced/High/Ultra)
progress_callback: Optional callback for progress updates
Returns:
GenerationResult with GLB path and metadata
"""
start_time = time.time()
def update_progress(value: float, desc: str):
if progress_callback:
progress_callback(value, desc=desc)
print(f"[Pipeline] {desc} ({value*100:.0f}%)")
try:
# Step 1: Security checks
update_progress(0.0, "Validating input...")
prompt = self.security.sanitize_prompt(prompt)
self.security.check_rate_limit()
# Step 2: Check cache
update_progress(0.05, "Checking cache...")
if cached_path := self.cache.get_cached_result(prompt, quality):
elapsed = time.time() - start_time
preset = QUALITY_PRESETS[quality]
metadata = AssetMetadata(
prompt=prompt,
quality=quality,
flux_steps=preset.flux_steps,
hunyuan_steps=preset.hunyuan_steps,
file_size_mb=cached_path.stat().st_size / 1e6,
generation_time_s=elapsed,
optimized=True,
rigged=False
)
return GenerationResult(
glb_path=cached_path,
status_message="✨ Loaded from cache (saved GPU quota!)",
metadata=metadata,
cached=True
)
# Step 3: Get quality preset
preset = QUALITY_PRESETS.get(quality, QUALITY_PRESETS["High"])
# Step 4: Generate 2D image (FLUX)
update_progress(0.1, f"Generating 2D image (FLUX {preset.flux_steps} steps)...")
image_path = self.flux.generate(prompt, preset, self.temp_dir)
# Step 5: Generate 3D model (Hunyuan3D)
update_progress(0.5, f"Converting to 3D (Hunyuan3D {preset.hunyuan_steps} steps)...")
glb_path = self.hunyuan.generate(image_path, preset, self.temp_dir)
# Step 6: Validate GLB
update_progress(0.8, "Validating 3D model...")
is_valid, validation_msg = self.validator.validate_glb(glb_path)
if not is_valid:
raise ValueError(f"GLB validation failed: {validation_msg}")
# Step 7: Blender optimization (if available)
update_progress(0.85, "Optimizing for game engine...")
raw_path = self.output_dir / f"asset_raw_{int(time.time())}.glb"
import shutil
shutil.copy(glb_path, raw_path)
optimized = False
optimization_msg = ""
if self.blender.is_available():
optimized_path = self.output_dir / f"asset_optimized_{int(time.time())}.glb"
script_path = self.script_dir / "blender_optimize.py"
success, message = self.blender.optimize(raw_path, optimized_path, script_path)
if success:
final_path = optimized_path
optimized = True
optimization_msg = f"\n✅ {message}"
else:
final_path = raw_path
optimization_msg = f"\n⚠️ Optimization skipped: {message}"
else:
final_path = raw_path
optimization_msg = "\n⚠️ Blender not available, using raw output"
# Step 8: Save to cache
update_progress(0.95, "Saving to cache...")
self.cache.save_to_cache(prompt, quality, final_path)
# Step 9: Cleanup temp files
if image_path.exists():
image_path.unlink()
# Step 10: Create result
elapsed = time.time() - start_time
metadata = AssetMetadata(
prompt=prompt,
quality=quality,
flux_steps=preset.flux_steps,
hunyuan_steps=preset.hunyuan_steps,
file_size_mb=final_path.stat().st_size / 1e6,
generation_time_s=elapsed,
optimized=optimized,
rigged=False
)
status_msg = f"✨ Generated in {elapsed:.1f}s"
status_msg += f"\n📊 {preset.flux_steps} FLUX steps, {preset.hunyuan_steps} Hunyuan3D steps"
status_msg += optimization_msg
update_progress(1.0, "Complete!")
return GenerationResult(
glb_path=final_path,
status_message=status_msg,
metadata=metadata,
cached=False
)
except Exception as e:
print(f"[Pipeline] Error: {e}")
raise