Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
98fa2c5
feat(anim): stabilized twist transport in the direction retarget (#857)
fernandotonon Jul 13, 2026
23123de
feat(anim): v5 flow-matching t2m model — canonical data, in-graph sam…
fernandotonon Jul 13, 2026
8cede98
feat(anim): foot-contact detection + IK pinning post-pass (#856)
fernandotonon Jul 13, 2026
4c2589b
chore(scripts): HF upload script for the v5 t2m flow model (#858)
fernandotonon Jul 13, 2026
843a0d5
test(anim): fix twist tests — humanoid-gate rig + cap-before-gain exp…
fernandotonon Jul 13, 2026
fe3d5ba
Merge twist-test fixes from feat/t2m-twist-857
fernandotonon Jul 13, 2026
26e3472
Merge twist-test fixes from feat/t2m-twist-857
fernandotonon Jul 13, 2026
faa7f4f
Merge #858/#840 flow model
fernandotonon Jul 14, 2026
9c54de7
fix(anim): quality follow-up — smooth-bake post-pass, tightened foot-…
fernandotonon Jul 14, 2026
4793240
feat(scripts): posture gates on t2m training windows (#837 quality fo…
fernandotonon Jul 17, 2026
9c8570a
fix(scripts): default t2m export guidance to 1.0 (#837)
fernandotonon Jul 17, 2026
86fbd17
feat(anim): v6 curation-grade t2m model + posture best-of-16 scorer (…
fernandotonon Jul 19, 2026
66ac50f
fix(anim): stride-directionality gate fixes sideways t2m walk (#837)
fernandotonon Jul 19, 2026
977e2c8
fix(anim): condition model clips — facing, arm-spread, foot-pitch (#837)
fernandotonon Jul 20, 2026
3bbc405
fix(anim): correct the yaw-flip quaternion in conditionModelClip (#837)
fernandotonon Jul 20, 2026
744c93f
fix(anim): root-yaw the whole body rigidly — stop reversing knee hing…
fernandotonon Jul 20, 2026
2cb0773
Revert "fix(anim): root-yaw the whole body rigidly — stop reversing k…
fernandotonon Jul 20, 2026
764ab9d
Revert "fix(anim): correct the yaw-flip quaternion in conditionModelC…
fernandotonon Jul 20, 2026
8fb0b66
Revert "fix(anim): condition model clips — facing, arm-spread, foot-p…
fernandotonon Jul 20, 2026
510a06a
test(anim): --apply-canonical retarget parity harness (#837)
fernandotonon Jul 20, 2026
025148f
fix(anim): twist caps apply to model clips only (#837 retarget)
fernandotonon Jul 20, 2026
da23151
fix(anim): rigid-yaw model clips to face forward (#837)
fernandotonon Jul 20, 2026
27c1a7a
Revert "fix(anim): rigid-yaw model clips to face forward (#837)"
fernandotonon Jul 20, 2026
ef212e9
Merge remote-tracking branch 'origin/master' into feat/t2m-epic-837
fernandotonon Jul 20, 2026
dc6cc2f
fix(anim): address PR #904 review comments (#837)
fernandotonon Jul 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion qml/PropertiesPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -8727,7 +8727,7 @@ Rectangle {
// skewing every new clip is exactly the bug this fixes).
var gr = AnimationControlController.generateMotion(
genPromptIn.text, 0.0, useModelChk.checked,
0.0)
0.0, footPinChk.checked)
if (gr && gr.animation) {
lastGeneratedAnim = gr.animation
// Point the slider at the fresh clip at a neutral 0
Expand Down Expand Up @@ -8770,6 +8770,29 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
}
}
// #856: foot-contact cleanup — ON by default (detect ground-contact
// spans + IK-pin the feet so they plant instead of skating).
Row {
spacing: 6
Rectangle {
id: footPinChk
property bool checked: true
width: 14; height: 14; radius: 2
anchors.verticalCenter: parent.verticalCenter
color: checked ? PropertiesPanelController.highlightColor
: PropertiesPanelController.inputColor
border.color: PropertiesPanelController.borderColor
Text { anchors.centerIn: parent; visible: parent.checked
text: "✓"; color: "white"; font.pixelSize: 10 }
MouseArea { anchors.fill: parent
onClicked: footPinChk.checked = !footPinChk.checked }
}
Text {
text: "Pin feet (contact cleanup)"
color: PropertiesPanelController.textColor; font.pixelSize: 10
anchors.verticalCenter: parent.verticalCenter
}
}
// ── Arm space (#854): Mixamo-style widen/tuck post-process ────────
// Targets `armSpaceAnim` — the last generated clip by default, or
// any animation the user picks via the per-row arm button below.
Expand Down
82 changes: 82 additions & 0 deletions scripts/build-motion-library-v5.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
# animation-name (normalized) → action. Order matters: first hit wins.
KEYWORDS = [
("tpose", None), ("t_pose", None), ("bind", None), ("rest", None),
# compound names FIRST — "walk"/"run" below would swallow them
("crouchwalk", "crouch"), ("crouchrun", "crouch"),
("sneakwalk", "sneak"), ("sneakrun", "sneak"),
("walk", "walk"), ("run", "run"), ("jog", "run"), ("sprint", "run"),
("idle", "idle"), ("stand", "idle"), ("breath", "idle"),
("jump", "jump"), ("hop", "jump"), ("leap", "jump"),
Expand Down Expand Up @@ -238,6 +241,85 @@ def axis(role):
elif action in HORIZONTAL_OK:
upness = 0.7

# Placeholder-arm gate: game walk/run cycles are often authored with
# STATIC (T-pose) arms — legs stride while the arms stick straight out.
# Retargeted, that reads as a broken clip. If the legs carry real motion
# but BOTH arms are near-frozen, drop the take.
ARM_ROLES = (7, 8, 11, 12)
LEG_ROLES = (15, 16, 19, 20)
if len(quats) > 1 and action not in ("idle", "sit", "sleep"):
# Energy RELATIVE to the chest (role 2): world quats inherit the
# torso's sway, so locally-frozen arms still show world energy.
# rel = chest^-1 * bone isolates the limb's own motion.
def rel(f, r):
c = quats[f][2]
b = quats[f][r]
ci = [-c[0], -c[1], -c[2], c[3]]
return qmul(ci, b)
def group_energy(roles):
tot = 0.0
for f in range(1, len(quats)):
tot += sum(quat_angle(rel(f - 1, r), rel(f, r))
for r in roles) / len(roles)
return tot / (len(quats) - 1)
legs_e = group_energy(LEG_ROLES)
arms_e = group_energy(ARM_ROLES)
if legs_e > 0.004 and arms_e < 0.0015:
return 0.0, (f"static placeholder arms (rel arm energy "
f"{arms_e:.4f} vs legs {legs_e:.4f})")

# Neck-up gate: some rigs export the neck/head bone with an INVERTED
# axis — the extracted direction points down for the whole clip and the
# retargeted head renders thrown back / buried in the torso. For any
# upright action, the neck chain's animated direction must stay roughly
# up.
if action not in HORIZONTAL_OK and rest_world and rest_dir:
for r in (3, 4, 5): # neck, neck1, head
d = vnorm(rest_dir[r])
if d is None:
continue
q = rest_world[r]
a_s = qrot([-q[0], -q[1], -q[2], q[3]], d)
tot = sum(qrot(quats[f][r], a_s)[1] for f in range(len(quats)))
if tot / max(1, len(quats)) < 0.3:
return 0.0, (f"neck/head direction not upright (role {r} "
f"mean up-dot {tot / max(1, len(quats)):.2f}) — "
"inverted neck axis, head renders thrown back")
break # first resolvable is enough

# Horizontal-arm gate for plain locomotion: zombie-shamble / T-pose-armed
# walk cycles hold the upper arms near-horizontal for the whole clip
# (mean |up-component| of the upper-arm direction ~0 vs 0.6-0.95 on a
# natural walk). Retargeted onto a generic character under a generic
# "walk" prompt they read broken — drop them from locomotion actions.
RSHO, LSHO = 7, 11
if action in ("walk", "run", "march") and rest_world and rest_dir:
downdots = []
for r in (RSHO, LSHO):
d = vnorm(rest_dir[r])
if d is None:
continue
q = rest_world[r]
a_s = qrot([-q[0], -q[1], -q[2], q[3]], d)
tot = 0.0
for f in range(len(quats)):
tot += qrot(quats[f][r], a_s)[1] # SIGNED up-component
downdots.append(tot / max(1, len(quats)))
if not downdots:
# Arm roles UNRESOLVED: the retarget leaves the target's arms at
# its bind pose — a literal T-pose held for the whole clip.
return 0.0, (f"arm roles unresolved — target arms would freeze "
f"in the bind T-pose during {action}")
# judge each arm separately, on the SIGNED up-component: a natural
# locomotion arm hangs (mean Y ~ -0.6..-0.95). Horizontal zombie arms
# (~0) AND raised arms (+) both read broken — an abs() gate passed a
# straight-up arm as if it were hanging (several corpus rigs carry a
# per-bone axis inversion that renders one arm skyward).
if max(downdots) > -0.25:
return 0.0, (f"arm(s) not hanging (upper-arm signed up-dots "
f"{[round(u, 2) for u in downdots]}) — zombie/"
f"T-pose/raised style, not a generic {action}")

# Energy band: below = a pose, way above = spasm/mis-mapped.
lo, hi, cap = min_energy * 2.0, 0.10, 0.20
if mean_energy <= lo:
Expand Down
Loading
Loading