123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- # ##### 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 #####
- # <pep8 compliant>
- __all__ = (
- "bake_action",
- "bake_action_objects",
- "bake_action_iter",
- "bake_action_objects_iter",
- )
- import bpy
- def bake_action(
- obj,
- *,
- 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 (kwargs.get("do_pose") or kwargs.get("do_object")):
- return None
- action, = bake_action_objects(
- [(obj, action)],
- frames=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)
- bpy.context.view_layer.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,
- do_visual_keying=True,
- do_constraint_clear=False,
- do_parents_clear=False,
- do_clean=False
- ):
- """
- An coroutine that bakes action for a single object.
- :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 only_selected: Only bake selected bones.
- :type only_selected: bool
- :arg do_pose: Bake pose channels.
- :type do_pose: bool
- :arg do_object: Bake objects.
- :type do_object: bool
- :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.
- :type do_constraint_clear: bool
- :arg do_parents_clear: Unparent after baking objects.
- :type do_parents_clear: bool
- :arg do_clean: Remove redundant keyframes after baking.
- :type do_clean: bool
- :return: an action or None
- :rtype: :class:`bpy.types.Action`
- """
- # -------------------------------------------------------------------------
- # Helper Functions and vars
- # 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',
- 'bbone_scaleinx', 'bbone_scaleoutx',
- 'bbone_scaleiny', 'bbone_scaleouty',
- 'bbone_easein', 'bbone_easeout'
- ]
- def pose_frame_info(obj):
- matrix = {}
- bbones = {}
- 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(pose_bone=pbone, matrix=pbone.matrix,
- from_space='POSE', to_space='LOCAL')
- else:
- matrix[name] = pbone.matrix_basis.copy()
- # Bendy Bones
- if pbone.bone.bbone_segments > 1:
- bbones[name] = {bb_prop: getattr(pbone, bb_prop) for bb_prop in BBONE_PROPS}
- return matrix, bbones
- if do_parents_clear:
- 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:
- return parent.matrix_world @ matrix
- else:
- return matrix.copy()
- else:
- if do_visual_keying:
- def obj_frame_info(obj):
- parent = obj.parent
- matrix = obj.matrix_world
- if parent:
- return parent.matrix_world.inverted_safe() @ matrix
- else:
- return matrix.copy()
- else:
- def obj_frame_info(obj):
- return obj.matrix_basis.copy()
- # -------------------------------------------------------------------------
- # Setup the Context
- if obj.pose is None:
- do_pose = False
- if not (do_pose or do_object):
- raise Exception("Pose and object baking is disabled, no action needed")
- pose_info = []
- obj_info = []
- options = {'INSERTKEY_NEEDED'}
- # -------------------------------------------------------------------------
- # Collect transformations
- 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((frame, *pose_frame_info(obj)))
- if do_object:
- obj_info.append((frame, obj_frame_info(obj)))
- # -------------------------------------------------------------------------
- # 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 = {}
- # -------------------------------------------------------------------------
- # Create action
- # in case animation data hasn't been created
- atd = obj.animation_data_create()
- if action is None:
- action = bpy.data.actions.new("Action")
- # Leave tweak mode before trying to modify the action (T48397)
- if atd.use_tweak_mode:
- atd.use_tweak_mode = False
- atd.action = action
- # -------------------------------------------------------------------------
- # Apply transformations to action
- # pose
- 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
- for (f, matrix, bbones) in pose_info:
- pbone.matrix_basis = matrix[name].copy()
- pbone.keyframe_insert("location", index=-1, frame=f, group=name, options=options)
- rotation_mode = pbone.rotation_mode
- if rotation_mode == 'QUATERNION':
- pbone.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name, options=options)
- elif rotation_mode == 'AXIS_ANGLE':
- pbone.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name, options=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", index=-1, frame=f, group=name, options=options)
- pbone.keyframe_insert("scale", index=-1, frame=f, group=name, options=options)
- # 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])
- pbone.keyframe_insert(bb_prop, index=-1, frame=f, group=name, options=options)
- # object. TODO. multiple objects
- if do_object:
- if do_constraint_clear:
- while obj.constraints:
- obj.constraints.remove(obj.constraints[0])
- # create compatible eulers
- euler_prev = None
- for (f, matrix) in obj_info:
- name = "Action Bake" # XXX: placeholder
- obj.matrix_basis = matrix
- obj.keyframe_insert("location", index=-1, frame=f, group=name, options=options)
- rotation_mode = obj.rotation_mode
- if rotation_mode == 'QUATERNION':
- obj.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name, options=options)
- elif rotation_mode == 'AXIS_ANGLE':
- obj.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name, options=options)
- else: # euler, XYZ, ZXY etc
- 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
- else:
- euler_prev = obj.rotation_euler.copy()
- obj.keyframe_insert("rotation_euler", index=-1, frame=f, group=name, options=options)
- obj.keyframe_insert("scale", index=-1, frame=f, group=name, options=options)
- if do_parents_clear:
- obj.parent = None
- # -------------------------------------------------------------------------
- # Clean
- if do_clean:
- for fcu in action.fcurves:
- fcu_orig_data = clean_orig_data.get(fcu, set())
- keyframe_points = fcu.keyframe_points
- i = 1
- while i < len(keyframe_points) - 1:
- val = keyframe_points[i].co[1]
- if val in fcu_orig_data:
- i += 1
- continue
- 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
- yield action
|