"Retargeting free Mixamo mocap onto a custom rig"
- Mixamo clip on your custom rig looks hunched forward in idle (spine/chest/neck stacked on top of hips yaw).
- Walk/run stride measures absurd (e.g. ~70 m/s) after you "fixed" scale.
- Ambient idles pop at the loop seam; locomotion clips almost loop but double a frame.
- Retarget script wipes your armature mid-build because Mixamo import resets the scene.
Mixamo and your rig have different rest poses. Copying world rotations double-counts every ancestor. The fix is rest-delta + parent-relative transfer.
Classic forward-hunch: feeding the Poser a raw world delta means Hips yaw is applied again into Spine/Chest/Neck/Head on top of the pelvis's own motion.
Scale trap: Mixamo imports at 0.01 armature-object scale. matrix_world already folds that in (pose worlds come out in meters). transform_apply(scale=True) leaves LOCATION fcurves in the pre-apply unit → Hips translation blows up ~100×. Rotations are unaffected — only root-motion / hip-bob translation.
Rest-delta + parent-relative
Per source bone at frame f:
dq_src = M_anim @ M_rest^-1 # world rotation away from THAT skeleton's rest
dq_src = F @ dq_src @ F^-1 # reframe Mixamo world → your armature world
Q = dq_parent^-1 @ dq_bone # PARENT-RELATIVE — not the raw world deltaFrame quat F for the common facing mismatch: Mixamo faces −Y / left +X, your rig faces +X / left +Y → F = Rz(+90). (needs verification) if your rig facing differs — measure once.
Feed Q into your keyer as a world-delta-from-rest (or a key_world_q helper that keeps quaternion hemisphere continuity: negate if dot(prev, new) < 0).
Do not feed raw world deltas into a poser that composes bone_world = dq_parent @ Q @ rest_world — that is the hunch.
Scale
# Leave Mixamo armature object scale ON
# Read everything through matrix_world — never transform_apply(scale=True)Loop / root motion
| Clip type | Loop | Root motion |
|---|---|---|
| Locomotion (walk/run) | Mixamo usually loops (frame 1 == N) — drop the duplicate seam frame | Strip Hips world XY (runtime moves the char); keep vertical bob × our_hip_z / mixamo_hip_z |
| Ambient idle | Pick lowest-seam sub-window (~5–8°/bone residual is OK) | Keep full XYZ scaled, window-relative for sit/collapse |
| Soft seam | Let vmdl fade_in/fade_out + Sequence.Blending hide residual | — |
Depicted speed handoff (don't re-time the clip):
depicted_mps = Mixamo_XY_travel * hip_scale / window_secondsUse that const for PlaybackRate = actualSpeed / depicted_mps.
Build order
Preload / cache Mixamo quats BEFORE building your rig. Importing a take often read_factory_settings and wipes the scene; cached mathutils quats survive. Pattern: preload_all() at the top of each rig script, then build.
Export with fbx-export-recipe.
Rest-delta cancels incompatible bind poses: you transfer motion away from rest, not absolute orientations. Parent-relative deltas stop ancestor rotations from being applied twice down the chain. Leaving Mixamo's object scale alone keeps matrix_world and LOCATION fcurves in the same unit, so hip travel stays meters instead of centimetres×100.