- 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>
107 lines
3.9 KiB
Python
Executable File
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")
|