File size: 4,534 Bytes
6a809ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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 command (default)
    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 command
    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"
    )

    # Legacy support: if --image and --output-dir are provided without a command
    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()

    # Legacy support: if --image and --output-dir provided, treat as pipeline run
    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())