2011-09-22 22:51:54 +00:00
|
|
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU General Public License
|
|
|
|
# as published by the Free Software Foundation; either version 2
|
|
|
|
# of the License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
#
|
|
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
|
2012-12-28 13:34:19 +00:00
|
|
|
# <pep8 compliant>
|
2011-09-22 22:51:54 +00:00
|
|
|
|
|
|
|
__all__ = (
|
|
|
|
"bake_action",
|
2017-09-10 16:58:04 +10:00
|
|
|
"bake_action_objects",
|
|
|
|
|
|
|
|
"bake_action_iter",
|
|
|
|
"bake_action_objects_iter",
|
|
|
|
)
|
2011-09-22 22:51:54 +00:00
|
|
|
|
|
|
|
import bpy
|
|
|
|
|
|
|
|
|
2017-09-10 14:30:03 +10:00
|
|
|
def bake_action(
|
|
|
|
obj,
|
2017-09-10 16:58:04 +10:00
|
|
|
*,
|
|
|
|
action, frames,
|
2017-09-14 17:03:40 +10:00
|
|
|
**kwargs
|
2017-09-10 16:58:04 +10:00
|
|
|
):
|
|
|
|
"""
|
|
|
|
: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`
|
|
|
|
"""
|
2019-01-10 13:11:48 +11:00
|
|
|
if not (kwargs.get("do_pose") or kwargs.get("do_object")):
|
2017-09-10 16:58:04 +10:00
|
|
|
return None
|
|
|
|
|
|
|
|
action, = bake_action_objects(
|
|
|
|
[(obj, action)],
|
2019-01-10 13:11:48 +11:00
|
|
|
frames=frames,
|
2017-09-10 16:58:04 +10:00
|
|
|
**kwargs,
|
|
|
|
)
|
|
|
|
return action
|
|
|
|
|
|
|
|
|
|
|
|
def bake_action_objects(
|
|
|
|
object_action_pairs,
|
|
|
|
*,
|
|
|
|
frames,
|
2017-09-14 17:03:40 +10:00
|
|
|
**kwargs
|
2017-09-10 16:58:04 +10:00
|
|
|
):
|
|
|
|
"""
|
|
|
|
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,
|
2017-09-14 17:03:40 +10:00
|
|
|
**kwargs
|
2017-09-10 16:58:04 +10:00
|
|
|
):
|
|
|
|
"""
|
|
|
|
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)
|
2019-05-17 10:40:44 +02:00
|
|
|
bpy.context.view_layer.update()
|
2017-09-10 16:58:04 +10:00
|
|
|
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,
|
2017-09-10 14:30:03 +10:00
|
|
|
only_selected=False,
|
|
|
|
do_pose=True,
|
|
|
|
do_object=True,
|
|
|
|
do_visual_keying=True,
|
|
|
|
do_constraint_clear=False,
|
|
|
|
do_parents_clear=False,
|
2017-09-14 17:03:40 +10:00
|
|
|
do_clean=False
|
2017-09-10 14:30:03 +10:00
|
|
|
):
|
2011-09-22 22:51:54 +00:00
|
|
|
"""
|
2017-09-10 16:58:04 +10:00
|
|
|
An coroutine that bakes action for a single object.
|
2011-09-22 22:51:54 +00:00
|
|
|
|
2017-09-10 14:30:03 +10:00
|
|
|
:arg obj: Object to bake.
|
|
|
|
:type obj: :class:`bpy.types.Object`
|
2017-09-10 16:58:04 +10:00
|
|
|
: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
|
2016-09-16 11:49:42 +02:00
|
|
|
:arg only_selected: Only bake selected bones.
|
2011-09-22 22:51:54 +00:00
|
|
|
:type only_selected: bool
|
|
|
|
:arg do_pose: Bake pose channels.
|
|
|
|
:type do_pose: bool
|
|
|
|
:arg do_object: Bake objects.
|
|
|
|
:type do_object: bool
|
2013-07-04 23:52:02 +00:00
|
|
|
:arg do_visual_keying: Use the final transformations for baking ('visual keying')
|
|
|
|
:type do_visual_keying: bool
|
|
|
|
:arg do_constraint_clear: Remove constraints after baking.
|
2011-09-22 22:51:54 +00:00
|
|
|
:type do_constraint_clear: bool
|
2013-04-11 08:42:25 +00:00
|
|
|
:arg do_parents_clear: Unparent after baking objects.
|
|
|
|
:type do_parents_clear: bool
|
2011-09-22 22:51:54 +00:00
|
|
|
:arg do_clean: Remove redundant keyframes after baking.
|
|
|
|
:type do_clean: bool
|
2011-09-26 15:39:15 +00:00
|
|
|
|
2011-09-22 22:51:54 +00:00
|
|
|
:return: an action or None
|
|
|
|
:rtype: :class:`bpy.types.Action`
|
|
|
|
"""
|
|
|
|
# -------------------------------------------------------------------------
|
2012-12-28 13:34:19 +00:00
|
|
|
# Helper Functions and vars
|
2011-09-22 22:51:54 +00:00
|
|
|
|
2017-11-20 01:32:03 +13:00
|
|
|
# Note: BBONE_PROPS is a list so we can preserve the ordering
|
|
|
|
BBONE_PROPS = [
|
|
|
|
'bbone_curveinx', 'bbone_curveoutx',
|
|
|
|
'bbone_curveiny', 'bbone_curveouty',
|
|
|
|
'bbone_rollin', 'bbone_rollout',
|
2019-05-09 02:56:30 +02:00
|
|
|
'bbone_scaleinx', 'bbone_scaleoutx',
|
|
|
|
'bbone_scaleiny', 'bbone_scaleouty',
|
2017-11-20 01:32:03 +13:00
|
|
|
'bbone_easein', 'bbone_easeout'
|
|
|
|
]
|
|
|
|
|
2015-02-17 07:16:59 +11:00
|
|
|
def pose_frame_info(obj):
|
2012-12-28 13:34:19 +00:00
|
|
|
matrix = {}
|
2017-11-20 01:32:03 +13:00
|
|
|
bbones = {}
|
2012-12-28 13:34:19 +00:00
|
|
|
for name, pbone in obj.pose.bones.items():
|
|
|
|
if do_visual_keying:
|
|
|
|
# Get the final transform of the bone in its own local space...
|
2018-09-12 11:50:35 +02:00
|
|
|
matrix[name] = obj.convert_space(pose_bone=pbone, matrix=pbone.matrix,
|
|
|
|
from_space='POSE', to_space='LOCAL')
|
2012-12-28 13:34:19 +00:00
|
|
|
else:
|
|
|
|
matrix[name] = pbone.matrix_basis.copy()
|
2017-11-20 01:32:03 +13:00
|
|
|
|
|
|
|
# Bendy Bones
|
|
|
|
if pbone.bone.bbone_segments > 1:
|
2018-07-03 06:27:53 +02:00
|
|
|
bbones[name] = {bb_prop: getattr(pbone, bb_prop) for bb_prop in BBONE_PROPS}
|
2017-11-20 01:32:03 +13:00
|
|
|
return matrix, bbones
|
2011-09-22 22:51:54 +00:00
|
|
|
|
2013-04-11 08:42:25 +00:00
|
|
|
if do_parents_clear:
|
2015-02-17 07:16:59 +11:00
|
|
|
if do_visual_keying:
|
|
|
|
def obj_frame_info(obj):
|
|
|
|
return obj.matrix_world.copy()
|
|
|
|
else:
|
|
|
|
def obj_frame_info(obj):
|
|
|
|
parent = obj.parent
|
|
|
|
matrix = obj.matrix_basis
|
|
|
|
if parent:
|
2019-02-14 13:51:14 +00:00
|
|
|
return parent.matrix_world @ matrix
|
2015-02-17 07:16:59 +11:00
|
|
|
else:
|
|
|
|
return matrix.copy()
|
2015-02-03 15:41:34 +01:00
|
|
|
else:
|
2015-02-17 07:16:59 +11:00
|
|
|
if do_visual_keying:
|
|
|
|
def obj_frame_info(obj):
|
|
|
|
parent = obj.parent
|
|
|
|
matrix = obj.matrix_world
|
|
|
|
if parent:
|
2019-02-14 13:51:14 +00:00
|
|
|
return parent.matrix_world.inverted_safe() @ matrix
|
2015-02-17 07:16:59 +11:00
|
|
|
else:
|
|
|
|
return matrix.copy()
|
|
|
|
else:
|
|
|
|
def obj_frame_info(obj):
|
|
|
|
return obj.matrix_basis.copy()
|
2011-09-22 22:51:54 +00:00
|
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Setup the Context
|
|
|
|
|
2012-12-28 13:34:19 +00:00
|
|
|
if obj.pose is None:
|
2011-09-22 22:51:54 +00:00
|
|
|
do_pose = False
|
|
|
|
|
2012-12-28 13:34:19 +00:00
|
|
|
if not (do_pose or do_object):
|
2017-09-10 16:58:04 +10:00
|
|
|
raise Exception("Pose and object baking is disabled, no action needed")
|
2011-09-22 22:51:54 +00:00
|
|
|
|
|
|
|
pose_info = []
|
|
|
|
obj_info = []
|
|
|
|
|
2012-12-28 13:34:19 +00:00
|
|
|
options = {'INSERTKEY_NEEDED'}
|
|
|
|
|
2011-09-22 22:51:54 +00:00
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Collect transformations
|
|
|
|
|
2017-09-10 16:58:04 +10:00
|
|
|
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
|
|
|
|
|
2011-09-22 22:51:54 +00:00
|
|
|
if do_pose:
|
2017-11-20 01:32:03 +13:00
|
|
|
pose_info.append((frame, *pose_frame_info(obj)))
|
2011-09-22 22:51:54 +00:00
|
|
|
if do_object:
|
2017-09-10 16:58:04 +10:00
|
|
|
obj_info.append((frame, obj_frame_info(obj)))
|
2011-09-22 22:51:54 +00:00
|
|
|
|
2015-09-08 03:59:03 +10:00
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Clean (store initial data)
|
|
|
|
if do_clean and action is not None:
|
|
|
|
clean_orig_data = {fcu: {p.co[1] for p in fcu.keyframe_points} for fcu in action.fcurves}
|
|
|
|
else:
|
|
|
|
clean_orig_data = {}
|
|
|
|
|
2011-09-22 22:51:54 +00:00
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Create action
|
|
|
|
|
2012-02-08 04:37:37 +00:00
|
|
|
# in case animation data hasn't been created
|
2011-09-22 22:51:54 +00:00
|
|
|
atd = obj.animation_data_create()
|
|
|
|
if action is None:
|
|
|
|
action = bpy.data.actions.new("Action")
|
2016-07-24 03:18:40 +02:00
|
|
|
|
|
|
|
# Leave tweak mode before trying to modify the action (T48397)
|
|
|
|
if atd.use_tweak_mode:
|
|
|
|
atd.use_tweak_mode = False
|
|
|
|
|
2011-09-22 22:51:54 +00:00
|
|
|
atd.action = action
|
|
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Apply transformations to action
|
|
|
|
|
|
|
|
# pose
|
2012-12-28 13:34:19 +00:00
|
|
|
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])
|
|
|
|
|
|
|
|
# create compatible eulers
|
|
|
|
euler_prev = None
|
|
|
|
|
2017-11-20 01:32:03 +13:00
|
|
|
for (f, matrix, bbones) in pose_info:
|
2012-12-28 13:34:19 +00:00
|
|
|
pbone.matrix_basis = matrix[name].copy()
|
|
|
|
|
2018-09-12 11:50:35 +02:00
|
|
|
pbone.keyframe_insert("location", index=-1, frame=f, group=name, options=options)
|
2012-12-28 13:34:19 +00:00
|
|
|
|
|
|
|
rotation_mode = pbone.rotation_mode
|
|
|
|
if rotation_mode == 'QUATERNION':
|
2018-09-12 11:50:35 +02:00
|
|
|
pbone.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name, options=options)
|
2012-12-28 13:34:19 +00:00
|
|
|
elif rotation_mode == 'AXIS_ANGLE':
|
2018-09-12 11:50:35 +02:00
|
|
|
pbone.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name, options=options)
|
2012-12-28 13:34:19 +00:00
|
|
|
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()
|
2018-09-12 11:50:35 +02:00
|
|
|
pbone.keyframe_insert("rotation_euler", index=-1, frame=f, group=name, options=options)
|
2012-12-28 13:34:19 +00:00
|
|
|
|
2018-09-12 11:50:35 +02:00
|
|
|
pbone.keyframe_insert("scale", index=-1, frame=f, group=name, options=options)
|
2011-09-22 22:51:54 +00:00
|
|
|
|
2017-11-20 01:32:03 +13:00
|
|
|
# Bendy Bones
|
|
|
|
if pbone.bone.bbone_segments > 1:
|
|
|
|
bbone_shape = bbones[name]
|
|
|
|
for bb_prop in BBONE_PROPS:
|
|
|
|
# update this property with value from bbone_shape, then key it
|
|
|
|
setattr(pbone, bb_prop, bbone_shape[bb_prop])
|
2018-09-12 11:50:35 +02:00
|
|
|
pbone.keyframe_insert(bb_prop, index=-1, frame=f, group=name, options=options)
|
2017-11-20 01:32:03 +13:00
|
|
|
|
2011-09-22 22:51:54 +00:00
|
|
|
# object. TODO. multiple objects
|
|
|
|
if do_object:
|
|
|
|
if do_constraint_clear:
|
|
|
|
while obj.constraints:
|
|
|
|
obj.constraints.remove(obj.constraints[0])
|
|
|
|
|
2012-08-25 12:37:15 +00:00
|
|
|
# create compatible eulers
|
|
|
|
euler_prev = None
|
|
|
|
|
2017-09-10 16:58:04 +10:00
|
|
|
for (f, matrix) in obj_info:
|
2013-03-28 19:33:14 +00:00
|
|
|
name = "Action Bake" # XXX: placeholder
|
2013-01-21 02:40:51 +00:00
|
|
|
obj.matrix_basis = matrix
|
2011-09-22 22:51:54 +00:00
|
|
|
|
2018-09-12 11:50:35 +02:00
|
|
|
obj.keyframe_insert("location", index=-1, frame=f, group=name, options=options)
|
2011-09-22 22:51:54 +00:00
|
|
|
|
|
|
|
rotation_mode = obj.rotation_mode
|
|
|
|
if rotation_mode == 'QUATERNION':
|
2018-09-12 11:50:35 +02:00
|
|
|
obj.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name, options=options)
|
2011-09-22 22:51:54 +00:00
|
|
|
elif rotation_mode == 'AXIS_ANGLE':
|
2018-09-12 11:50:35 +02:00
|
|
|
obj.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name, options=options)
|
2011-09-22 22:51:54 +00:00
|
|
|
else: # euler, XYZ, ZXY etc
|
2012-08-25 12:37:15 +00:00
|
|
|
if euler_prev is not None:
|
|
|
|
euler = obj.rotation_euler.copy()
|
|
|
|
euler.make_compatible(euler_prev)
|
|
|
|
obj.rotation_euler = euler
|
|
|
|
euler_prev = euler
|
|
|
|
del euler
|
2012-12-28 13:34:19 +00:00
|
|
|
else:
|
2012-08-25 12:37:15 +00:00
|
|
|
euler_prev = obj.rotation_euler.copy()
|
2018-09-12 11:50:35 +02:00
|
|
|
obj.keyframe_insert("rotation_euler", index=-1, frame=f, group=name, options=options)
|
2012-08-25 12:37:15 +00:00
|
|
|
|
2018-09-12 11:50:35 +02:00
|
|
|
obj.keyframe_insert("scale", index=-1, frame=f, group=name, options=options)
|
2011-09-22 22:51:54 +00:00
|
|
|
|
2013-04-11 08:42:25 +00:00
|
|
|
if do_parents_clear:
|
|
|
|
obj.parent = None
|
|
|
|
|
2011-09-22 22:51:54 +00:00
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Clean
|
|
|
|
|
|
|
|
if do_clean:
|
|
|
|
for fcu in action.fcurves:
|
2015-09-08 03:59:03 +10:00
|
|
|
fcu_orig_data = clean_orig_data.get(fcu, set())
|
|
|
|
|
2011-09-22 22:51:54 +00:00
|
|
|
keyframe_points = fcu.keyframe_points
|
|
|
|
i = 1
|
2015-09-08 03:59:03 +10:00
|
|
|
while i < len(keyframe_points) - 1:
|
|
|
|
val = keyframe_points[i].co[1]
|
|
|
|
|
|
|
|
if val in fcu_orig_data:
|
|
|
|
i += 1
|
|
|
|
continue
|
|
|
|
|
2011-09-22 22:51:54 +00:00
|
|
|
val_prev = keyframe_points[i - 1].co[1]
|
|
|
|
val_next = keyframe_points[i + 1].co[1]
|
|
|
|
|
|
|
|
if abs(val - val_prev) + abs(val - val_next) < 0.0001:
|
|
|
|
keyframe_points.remove(keyframe_points[i])
|
|
|
|
else:
|
|
|
|
i += 1
|
|
|
|
|
2017-09-10 16:58:04 +10:00
|
|
|
yield action
|