| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import random |
| from pathlib import Path |
|
|
| from .config import get_preset |
| from .workflow import UnifiedWorkflow |
| from .check import check_model |
|
|
|
|
| def build_parser() -> argparse.ArgumentParser: |
| p = argparse.ArgumentParser(description="MeshForge V2 unified pipeline runner") |
|
|
| subparsers = p.add_subparsers(dest="command", help="Command to run") |
|
|
| |
| pipeline_parser = subparsers.add_parser("run", help="Run the unified pipeline") |
| pipeline_parser.add_argument("--image", required=True, help="Path to reference portrait image") |
| pipeline_parser.add_argument("--output-dir", required=True, help="Directory for job outputs") |
| pipeline_parser.add_argument( |
| "--preset", default="quality", choices=["preview", "quality", "cinematic"] |
| ) |
| pipeline_parser.add_argument("--seed", type=int, default=None) |
| pipeline_parser.add_argument("--tex-seed", type=int, default=None) |
| pipeline_parser.add_argument("--export-fbx", action="store_true") |
|
|
| |
| check_parser = subparsers.add_parser("check", help="Validate a model for pshuman rendering") |
| check_parser.add_argument("model", help="Path to GLB model file") |
| check_parser.add_argument( |
| "--model-type", |
| choices=["head", "body", "auto"], |
| default="auto", |
| help="Model type (auto-detected from filename if not specified)" |
| ) |
| check_parser.add_argument( |
| "--json", |
| action="store_true", |
| help="Output results as JSON" |
| ) |
|
|
| |
| p.add_argument("--image", help=argparse.SUPPRESS) |
| p.add_argument("--output-dir", help=argparse.SUPPRESS) |
| p.add_argument( |
| "--preset", default="quality", choices=["preview", "quality", "cinematic"], |
| help=argparse.SUPPRESS |
| ) |
| p.add_argument("--seed", type=int, default=None, help=argparse.SUPPRESS) |
| p.add_argument("--tex-seed", type=int, default=None, help=argparse.SUPPRESS) |
| p.add_argument("--export-fbx", action="store_true", help=argparse.SUPPRESS) |
|
|
| return p |
|
|
|
|
| def run_pipeline(args) -> int: |
| cfg = get_preset(args.preset) |
| if args.export_fbx: |
| cfg = type(cfg)(**{**cfg.__dict__, "export_fbx": True}) |
|
|
| seed = args.seed if args.seed is not None else random.randint(0, 2**31 - 1) |
| tex_seed = ( |
| args.tex_seed if args.tex_seed is not None else random.randint(0, 2**31 - 1) |
| ) |
|
|
| wf = UnifiedWorkflow( |
| config=cfg, |
| output_dir=Path(args.output_dir), |
| seed=seed, |
| tex_seed=tex_seed, |
| ) |
| result = wf.run(Path(args.image)) |
|
|
| print(f"status={result['status']}") |
| print(f"manifest={Path(args.output_dir) / 'manifest.json'}") |
| if result["status"] != "completed": |
| print(result.get("error", {}).get("message", "unknown error")) |
| return 1 |
| return 0 |
|
|
|
|
| def run_check(args) -> int: |
| result = check_model(args.model, args.model_type) |
|
|
| if args.json: |
| print(json.dumps(result, indent=2)) |
| else: |
| if result["status"] != "ok": |
| print(f"Error: {result['message']}") |
| return 1 |
|
|
| model_type = "body" if any(r.get("location") for r in result.get("results", [])) else "head" |
| print(f"✓ Model validation passed ({model_type})") |
|
|
| if model_type == "body": |
| print(f"\nAnatomy checks (Y range: {result['y_range'][0]:.3f} to {result['y_range'][1]:.3f}):") |
| for r in result["results"]: |
| print(f" {r['location']:6s} (frac={r['fraction']:.2f}): " |
| f"x_mean={r['x_mean']:7.3f} x_std={r['x_std']:6.3f} " |
| f"z_mean={r['z_mean']:7.3f} z_std={r['z_std']:6.3f} " |
| f"n={r['points']}") |
| else: |
| for r in result["results"]: |
| print(f" {r['mesh']}: centroid={r['centroid']}, " |
| f"y_range=[{r['y_range'][0]:.3f}, {r['y_range'][1]:.3f}]") |
|
|
| return 0 |
|
|
|
|
| def main() -> int: |
| args = build_parser().parse_args() |
|
|
| |
| if args.image and args.output_dir and not args.command: |
| args.command = "run" |
| elif not args.command: |
| build_parser().print_help() |
| return 1 |
|
|
| if args.command == "run": |
| return run_pipeline(args) |
| elif args.command == "check": |
| return run_check(args) |
|
|
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| raise SystemExit(main()) |
|
|