# ##### 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 ##### # __all__ = ( "bake_action", ) import bpy def bake_action(frame_start, frame_end, frame_step=1, only_selected=False, do_pose=True, do_object=True, do_constraint_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. :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 only_selected: Only bake selected data. :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_constraint_clear: Remove constraints. :type do_constraint_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 def pose_frame_info(obj): from mathutils import 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 # ------------------------------------------------------------------------- # Setup the Context # 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: do_pose = False if do_pose is None and do_object is None: return None pose_info = [] obj_info = [] 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)) if do_object: obj_info.append(obj_frame_info(obj)) f += 1 # ------------------------------------------------------------------------- # 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") 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_constraint_clear: while pbone.constraints: pbone.constraints.remove(pbone.constraints[0]) # 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"] # pbone.location = matrix.to_translation() # pbone.rotation_quaternion = matrix.to_quaternion() pbone.matrix_basis = matrix pbone.keyframe_insert("location", -1, f, name) 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) # 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 in frame_range: matrix = obj_info[(f - frame_start) // frame_step]["matrix_key"] obj.matrix_local = matrix obj.keyframe_insert("location", -1, f) rotation_mode = obj.rotation_mode if rotation_mode == 'QUATERNION': obj.keyframe_insert("rotation_quaternion", -1, f) elif rotation_mode == 'AXIS_ANGLE': obj.keyframe_insert("rotation_axis_angle", -1, f) 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 obj.keyframe_insert("rotation_euler", -1, f) if euler_prev is None: euler_prev = obj.rotation_euler.copy() obj.keyframe_insert("scale", -1, f) scene.frame_set(frame_back) # ------------------------------------------------------------------------- # Clean if do_clean: for fcu in action.fcurves: keyframe_points = fcu.keyframe_points i = 1 while i < len(fcu.keyframe_points) - 1: val_prev = keyframe_points[i - 1].co[1] val_next = keyframe_points[i + 1].co[1] val = keyframe_points[i].co[1] if abs(val - val_prev) + abs(val - val_next) < 0.0001: keyframe_points.remove(keyframe_points[i]) else: i += 1 return action