#!/usr/bin/env python3 """Generate a small synthetic BVH clip (90 frames @ 30fps: hip bob + arm swing + slight walk-forward) for the smoke test. Pure Python — no Blender needed. Bone names are Mixamo-style without the namespace, which exercises the retargeter's fuzzy name matching. Usage: python3 tests/gen_test_bvh.py out/test_clip.bvh """ import math import sys # hierarchy: name, parent, offset (BVH Y-up, meters) JOINTS = [ ("Hips", None, (0, 0.93, 0)), ("Spine", "Hips", (0, 0.10, 0)), ("Spine1", "Spine", (0, 0.10, 0)), ("Spine2", "Spine1", (0, 0.10, 0)), ("Neck", "Spine2", (0, 0.10, 0)), ("Head", "Neck", (0, 0.09, 0)), ("LeftShoulder", "Spine2", (0.06, 0.08, 0)), ("LeftArm", "LeftShoulder", (0.12, 0, 0)), ("LeftForeArm", "LeftArm", (0.26, 0, 0)), ("LeftHand", "LeftForeArm", (0.24, 0, 0)), ("RightShoulder", "Spine2", (-0.06, 0.08, 0)), ("RightArm", "RightShoulder", (-0.12, 0, 0)), ("RightForeArm", "RightArm", (-0.26, 0, 0)), ("RightHand", "RightForeArm", (-0.24, 0, 0)), ("LeftUpLeg", "Hips", (0.09, -0.05, 0)), ("LeftLeg", "LeftUpLeg", (0, -0.40, 0)), ("LeftFoot", "LeftLeg", (0, -0.40, 0)), ("LeftToeBase", "LeftFoot", (0, -0.05, 0.14)), ("RightUpLeg", "Hips", (-0.09, -0.05, 0)), ("RightLeg", "RightUpLeg", (0, -0.40, 0)), ("RightFoot", "RightLeg", (0, -0.40, 0)), ("RightToeBase", "RightFoot", (0, -0.05, 0.14)), ] FRAMES = 90 FPS = 30.0 def children_of(name): return [j for j in JOINTS if j[1] == name] def write_joint(f, joint, depth): name, parent, off = joint ind = " " * depth kw = "ROOT" if parent is None else "JOINT" f.write(f"{ind}{kw} {name}\n{ind}{{\n") f.write(f"{ind} OFFSET {off[0]:.4f} {off[1]:.4f} {off[2]:.4f}\n") if parent is None: f.write(f"{ind} CHANNELS 6 Xposition Yposition Zposition " "Zrotation Xrotation Yrotation\n") else: f.write(f"{ind} CHANNELS 3 Zrotation Xrotation Yrotation\n") kids = children_of(name) if kids: for k in kids: write_joint(f, k, depth + 1) else: f.write(f"{ind} End Site\n{ind} {{\n") f.write(f"{ind} OFFSET 0 0.05 0\n{ind} }}\n") f.write(f"{ind}}}\n") def motion_row(frame): t = frame / FPS phase = 2 * math.pi * t # 1 Hz cycle swing = 35 * math.sin(phase) # degrees, arm/leg swing bob = 0.03 * abs(math.sin(phase)) # hip bob fwd = 0.6 * t # walk forward (BVH +Z) rot = {name: (0.0, 0.0, 0.0) for name, _, _ in JOINTS} # (Z, X, Y) rot["LeftArm"] = (0.0, swing, 0.0) rot["RightArm"] = (0.0, -swing, 0.0) rot["LeftForeArm"] = (0.0, -10 - 10 * abs(math.sin(phase)), 0.0) rot["RightForeArm"] = (0.0, -10 - 10 * abs(math.sin(phase)), 0.0) rot["LeftUpLeg"] = (0.0, -swing, 0.0) rot["RightUpLeg"] = (0.0, swing, 0.0) rot["LeftLeg"] = (0.0, 15 * max(0.0, math.sin(phase)), 0.0) rot["RightLeg"] = (0.0, 15 * max(0.0, -math.sin(phase)), 0.0) rot["Spine"] = (3 * math.sin(phase), 0.0, 0.0) rot["Head"] = (0.0, 5 * math.sin(0.5 * phase), 0.0) vals = [0.0, 0.93 + bob, fwd] # root Xpos Ypos Zpos for name, parent, _ in JOINTS: z, x, y = rot[name] vals.extend([z, x, y]) return vals def main(out_path): with open(out_path, "w") as f: f.write("HIERARCHY\n") write_joint(f, JOINTS[0], 0) f.write(f"MOTION\nFrames: {FRAMES}\n") f.write(f"Frame Time: {1.0 / FPS:.6f}\n") for i in range(FRAMES): f.write(" ".join(f"{v:.4f}" for v in motion_row(i)) + "\n") print(f"[mirpamo] wrote {out_path} ({FRAMES} frames)") if __name__ == "__main__": main(sys.argv[1] if len(sys.argv) > 1 else "out/test_clip.bvh")