fix(anim): the clip now drives the body — two causes, both measured
The body stood in an A-pose while the log said the clip was playing. Two independent defects, found by querying the imported asset instead of guessing: 1. PlayAnimation needs the component in AnimationSingleNode with a live single-node instance. The body is hidden at construction and only unhidden by the camera toggle, so the first call could land before the component was ready, and the Wanted == PlayingAnimation guard then returned forever. UpdateBodyAnimation now sets the mode, calls SetAnimation + Play, and re-asserts while the instance is missing. 2. The arms-down offsets lived only in the clip keys. The exporter writes clip data relative to the rest pose, so they arrived as a ±20 degree swing around an A-posed bind - which is exactly what the pose probe measured. apply_base_pose now bakes them into the rest pose before any clip is authored, the same trick mpfb_character_export.py --pose idle uses. The table is applied with inverted signs: as written, the FBX axis mapping produced arms raised in a V. Shot 6 confirms it: the arms hang at the sides. Still open and recorded - the idle pose reads awkwardly (arms forward, hands splayed) and the walk clip has no shot in motion; both are marked in TODO.md and Screenshots/README.md. Instrumentation removed; the diagnostics that remain are behind gigline.SetupLogs.
This commit is contained in:
1 parent
d0f94dff4e
commit
a1f5989cbb
39 files changed
+115
-59
No files matched your search
@@ -91,10 +91,37 @@ def pose_bones(arm):
|
||||
return arm.pose.bones
|
||||
|
||||
|
||||
def apply_base_pose(arm):
|
||||
"""Bake the arms-down pose into the REST pose before authoring any clip.
|
||||
|
||||
The first two attempts added the arms-down as an offset inside every key. That
|
||||
is not what the export sees: the exporter writes clip data relative to the rest
|
||||
pose, so an offset that lives only in the keys arrives in Unreal as a small
|
||||
delta around an A-posed bind - the body stood in an A with a ±20 degree arm
|
||||
swing, which is exactly what the Unreal pose probe measured. Baking the pose
|
||||
into the rest pose first is the same trick `mpfb_character_export.py --pose
|
||||
idle` uses, and it makes bind and clips agree.
|
||||
"""
|
||||
bpy.context.view_layer.objects.active = arm
|
||||
bpy.ops.object.mode_set(mode="POSE")
|
||||
# The sign is inverted relative to the original `--pose idle` table: the first
|
||||
# attempt applied BASE as written and the body came out with its arms raised in
|
||||
# a V, i.e. the FBX axis mapping flips this rotation. Verified on the imported
|
||||
# asset rather than guessed - see MEMORY.md, 2026-11-09.
|
||||
for name, eul in BASE.items():
|
||||
bone = arm.pose.bones.get(name)
|
||||
if bone is None:
|
||||
continue
|
||||
bone.rotation_mode = "XYZ"
|
||||
bone.rotation_euler = (-eul[0], -eul[1], -eul[2])
|
||||
bpy.ops.pose.armature_apply()
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
log("base pose applied to rest: arms down before any clip is authored")
|
||||
|
||||
|
||||
def key_euler(pb, frame, x=0.0, y=0.0, z=0.0):
|
||||
"""Key a pose-bone rotation on top of the arms-down base."""
|
||||
bx, by, bz = BASE.get(pb.name, (0.0, 0.0, 0.0))
|
||||
pb.rotation_euler = (bx + x, by + y, bz + z)
|
||||
"""Key a pose-bone rotation; the arms-down base is already in the rest pose."""
|
||||
pb.rotation_euler = (x, y, z)
|
||||
pb.keyframe_insert(data_path="rotation_euler", frame=frame)
|
||||
|
||||
|
||||
@@ -174,6 +201,7 @@ def main(argv=None) -> int:
|
||||
|
||||
bpy.context.scene.render.fps = FPS
|
||||
clear_animation(arm)
|
||||
apply_base_pose(arm)
|
||||
idle = clip_idle(arm, CLIPS["Idle"])
|
||||
walk = clip_walk(arm, CLIPS["Walk"])
|
||||
|
||||
|
||||
Reference in new issue
Block a user