ラベル Blender Asset の投稿を表示しています。 すべての投稿を表示
ラベル Blender Asset の投稿を表示しています。 すべての投稿を表示

2026年7月20日月曜日

How to transfer root bone pose animation to the animation of the entire armature 5.x

 import bpy

import math


from mathutils import Matrix

from bpy_extras import anim_utils



# ============================================================

# Settings

# ============================================================


ROOT_BONE_NAME = "Root"


# If True:

#   Bake a keyframe on every frame between the first and last

#   root bone keyframe.

#   This preserves interpolation more accurately, but creates

#   many additional keyframes.

#

# If False:

#   Process only frames that already contain root bone keys.

#   This behaves similarly to the original version of the script.

BAKE_EVERY_FRAME = False


# Delete the root bone after the transfer is complete.

DELETE_ROOT_BONE = True



# ============================================================

# Blender 5.x / Legacy Action Compatibility

# ============================================================


def get_action_fcurves(animated_id):

    """

    Return all F-Curves from the Action currently assigned

    to the specified animated datablock.


    Blender 4.x and earlier:

        action.fcurves


    Blender 5.x:

        Action Slot

            -> Channelbag

                -> F-Curves

    """


    animation_data = animated_id.animation_data


    if animation_data is None:

        return []


    action = animation_data.action


    if action is None:

        return []


    # Legacy Blender API.

    if hasattr(action, "fcurves"):

        return list(action.fcurves)


    # Blender 5.x layered Action API.

    action_slot = getattr(animation_data, "action_slot", None)


    # Normally animation_data.action_slot is available.

    # As a fallback, use the only slot if the Action has exactly one.

    if action_slot is None:

        slots = getattr(action, "slots", None)


        if slots is not None and len(slots) == 1:

            action_slot = slots[0]


    if action_slot is None:

        print(

            f"Warning: No Action slot was found for "

            f"'{animated_id.name}'."

        )

        return []


    channelbag = anim_utils.action_get_channelbag_for_slot(

        action,

        action_slot

    )


    if channelbag is None:

        return []


    return list(channelbag.fcurves)



def collect_keyframes(fcurves, data_path_prefix=None):

    """

    Collect all unique keyframe times from a list of F-Curves.


    If data_path_prefix is specified, only F-Curves whose

    data paths start with that prefix are included.

    """


    frames = set()


    for fcurve in fcurves:

        if (

            data_path_prefix is not None

            and not fcurve.data_path.startswith(data_path_prefix)

        ):

            continue


        for keyframe in fcurve.keyframe_points:

            frames.add(float(keyframe.co.x))


    return sorted(frames)



def set_scene_frame(scene, frame):

    """

    Set the current timeline position with subframe support,

    then update the dependency graph.

    """


    whole_frame = math.floor(frame)

    subframe = frame - whole_frame


    scene.frame_set(whole_frame, subframe=subframe)

    bpy.context.view_layer.update()



# ============================================================

# Main

# ============================================================


scene = bpy.context.scene


# Store the original timeline position so it can be restored later.

original_frame = scene.frame_current + scene.frame_subframe


# Use the currently active object as the source armature.

armature = bpy.context.active_object


if armature is None or armature.type != 'ARMATURE':

    raise ValueError("Please select an armature object.")


bpy.context.view_layer.objects.active = armature

armature.select_set(True)


# Switch to Object Mode before reading transforms.

if armature.mode != 'OBJECT':

    bpy.ops.object.mode_set(mode='OBJECT')



# ============================================================

# Validate Root Bone

# ============================================================


root_pose_bone = armature.pose.bones.get(ROOT_BONE_NAME)


if root_pose_bone is None:

    raise ValueError(

        f"Root bone '{ROOT_BONE_NAME}' was not found."

    )


root_data_bone = armature.data.bones.get(ROOT_BONE_NAME)


if root_data_bone is None:

    raise ValueError(

        f"Root data bone '{ROOT_BONE_NAME}' was not found."

    )



# ============================================================

# Store Root Bone Rest Transform

# ============================================================


armature_matrix_world = armature.matrix_world.copy()

root_bone_matrix_rest = root_data_bone.matrix_local.copy()


root_bone_matrix_world_rest = (

    armature_matrix_world

    @ root_bone_matrix_rest

)


(

    root_bone_default_loc,

    root_bone_default_rot,

    root_bone_default_scale

) = root_bone_matrix_world_rest.decompose()


inverse_root_bone_default_rot = (

    root_bone_default_rot.inverted()

)



# ============================================================

# Collect IK Targets

# ============================================================


ik_targets = []


for pose_bone in armature.pose.bones:

    for constraint in pose_bone.constraints:


        if constraint.type != 'IK':

            continue


        target = constraint.target


        if target is None:

            continue


        # Only process external IK target objects parented

        # to the selected armature.

        if target.parent != armature:

            continue


        # Prevent duplicate targets from being added.

        if target not in ik_targets:

            ik_targets.append(target)



# ============================================================

# Store Original IK Target World Positions

# ============================================================


ik_target_keyframes = {}


for target in ik_targets:

    target_fcurves = get_action_fcurves(target)

    target_frames = collect_keyframes(target_fcurves)


    ik_target_keyframes[target.name] = {}


    for frame in target_frames:

        set_scene_frame(scene, frame)


        ik_target_keyframes[target.name][frame] = (

            target.matrix_world.translation.copy()

        )



# ============================================================

# Get Armature Action

# ============================================================


animation_data = armature.animation_data


if animation_data is None:

    raise ValueError("Animation data was not found.")


action = animation_data.action


if action is None:

    raise ValueError("No Action is assigned to the armature.")


armature_fcurves = get_action_fcurves(armature)


if not armature_fcurves:

    raise ValueError(

        "No F-Curves were found in the armature Action."

    )



# ============================================================

# Collect Root Bone Keyframes

# ============================================================


root_data_path_prefix = (

    f'pose.bones["{ROOT_BONE_NAME}"]'

)


root_keyframes = collect_keyframes(

    armature_fcurves,

    root_data_path_prefix

)


if not root_keyframes:

    raise ValueError(

        f"No animation keys were found for "

        f"root bone '{ROOT_BONE_NAME}'."

    )


# Optionally bake every integer frame between

# the first and last root bone keyframe.

if BAKE_EVERY_FRAME:

    first_frame = math.floor(min(root_keyframes))

    last_frame = math.ceil(max(root_keyframes))


    sample_frames = [

        float(frame)

        for frame in range(first_frame, last_frame + 1)

    ]


else:

    sample_frames = root_keyframes



# ============================================================

# Sample Root Bone Transforms

#

# At this stage, transforms are only recorded.

# No keys are written to the armature object yet.

#

# This prevents newly inserted armature object keyframes

# from affecting transform evaluation on later frames.

# ============================================================


transform_data = {}


for frame in sample_frames:

    set_scene_frame(scene, frame)


    # PoseBone.matrix is expressed in armature object space.

    root_bone_matrix = root_pose_bone.matrix.copy()


    # Convert the root bone transform to world space.

    global_matrix = (

        armature.matrix_world

        @ root_bone_matrix

    )


    global_loc, global_rot, global_scale = (

        global_matrix.decompose()

    )


    transform_data[frame] = {

        "location": global_loc.copy(),

        "rotation": global_rot.copy(),

        "scale": global_scale.copy(),

    }



# ============================================================

# Bake the Root Bone Transform to the Armature Object

# ============================================================


bpy.ops.object.mode_set(mode='OBJECT')


# Quaternion rotation avoids Euler angle discontinuities

# during the transfer.

armature.rotation_mode = 'QUATERNION'


translation_matrix_to_default = Matrix.Translation(

    -root_bone_default_loc

)


for frame in sample_frames:

    data = transform_data[frame]


    loc = data["location"]

    rot = data["rotation"]

    scale = data["scale"]


    # Remove the root bone's rest rotation.

    corrected_rot = (

        rot

        @ inverse_root_bone_default_rot

    )


    translation_matrix_current = Matrix.Translation(loc)

    rotation_matrix = corrected_rot.to_matrix().to_4x4()


    # Apply the same transform order as the original script:

    #   1. Translate relative to the root bone's rest location.

    #   2. Apply the corrected root bone rotation.

    #   3. Move to the current root bone location.

    final_matrix = (

        translation_matrix_current

        @ rotation_matrix

        @ translation_matrix_to_default

    )


    final_loc, final_rot, _ = final_matrix.decompose()


    armature.location = final_loc

    armature.rotation_quaternion = final_rot

    armature.scale = scale


    armature.keyframe_insert(

        data_path="location",

        frame=frame

    )


    armature.keyframe_insert(

        data_path="rotation_quaternion",

        frame=frame

    )


    armature.keyframe_insert(

        data_path="scale",

        frame=frame

    )



# ============================================================

# Preserve IK Target World Positions

# ============================================================


for target in ik_targets:

    saved_frames = ik_target_keyframes.get(target.name, {})


    for frame, initial_world_position in saved_frames.items():

        set_scene_frame(scene, frame)


        # Convert the stored world-space position back into

        # the target's armature-relative local space using

        # the armature transform at the current frame.

        local_position = (

            armature.matrix_world.inverted()

            @ initial_world_position

        )


        target.location = local_position


        target.keyframe_insert(

            data_path="location",

            frame=frame

        )



# ============================================================

# Delete Root Bone

# ============================================================


if DELETE_ROOT_BONE:

    bpy.context.view_layer.objects.active = armature

    armature.select_set(True)


    bpy.ops.object.mode_set(mode='EDIT')


    edit_root_bone = armature.data.edit_bones.get(

        ROOT_BONE_NAME

    )


    if edit_root_bone is not None:

        armature.data.edit_bones.remove(edit_root_bone)


    bpy.ops.object.mode_set(mode='OBJECT')



# ============================================================

# Restore Original Timeline Position

# ============================================================


set_scene_frame(scene, original_frame)


print(

    f"Finished. Root bone animation has been transferred "

    f"to the armature object '{armature.name}'."

)


2024年5月28日火曜日

How to transfer root bone pose animation to the animation of the entire armature Old

#RootPoseToArmature

import bpy

from mathutils import Matrix, Vector


# Note: This script will automatically set the armature's rotation mode to QUATERNION.

# The root bone will be deleted after the script runs.

# Make sure to back up your project before running this script.


# Set the name of the root bone

root_bone_name = "Root"  # Set the name of the root bone


# Get the currently selected object

armature = bpy.context.active_object

if not armature or armature.type != 'ARMATURE':

    raise ValueError("Please select an armature object.")


bpy.context.view_layer.objects.active = armature


# Switch to Object Mode to get the default pose

bpy.ops.object.mode_set(mode='OBJECT')


# Get the default global location and rotation of the root bone

root_bone = armature.pose.bones.get(root_bone_name)

if not root_bone:

    raise ValueError(f"Root bone '{root_bone_name}' not found.")


# Get the armature's rest pose

armature_matrix_world = armature.matrix_world.copy()

root_bone_matrix_rest = armature.data.bones[root_bone_name].matrix_local.copy()

root_bone_matrix_world_rest = armature_matrix_world @ root_bone_matrix_rest

root_bone_default_loc, root_bone_default_rot, _ = root_bone_matrix_world_rest.decompose()

inverse_root_bone_default_rot = root_bone_default_rot.inverted()


# Get the IK targets

ik_targets = []

for bone in armature.pose.bones:

    for constraint in bone.constraints:

        if constraint.type == 'IK' and constraint.target:

            target = constraint.target

            if target.parent == armature:

                ik_targets.append(target)


# Record the global positions of the IK targets for each keyframe

ik_target_keyframes = {}

for target in ik_targets:

    ik_target_keyframes[target.name] = {}

    if target.animation_data and target.animation_data.action:

        for fcurve in target.animation_data.action.fcurves:

            for keyframe in fcurve.keyframe_points:

                frame = int(keyframe.co.x)

                bpy.context.scene.frame_set(frame)

                ik_target_keyframes[target.name][frame] = target.matrix_world.translation.copy()


# Get the animation data

animation_data = armature.animation_data

if not animation_data:

    raise ValueError("Animation data not found.")


action = animation_data.action

if not action:

    raise ValueError("Action not found in animation data.")


# Process the animation data and apply the root bone transform to the armature

transform_data = {'location': [], 'rotation_quaternion': [], 'scale': []}

for fcurve in action.fcurves:

    if fcurve.data_path.startswith(f"pose.bones[\"{root_bone_name}\"]"):

        for keyframe in fcurve.keyframe_points:

            frame = int(keyframe.co.x)


            # Move to the frame to set the keyframe

            bpy.context.scene.frame_set(frame)


            # Get the pose position of the root bone

            root_bone_matrix = root_bone.matrix


            # Calculate the global transform of the root bone

            global_matrix = armature.matrix_world @ root_bone_matrix


            # Decompose the matrix into location, rotation, and scale

            global_loc, global_rot, global_scale = global_matrix.decompose()


            # Save the transform data in the dictionary

            transform_data['location'].append((frame, global_loc))

            transform_data['rotation_quaternion'].append((frame, global_rot))

            transform_data['scale'].append((frame, global_scale))


# Switch to Object Mode to apply transformations

bpy.ops.object.mode_set(mode='OBJECT')


# Automatically set the armature's rotation mode to QUATERNION

armature.rotation_mode = 'QUATERNION'


# Apply the transforms to the armature object and insert keyframes

for (frame, loc), (_, rot) in zip(transform_data['location'], transform_data['rotation_quaternion']):

    # Correct the rotation for the root bone's default rotation

    corrected_rot = (rot @ inverse_root_bone_default_rot)

    

    # Create the translation matrices for the default and current locations

    translation_matrix_to_default = Matrix.Translation(-root_bone_default_loc)

    translation_matrix_back = Matrix.Translation(root_bone_default_loc)

    translation_matrix_current = Matrix.Translation(loc)

    

    # Create the rotation matrix for the corrected rotation

    rotation_matrix = corrected_rot.to_matrix().to_4x4()

    

    # Combine the matrices: 

    # 1. Translate to default position

    # 2. Apply rotation

    # 3. Translate to the current location

    final_matrix = translation_matrix_current @ rotation_matrix @ translation_matrix_to_default

    

    # Decompose the final matrix into location and rotation

    final_loc, final_rot, _ = final_matrix.decompose()

    

    # Apply the final location and rotation to the armature

    armature.location = final_loc

    armature.rotation_quaternion = final_rot

    armature.keyframe_insert(data_path="location", frame=frame)

    armature.keyframe_insert(data_path="rotation_quaternion", frame=frame)


for frame, scale in transform_data['scale']:

    armature.scale = scale

    armature.keyframe_insert(data_path="scale", frame=frame)


# Update the IK target positions for each frame

for target in ik_targets:

    for frame, initial_position in ik_target_keyframes[target.name].items():

        bpy.context.scene.frame_set(frame)

        # Convert the initial position to the armature's local space at the current frame

        local_pos = armature.matrix_world.inverted() @ initial_position

        target.location = local_pos

        target.keyframe_insert(data_path="location", frame=frame)


# Delete the root bone

bpy.ops.object.mode_set(mode='EDIT')

bones = armature.data.edit_bones

root_bone = bones.get(root_bone_name)

if root_bone:

    bones.remove(root_bone)


# Restore the current frame

bpy.context.scene.frame_set(bpy.context.scene.frame_current)


2024年4月22日月曜日

Efficient Use of Blender Assets

-How to bake actions in Blender

http://eizouasobi.blogspot.com/2024/04/how-to-bake-actions-in-blender.html


-How to split actions in Blender

http://eizouasobi.blogspot.com/2024/04/how-to-split-actions-in-blender.html


-How to use in game engine

https://eizouasobi.blogspot.com/2019/01/how-to-use-in-game-engine.html

-How to change the scale
http://eizouasobi.blogspot.com/2019/01/how-to-change-scale.html

Formatting Animation Name Files for Use with ActionMaking.py

If the animation number files attached to your assets are not in the correct format for use with the ActionMaking.py script, please use the FormatAnimationNamesAnimColonNum.py script to adjust them.


This script performs the following tasks:

-Removes empty lines and trailing colons: This step cleans up unnecessary formatting that may interfere with processing.

-Replaces spaces with colons: This adjustment is made for lines where the frame range and animation name are separated by spaces instead of colons, ensuring consistency in data format.

-Checks and adjusts the position of name and frame data: This part verifies whether the line format is 'frames:name' or 'name:frames' and adjusts accordingly.

-Processes frame data: Depending on the number of listed frame ranges, the script selects the appropriate frames (the middle ones if there are four frames, the first two if there are three).

For example, a file with lines like:

50-80-110-140:WalkForward:

IdleToTrot:3850-3880-3910

will be changed to:

WalkForward:80-110

IdleToTrot:3850-3880

If there are any extraneous comments or notes that cannot be adjusted by this script, please remove them before running the script.

2024年4月20日土曜日

How to split actions in Blender

One of the ways to further utilize animation assets in Blender is to split actions. Here is a step-by-step guide on how to split actions.


Step 1: Baking the Animation

First, bake the animation in Blender. This involves solidifying the animation data and making any necessary adjustments such as removing root motion. For detailed instructions, please refer to this link.


Step 2: Preparing a Text File for Action Splitting

Prepare a text file in the following format to split actions:

WalkForward:10-40

WalkRight:50-80

If the format of the animation names in the text file differs (e.g., "10-40 WalkForward"), please adjust the format using the script provided at this link.


Step 3: Using the ActionMakingV5.py Script

Open the  script from Blender’s text editor. Set the animation_data to text prepared in Step 2, open the NLA editor then push down action and set the strip.name to the name of the strip you want to split.

Step 4: Running the Script

Select the target armature and run the Run Script. This will appropriately split the specified action based on the text, making each action available as a separate item.

ActionMakingV5.py

import bpy


def split_action_strip_manual_steps(animation_data):

    """

    Splits an NLA strip named "Action" into multiple strips by following the exact manual steps:

    1. Select frame range

    2. Bake with VisualKeying=True and OnlySelectedBones=False

    3. Push Down Action to create a new strip

    4. Rename the strip

    

    Makes sure to prioritize the Action strip by moving its track to the top each iteration.

    

    Args:

        animation_data: A string of animation segments in format "Name:start-end"

                        with each segment on a new line

    """

    # Parse the animation data

    animations = []

    for line in animation_data.strip().split('\n'):

        if line.strip():

            name, frame_range = line.split(':')

            start, end = map(int, frame_range.split('-'))

            animations.append((name, start, end))

    

    # Sort animations by start frame

    animations.sort(key=lambda x: x[1])

    

    # Get the active object (should be an armature)

    obj = bpy.context.active_object

    if not obj or obj.type != 'ARMATURE':

        raise Exception("Please select an armature object")

    

    # Make sure we have animation data

    if not obj.animation_data:

        raise Exception("The selected armature has no animation data")

    

    # Find the action strip in the NLA editor

    action_strip = None

    action_track = None

    

    for track in obj.animation_data.nla_tracks:

        for strip in track.strips:

            if strip.name == "Action":

                action_strip = strip

                action_track = track

                break

        if action_strip:

            break

    

    if not action_strip:

        raise Exception("Could not find a strip named 'Action' in the NLA editor")

    

    # Store the original action

    original_action = action_strip.action

    if not original_action:

        raise Exception("The Action strip does not have an action assigned")

    

    # Store original frame settings

    original_frame = bpy.context.scene.frame_current

    

    # Clear any active action

    stored_action = obj.animation_data.action

    obj.animation_data.action = None

    

    # Store the original strip settings to restore later

    original_strip_start = action_strip.frame_start

    original_strip_end = action_strip.frame_end

    original_action_start = action_strip.action_frame_start

    original_action_end = action_strip.action_frame_end

    

    # Find the NLA Editor area (we'll need this for several operations)

    nla_editor = None

    for area in bpy.context.screen.areas:

        if area.type == 'NLA_EDITOR':

            nla_editor = area

            break

    

    if not nla_editor:

        raise Exception("Please open an NLA Editor window")

    

    # Create a context override to operate in the NLA editor

    override = {'area': nla_editor, 'region': nla_editor.regions[-1]}

    

    # Create a list to store the created strips for cleanup

    created_strips = []

    

    # Process each animation segment

    for i, (name, start, end) in enumerate(animations):

        print(f"\nProcessing {name} ({start}-{end})...")

        

        # IMPORTANT: Move the original action track to the top of the stack

        # This ensures it has priority in the animation evaluation

        if i > 0:  # We don't need to do this for the first iteration

            # Get the current index of the action track

            action_track_index = list(obj.animation_data.nla_tracks).index(action_track)

            

            # Move the action track to the top

            for _ in range(action_track_index):

                with bpy.context.temp_override(**override):

                    action_track.select = True

                    bpy.ops.nla.tracks_move(direction='UP')

        

        # STEP 1: Select frame range by modifying the action strip

        action_strip.frame_start = start

        action_strip.frame_end = end

        action_strip.action_frame_start = start

        action_strip.action_frame_end = end

        

        # Make sure we're in pose mode

        if obj.mode != 'POSE':

            bpy.ops.object.mode_set(mode='POSE')

        

        # Set the current frame to the start of the range

        bpy.context.scene.frame_set(start)

        

        # STEP 2: Bake the current action strip

        

        # Make sure only the action strip is mutable, mute all other strips

        # This ensures only our target animation is evaluated during baking

        for track in obj.animation_data.nla_tracks:

            for strip in track.strips:

                if strip != action_strip:

                    strip.mute = True

        

        # Deselect all strips

        with bpy.context.temp_override(**override):

            bpy.ops.nla.select_all(action='DESELECT')

        

        # Select our action strip

        action_strip.select = True

        

        # Make sure the NLA is enabled for evaluation

        obj.animation_data.use_nla = True

        

        # Bake the animation with the specified settings

        with bpy.context.temp_override(**override):

            bpy.ops.nla.bake(

                frame_start=start,

                frame_end=end,

                step=1,

                only_selected=False,

                visual_keying=True,

                clear_constraints=False,

                clear_parents=False,

                use_current_action=True,

                bake_types={'POSE'}

            )

        

        # STEP 3: Push Down Action to create a strip

        # At this point, the bake operation would have created a new active action

        baked_action = obj.animation_data.action

        if not baked_action:

            raise Exception(f"Baking failed for {name}, no action was created")

        

        # Rename the baked action to match our segment name

        baked_action.name = name

        

        # Push down the action to create a strip

        with bpy.context.temp_override(**override):

            bpy.ops.nla.actionclip_add(action=baked_action.name)

        

        # Find the newly created strip (should be the latest one added)

        new_track = None

        new_strip = None

        

        # Look for the new strip in all tracks

        for track in obj.animation_data.nla_tracks:

            for strip in track.strips:

                if strip.action == baked_action and strip != action_strip:

                    new_track = track

                    new_strip = strip

                    break

            if new_strip:

                break

        

        if not new_strip:

            print(f"Warning: Could not find the new strip for {name}, creating it manually")

            # Create a new track and strip manually as a fallback

            new_track = obj.animation_data.nla_tracks.new()

            new_track.name = name

            new_strip = new_track.strips.new(name=name, start=start, action=baked_action)

            new_strip.frame_start = start

            new_strip.frame_end = end

        

        # STEP 4: Rename the strip

        new_strip.name = name

        if new_track:

            new_track.name = "SplitActions"

        

        # Add to our list of created strips

        created_strips.append((new_track, new_strip))

        

        print(f"Created strip: {new_strip.name} with action: {baked_action.name}")

        

        # Reset for the next iteration

        obj.animation_data.action = None

        

        # Restore the original action strip settings for the next iteration

        action_strip.frame_start = original_strip_start

        action_strip.frame_end = original_strip_end

        action_strip.action_frame_start = original_action_start

        action_strip.action_frame_end = original_action_end

        

        # Unmute the strip we just created

        new_strip.mute = False

        

        # Mute all other strips except the action strip for the next iteration

        action_strip.mute = False

    

    # Cleanup - unmute all strips

    for track in obj.animation_data.nla_tracks:

        for strip in track.strips:

            strip.mute = False

    

    # Restore the original frame

    bpy.context.scene.frame_set(original_frame)

    

    # Restore original active action if there was one

    if stored_action:

        obj.animation_data.action = stored_action

    

    print("\nAnimation split complete!")

    print("Created action strips:")

    for anim in animations:

        print(f"- {anim[0]} (frames {anim[1]}-{anim[2]})")

    

    return True


# Example usage

animation_data = """Idle1:10-30

IdleToSoar:40-75

FlapForward:100-130

GlideForward:140-170

GlideRight:180-210

GlideLeft:220-250"""


# Run the function

try:

    split_action_strip_manual_steps(animation_data)

except Exception as e:

    print(f"Error: {str(e)}")

How to bake actions in Blender

1.Select all bones in Pose Mode.

2.Go to the menu in the 3D Viewport and select Pose -> Animation -> Bake Action.

3.Ensure these five options are set before clicking OK:

-Set the Start Frame.

-Set the End Frame.

-Check the box for Visual Keying.

-Check the box for Clear Constraints.

-For Bake Data, select Pose.

This should consolidate everything into a single action.