from __future__ import annotations import json import traceback from dataclasses import asdict from datetime import datetime, timezone from pathlib import Path from typing import Any import numpy as np from PIL import Image from .config import WorkflowConfig, ensure_job_dirs from .stages import ( materialize_artifacts, run_rig_facefusion_stage, run_shape_stage, run_texture_stage, ) class UnifiedWorkflow: def __init__( self, config: WorkflowConfig, output_dir: Path, seed: int, tex_seed: int ): self.config = config self.output_dir = output_dir self.seed = int(seed) self.tex_seed = int(tex_seed) self.artifacts_dir, self.logs_dir = ensure_job_dirs(output_dir) def run(self, image_path: Path) -> dict[str, Any]: started = datetime.now(timezone.utc) manifest: dict[str, Any] = { "workflow": "meshforge-v2-unified", "started_at": started.isoformat(), "input": {"image": str(image_path)}, "config": asdict(self.config), "seed": self.seed, "tex_seed": self.tex_seed, "stages": [], "artifacts": {}, "status": "running", } all_outputs: dict[str, Any] = {} try: image_array = np.array(Image.open(image_path).convert("RGB")) shape = run_shape_stage(image_array, self.config, self.seed) all_outputs.update(shape) manifest["stages"].append( {"name": "shape", "status": "ok", "detail": shape["status"]} ) texture = run_texture_stage( shape["shape_glb"], image_array, self.config, self.tex_seed ) all_outputs.update(texture) manifest["stages"].append( {"name": "texture", "status": "ok", "detail": texture["status"]} ) fused = run_rig_facefusion_stage( texture["textured_glb"], image_array, self.config ) all_outputs.update(fused) manifest["stages"].append( { "name": "rig_pshuman_fusion", "status": "ok", "detail": fused["status"], } ) manifest["artifacts"] = materialize_artifacts( all_outputs, self.artifacts_dir ) manifest["status"] = "completed" except Exception as exc: manifest["status"] = "failed" manifest["error"] = { "message": str(exc), "traceback": traceback.format_exc(), } manifest["finished_at"] = datetime.now(timezone.utc).isoformat() self._write_manifest(manifest) return manifest def _write_manifest(self, manifest: dict[str, Any]) -> None: out = self.output_dir / "manifest.json" out.parent.mkdir(parents=True, exist_ok=True) out.write_text(json.dumps(manifest, indent=2), encoding="utf-8")