File size: 5,641 Bytes
8f1bcd9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
cli.py
Command-line interface for rig_retarget.

Usage:
    python -m rig_retarget.cli \\
        --source  walk.bvh \\
        --dest    unirig_character.glb \\
        --mapping radical2unirig.json \\
        --output  animated_character.glb \\
        [--fps 30] [--start 0] [--frames 100] [--step 1]

    # Calculate corrections only (no transfer):
    python -m rig_retarget.cli --calc-corrections \\
        --source walk.bvh --dest unirig_character.glb \\
        --mapping mymap.json
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path


def _parse_args(argv=None):
    p = argparse.ArgumentParser(
        prog="rig_retarget",
        description="Retarget animation from BVH/glTF source onto UniRig/glTF destination.",
    )
    p.add_argument("--source", required=True, help="Source animation file (.bvh or .glb/.gltf)")
    p.add_argument("--dest", required=True, help="Destination skeleton file (.glb/.gltf, UniRig output)")
    p.add_argument("--mapping", required=True, help="KeeMap-compatible JSON bone mapping file")
    p.add_argument("--output", default=None, help="Output animated .glb (default: dest_retargeted.glb)")
    p.add_argument("--fps", type=float, default=30.0)
    p.add_argument("--start", type=int, default=0, help="Start frame index (0-based)")
    p.add_argument("--frames", type=int, default=None, help="Number of frames to transfer (default: all)")
    p.add_argument("--step", type=int, default=1, help="Keyframe every N source frames")
    p.add_argument("--skin", type=int, default=0, help="Skin index in destination glTF")
    p.add_argument("--calc-corrections", action="store_true",
                   help="Auto-calculate bone corrections and update the mapping JSON, then exit.")
    p.add_argument("--verbose", action="store_true")
    return p.parse_args(argv)


def main(argv=None) -> None:
    args = _parse_args(argv)

    from .io.mapping import load_mapping, save_mapping, KeeMapSettings
    from .io.gltf_io import load_gltf, write_gltf_animation
    from .retarget import (
        calc_all_corrections, transfer_animation,
    )

    # -----------------------------------------------------------------------
    # Load mapping
    # -----------------------------------------------------------------------
    print(f"[*] Loading mapping  : {args.mapping}")
    settings, bone_items = load_mapping(args.mapping)

    # Override settings from CLI args
    settings.start_frame_to_apply = args.start
    settings.keyframe_every_n_frames = args.step

    # -----------------------------------------------------------------------
    # Load source animation
    # -----------------------------------------------------------------------
    src_path = Path(args.source)
    print(f"[*] Loading source   : {src_path}")

    if src_path.suffix.lower() == ".bvh":
        from .io.bvh import load_bvh
        src_anim = load_bvh(str(src_path))
        if args.verbose:
            print(f"    BVH: {src_anim.num_frames} frames, "
                  f"{src_anim.frame_time*1000:.1f} ms/frame, "
                  f"{len(src_anim.armature.pose_bones)} joints")
    elif src_path.suffix.lower() in (".glb", ".gltf"):
        # glTF source — load skeleton only; animation reading is TODO
        raise NotImplementedError(
            "glTF source animation reading is not yet implemented. "
            "Use a BVH file for the source animation."
        )
    else:
        print(f"[!] Unsupported source format: {src_path.suffix}", file=sys.stderr)
        sys.exit(1)

    if args.frames is not None:
        settings.number_of_frames_to_apply = args.frames
    else:
        settings.number_of_frames_to_apply = src_anim.num_frames - args.start

    # -----------------------------------------------------------------------
    # Load destination skeleton
    # -----------------------------------------------------------------------
    dst_path = Path(args.dest)
    print(f"[*] Loading dest     : {dst_path}")
    dst_arm = load_gltf(str(dst_path), skin_index=args.skin)
    if args.verbose:
        print(f"    Skeleton: {len(dst_arm.pose_bones)} bones")

    # -----------------------------------------------------------------------
    # Auto-correct pass (optional)
    # -----------------------------------------------------------------------
    if args.calc_corrections:
        print("[*] Calculating bone corrections ...")
        src_anim.apply_frame(args.start)
        calc_all_corrections(bone_items, src_anim.armature, dst_arm, settings)
        save_mapping(args.mapping, settings, bone_items)
        print(f"[*] Updated mapping saved → {args.mapping}")
        return

    # -----------------------------------------------------------------------
    # Transfer
    # -----------------------------------------------------------------------
    print(f"[*] Transferring {settings.number_of_frames_to_apply} frames "
          f"(start={settings.start_frame_to_apply}, step={settings.keyframe_every_n_frames}) ...")
    keyframes = transfer_animation(src_anim, dst_arm, bone_items, settings)
    print(f"[*] Generated {len(keyframes)} keyframes")

    # -----------------------------------------------------------------------
    # Write output
    # -----------------------------------------------------------------------
    out_path = args.output or str(dst_path.with_name(dst_path.stem + "_retargeted.glb"))
    print(f"[*] Writing output   : {out_path}")
    write_gltf_animation(str(dst_path), dst_arm, keyframes, out_path, fps=args.fps, skin_index=args.skin)
    print("[✓] Done")


if __name__ == "__main__":
    main()