Bake Action: operate on selected objects

Previously only the active object was used.

Use coroutines to support baking frames for multiple objects at once,
without having to playback the animation multiple times.
This commit is contained in:
Campbell Barton 2017-09-10 16:58:04 +10:00
parent a4a59d578c
commit 6d2cd1719b
2 changed files with 120 additions and 51 deletions

@ -20,17 +20,102 @@
__all__ = (
"bake_action",
)
"bake_action_objects",
"bake_action_iter",
"bake_action_objects_iter",
)
import bpy
# XXX visual keying is actually always considered as True in this code...
def bake_action(
obj,
frame_start,
frame_end,
frame_step=1,
*,
action, frames,
**kwargs,
):
"""
:arg obj: Object to bake.
:type obj: :class:`bpy.types.Object`
:arg action: An action to bake the data into, or None for a new action
to be created.
:type action: :class:`bpy.types.Action` or None
:arg frames: Frames to bake.
:type frames: iterable of int
:return: an action or None
:rtype: :class:`bpy.types.Action`
"""
if not (do_pose or do_object):
return None
action, = bake_action_objects(
[(obj, action)],
frames,
**kwargs,
)
return action
def bake_action_objects(
object_action_pairs,
*,
frames,
**kwargs,
):
"""
A version of :func:`bake_action_objects_iter` that takes frames and returns the output.
:arg frames: Frames to bake.
:type frames: iterable of int
:return: A sequence of Action or None types (aligned with `object_action_pairs`)
:rtype: sequence of :class:`bpy.types.Action`
"""
iter = bake_action_objects_iter(object_action_pairs, **kwargs)
iter.send(None)
for frame in frames:
iter.send(frame)
return iter.send(None)
def bake_action_objects_iter(
object_action_pairs,
**kwargs,
):
"""
An coroutine that bakes actions for multiple objects.
:arg object_action_pairs: Sequence of object action tuples,
action is the destination for the baked data. When None a new action will be created.
:type object_action_pairs: Sequence of (:class:`bpy.types.Object`, :class:`bpy.types.Action`)
"""
scene = bpy.context.scene
frame_back = scene.frame_current
iter_all = tuple(
bake_action_iter(obj, action=action, **kwargs)
for (obj, action) in object_action_pairs
)
for iter in iter_all:
iter.send(None)
while True:
frame = yield None
if frame is None:
break
scene.frame_set(frame)
scene.update()
for iter in iter_all:
iter.send(frame)
scene.frame_set(frame_back)
yield tuple(iter.send(None) for iter in iter_all)
# XXX visual keying is actually always considered as True in this code...
def bake_action_iter(
obj,
*,
action,
only_selected=False,
do_pose=True,
do_object=True,
@ -38,21 +123,15 @@ def bake_action(
do_constraint_clear=False,
do_parents_clear=False,
do_clean=False,
action=None,
):
"""
Return an image from the file path with options to search multiple paths
and return a placeholder if its not found.
An coroutine that bakes action for a single object.
:arg obj: Object to bake.
:type obj: :class:`bpy.types.Object`
:arg frame_start: First frame to bake.
:type frame_start: int
:arg frame_end: Last frame to bake.
:type frame_end: int
:arg frame_step: Frame step.
:type frame_step: int
:arg action: An action to bake the data into, or None for a new action
to be created.
:type action: :class:`bpy.types.Action` or None
:arg only_selected: Only bake selected bones.
:type only_selected: bool
:arg do_pose: Bake pose channels.
@ -67,14 +146,10 @@ def bake_action(
:type do_parents_clear: bool
:arg do_clean: Remove redundant keyframes after baking.
:type do_clean: bool
:arg action: An action to bake the data into, or None for a new action
to be created.
:type action: :class:`bpy.types.Action` or None
:return: an action or None
:rtype: :class:`bpy.types.Action`
"""
# -------------------------------------------------------------------------
# Helper Functions and vars
@ -116,33 +191,32 @@ def bake_action(
# -------------------------------------------------------------------------
# Setup the Context
# TODO, pass data rather then grabbing from the context!
scene = bpy.context.scene
frame_back = scene.frame_current
if obj.pose is None:
do_pose = False
if not (do_pose or do_object):
return None
raise Exception("Pose and object baking is disabled, no action needed")
pose_info = []
obj_info = []
options = {'INSERTKEY_NEEDED'}
frame_range = range(frame_start, frame_end + 1, frame_step)
# -------------------------------------------------------------------------
# Collect transformations
for f in frame_range:
scene.frame_set(f)
scene.update()
while True:
# Caller is responsible for setting the frame and updating the scene.
frame = yield None
# Signal we're done!
if frame is None:
break
if do_pose:
pose_info.append(pose_frame_info(obj))
pose_info.append((frame, pose_frame_info(obj)))
if do_object:
obj_info.append(obj_frame_info(obj))
obj_info.append((frame, obj_frame_info(obj)))
# -------------------------------------------------------------------------
# Clean (store initial data)
@ -181,7 +255,7 @@ def bake_action(
# create compatible eulers
euler_prev = None
for (f, matrix) in zip(frame_range, pose_info):
for (f, matrix) in pose_info:
pbone.matrix_basis = matrix[name].copy()
pbone.keyframe_insert("location", -1, f, name, options)
@ -213,7 +287,7 @@ def bake_action(
# create compatible eulers
euler_prev = None
for (f, matrix) in zip(frame_range, obj_info):
for (f, matrix) in obj_info:
name = "Action Bake" # XXX: placeholder
obj.matrix_basis = matrix
@ -264,6 +338,4 @@ def bake_action(
else:
i += 1
scene.frame_set(frame_back)
return action
yield action

@ -198,7 +198,7 @@ class ANIM_OT_keying_set_export(Operator):
class BakeAction(Operator):
"""Bake object/pose loc/scale/rotation animation to a new action"""
"""Bake all selected objects loc/scale/rotation animation to an action"""
bl_idname = "nla.bake"
bl_label = "Bake Action"
bl_options = {'REGISTER', 'UNDO'}
@ -222,7 +222,7 @@ class BakeAction(Operator):
default=1,
)
only_selected = BoolProperty(
name="Only Selected",
name="Only Selected Bones",
description="Only key selected bones (Pose baking only)",
default=True,
)
@ -259,18 +259,16 @@ class BakeAction(Operator):
def execute(self, context):
from bpy_extras import anim_utils
objects = context.selected_editable_objects
object_action_pairs = (
[(obj, getattr(obj.animation_data, "action", None)) for obj in objects]
if self.use_current_action else
[(obj, None) for obj in objects]
)
obj = context.object
action = None
if self.use_current_action:
if obj.animation_data:
action = obj.animation_data.action
action = anim_utils.bake_action(
obj,
self.frame_start,
self.frame_end,
frame_step=self.step,
actions = anim_utils.bake_action_objects(
object_action_pairs,
frames=range(self.frame_start, self.frame_end + 1, self.step),
only_selected=self.only_selected,
do_pose='POSE' in self.bake_types,
do_object='OBJECT' in self.bake_types,
@ -278,10 +276,9 @@ class BakeAction(Operator):
do_constraint_clear=self.clear_constraints,
do_parents_clear=self.clear_parents,
do_clean=True,
action=action,
)
if action is None:
if not any(actions):
self.report({'INFO'}, "Nothing to bake")
return {'CANCELLED'}