Fix [#33424] Inadequate bake action.

bake_action tries to make kind of a 'visual keying'... On one side, this is rather stupid when you keep constraints (in this case, keying actual loc/rot/scale transforms, i.e. matrix_basis, is enough, doing more would lead to unexpected behavior with some constraints). On the other one, making a good visual keying of bones is *really* tricky, so now using the new object's convert_space() func to compute that (when the user chooses to remove the constraints).

Incidentally, this greatly simplifies the code of bake_action!
This commit is contained in:
Bastien Montagne
2012-12-28 13:34:19 +00:00
parent 921750149b
commit c587c985e8
2 changed files with 69 additions and 127 deletions

View File

@@ -16,7 +16,7 @@
#
# ##### END GPL LICENSE BLOCK #####
# <pep8-80 compliant>
# <pep8 compliant>
__all__ = (
"bake_action",
@@ -52,7 +52,7 @@ def bake_action(frame_start,
:type do_pose: bool
:arg do_object: Bake objects.
:type do_object: bool
:arg do_constraint_clear: Remove constraints.
:arg do_constraint_clear: Remove constraints (and do 'visual keying').
:type do_constraint_clear: bool
:arg do_clean: Remove redundant keyframes after baking.
:type do_clean: bool
@@ -65,61 +65,20 @@ def bake_action(frame_start,
"""
# -------------------------------------------------------------------------
# Helper Functions
# Helper Functions and vars
def pose_frame_info(obj):
from mathutils import Matrix
def pose_frame_info(obj, do_visual_keying):
matrix = {}
for name, pbone in obj.pose.bones.items():
if do_visual_keying:
# Get the final transform of the bone in its own local space...
matrix[name] = obj.convert_space(pbone, pbone.matrix, 'POSE', 'LOCAL')
else:
matrix[name] = pbone.matrix_basis.copy()
return matrix
info = {}
pose = obj.pose
pose_items = pose.bones.items()
for name, pbone in pose_items:
binfo = {}
bone = pbone.bone
binfo["parent"] = getattr(bone.parent, "name", None)
binfo["bone"] = bone
binfo["pbone"] = pbone
binfo["matrix_local"] = bone.matrix_local.copy()
try:
binfo["matrix_local_inv"] = binfo["matrix_local"].inverted()
except:
binfo["matrix_local_inv"] = Matrix()
binfo["matrix"] = bone.matrix.copy()
binfo["matrix_pose"] = pbone.matrix.copy()
try:
binfo["matrix_pose_inv"] = binfo["matrix_pose"].inverted()
except:
binfo["matrix_pose_inv"] = Matrix()
info[name] = binfo
for name, pbone in pose_items:
binfo = info[name]
binfo_parent = binfo.get("parent", None)
if binfo_parent:
binfo_parent = info[binfo_parent]
matrix = binfo["matrix_pose"]
rest_matrix = binfo["matrix_local"]
if binfo_parent:
matrix = binfo_parent["matrix_pose_inv"] * matrix
rest_matrix = binfo_parent["matrix_local_inv"] * rest_matrix
binfo["matrix_key"] = rest_matrix.inverted() * matrix
return info
def obj_frame_info(obj):
info = {}
# parent = obj.parent
info["matrix_key"] = obj.matrix_local.copy()
return info
def obj_frame_info(obj, do_visual_keying):
return obj.matrix_local.copy() if do_visual_keying else obj.matrix_basis.copy()
# -------------------------------------------------------------------------
# Setup the Context
@@ -127,33 +86,30 @@ def bake_action(frame_start,
# TODO, pass data rather then grabbing from the context!
scene = bpy.context.scene
obj = bpy.context.object
pose = obj.pose
frame_back = scene.frame_current
if pose is None:
if obj.pose is None:
do_pose = False
if do_pose is None and do_object is None:
if not (do_pose or do_object):
return None
pose_info = []
obj_info = []
options = {'INSERTKEY_NEEDED'}
frame_range = range(frame_start, frame_end + 1, frame_step)
# -------------------------------------------------------------------------
# Collect transformations
# could speed this up by applying steps here too...
for f in frame_range:
scene.frame_set(f)
if do_pose:
pose_info.append(pose_frame_info(obj))
pose_info.append(pose_frame_info(obj, do_constraint_clear))
if do_object:
obj_info.append(obj_frame_info(obj))
f += 1
obj_info.append(obj_frame_info(obj, do_constraint_clear))
# -------------------------------------------------------------------------
# Create action
@@ -164,57 +120,44 @@ def bake_action(frame_start,
action = bpy.data.actions.new("Action")
atd.action = action
if do_pose:
pose_items = pose.bones.items()
else:
pose_items = [] # skip
# -------------------------------------------------------------------------
# Apply transformations to action
# pose
for name, pbone in (pose_items if do_pose else ()):
if only_selected and not pbone.bone.select:
continue
if do_pose:
for name, pbone in obj.pose.bones.items():
if only_selected and not pbone.bone.select:
continue
if do_constraint_clear:
while pbone.constraints:
pbone.constraints.remove(pbone.constraints[0])
if do_constraint_clear:
while pbone.constraints:
pbone.constraints.remove(pbone.constraints[0])
# create compatible eulers
euler_prev = None
# create compatible eulers
euler_prev = None
for f in frame_range:
f_step = (f - frame_start) // frame_step
matrix = pose_info[f_step][name]["matrix_key"]
for (f, matrix) in zip(frame_range, pose_info):
pbone.matrix_basis = matrix[name].copy()
# pbone.location = matrix.to_translation()
# pbone.rotation_quaternion = matrix.to_quaternion()
pbone.matrix_basis = matrix
pbone.keyframe_insert("location", -1, f, name, options)
pbone.keyframe_insert("location", -1, f, name)
rotation_mode = pbone.rotation_mode
if rotation_mode == 'QUATERNION':
pbone.keyframe_insert("rotation_quaternion", -1, f, name, options)
elif rotation_mode == 'AXIS_ANGLE':
pbone.keyframe_insert("rotation_axis_angle", -1, f, name, options)
else: # euler, XYZ, ZXY etc
if euler_prev is not None:
euler = pbone.rotation_euler.copy()
euler.make_compatible(euler_prev)
pbone.rotation_euler = euler
euler_prev = euler
del euler
else:
euler_prev = pbone.rotation_euler.copy()
pbone.keyframe_insert("rotation_euler", -1, f, name, options)
rotation_mode = pbone.rotation_mode
if rotation_mode == 'QUATERNION':
pbone.keyframe_insert("rotation_quaternion", -1, f, name)
elif rotation_mode == 'AXIS_ANGLE':
pbone.keyframe_insert("rotation_axis_angle", -1, f, name)
else: # euler, XYZ, ZXY etc
if euler_prev is not None:
euler = pbone.rotation_euler.copy()
euler.make_compatible(euler_prev)
pbone.rotation_euler = euler
euler_prev = euler
del euler
pbone.keyframe_insert("rotation_euler", -1, f, name)
if euler_prev is None:
euler_prev = pbone.rotation_euler.copy()
pbone.keyframe_insert("scale", -1, f, name)
pbone.keyframe_insert("scale", -1, f, name, options)
# object. TODO. multiple objects
if do_object:
@@ -225,18 +168,16 @@ def bake_action(frame_start,
# create compatible eulers
euler_prev = None
for f in frame_range:
matrix = obj_info[(f - frame_start) // frame_step]["matrix_key"]
obj.matrix_local = matrix
for (f, matrix) in zip(frame_range, obj_info):
obj.matrix_basis = matrix[name]
obj.keyframe_insert("location", -1, f)
obj.keyframe_insert("location", -1, f, options)
rotation_mode = obj.rotation_mode
if rotation_mode == 'QUATERNION':
obj.keyframe_insert("rotation_quaternion", -1, f)
obj.keyframe_insert("rotation_quaternion", -1, f, options)
elif rotation_mode == 'AXIS_ANGLE':
obj.keyframe_insert("rotation_axis_angle", -1, f)
obj.keyframe_insert("rotation_axis_angle", -1, f, options)
else: # euler, XYZ, ZXY etc
if euler_prev is not None:
euler = obj.rotation_euler.copy()
@@ -244,15 +185,11 @@ def bake_action(frame_start,
obj.rotation_euler = euler
euler_prev = euler
del euler
obj.keyframe_insert("rotation_euler", -1, f)
if euler_prev is None:
else:
euler_prev = obj.rotation_euler.copy()
obj.keyframe_insert("rotation_euler", -1, f, options)
obj.keyframe_insert("scale", -1, f)
scene.frame_set(frame_back)
obj.keyframe_insert("scale", -1, f, options)
# -------------------------------------------------------------------------
# Clean
@@ -271,4 +208,6 @@ def bake_action(frame_start,
else:
i += 1
scene.frame_set(frame_back)
return action