MIRPAMO/tests/gen_test_bvh.py
jing a148b4a543 MIRPAMO v0: local Mixamo — autorig + retarget via headless Blender
- autorig: landmark detection from vertex cloud (markers JSON override),
  mixamorig 22-bone skeleton fit, bone-heat weights + nearest-bone rescue
- retarget: world-space quaternion delta transfer, hip-height scale
  compensation, fuzzy/explicit bone mapping (CMU map bundled)
- smoke: procedural test humanoid + synthetic BVH -> rig -> retarget -> verify
- deploy: push to gitea, pull + smoke on m3ultra and m1ultra

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 01:26:13 +10:00

107 lines
3.9 KiB
Python
Executable File

#!/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")