Warning: PoseAsset XXX is out-of-date with its source animation

In case it helps people in the future, I wrote a Python script to fix this error. It’s very easy to run this in editor to fix all out of date poses.

#
# UE_FixOutOfDatePoseAssets.py
#
# Copyright (c) 2025 Xist.GG LLC
# LICENSE: MIT (free use for all including commercial)
#
# Problem: "Warning: PoseAsset XXX is out-of-date with its source animation YYY"
#
# Solution: Select the problematic assets (or folders containing the assets)
# in the UnrealEditor Content Browser, then in the UnrealEditor menu bar, select
# `Tools > Execute Python Script...` and select this script.
#
# Expects assets to be named in one of these formats:
#
#   foo_pose.uasset     <=> foo_anim.uasset
#   foo_pose_bar.uasset <=> foo_anim_bar.uasset
#
# (In other words, asset names must end in "_pose" and "_anim",
# or must contain "_pose_" and "_anim_").
#
# To set your project up to allow Python scripting:
# @see https://dev.epicgames.com/documentation/en-us/unreal-engine/scripting-the-unreal-editor-using-python
#

import unreal
import re

def update_pose(pose_asset:object, verbose:bool):
    pose_path = pose_asset.get_path_name()

    # Extract just the directory part of the pose asset path
    asset_dir = re.sub(r"(.*)/[^/]+$", r"\1", pose_path)
    # Extract just the name part of the pose asset path
    pose_name = re.sub(r".*\.([^.]+$)", r"\1", pose_path)

    # Compute the expected name of the matching animation asset
    anim_name = re.sub(r"(.+)_pose(_|$)", r"\1_anim\2", pose_name)
    # Compute the full path to the expected matching animation (in the same dir as the pose asset)
    anim_path = asset_dir + '/' + anim_name

    # Try to load the animation
    anim_asset = unreal.EditorAssetLibrary.load_asset(anim_path)
    if not anim_asset or not isinstance(anim_asset, unreal.AnimSequence):
        unreal.log_error("Could not find animation asset for {} (tried: {})".format(pose_path, anim_path))
        return False

    if verbose: unreal.log('Updating {} using {}'.format(pose_name, anim_name))
    pose_asset.update_pose_from_animation(anim_asset)

    # For whatever reason, in UE 5.5 update_pose_from_animation doesn't mark the pose as dirty,
    # so here we will explicitly resave the assets, we can't tell if they changed or not.
    only_if_is_dirty=False
    if unreal.EditorAssetLibrary.save_loaded_asset(pose_asset, only_if_is_dirty):
        if verbose: unreal.log('Saved ' + pose_path)
    return True

def main(verbose:bool):
    unreal.log('Collecting PoseAsset assets from Content Browser selection...')

    # Initialize poseAssets with any explicitly selected ones (array of asset objects)
    pose_assets = unreal.EditorUtilityLibrary.get_selected_assets_of_class(unreal.PoseAsset)

    # If specific folders are selected, recursively scan those for PoseAsset assets
    selected_folders = unreal.EditorUtilityLibrary.get_selected_folder_paths() \
        or unreal.EditorUtilityLibrary.get_selected_path_view_folder_paths()
    for asset_dir in selected_folders:
        asset_dir = asset_dir.removeprefix('/All')  # Is there an Unreal way to do this? :shrug:
        if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
            unreal.log_warning('Asset folder does not exist: {}'.format(asset_dir))
            continue
        if verbose: unreal.log('Scanning folder: {}'.format(asset_dir))
        # Recursively list assets under this path
        for asset_path in unreal.EditorAssetLibrary.list_assets(asset_dir, True):
            asset = unreal.EditorAssetLibrary.load_asset(asset_path)
            if asset and isinstance(asset, unreal.PoseAsset):
                pose_assets.append(asset)

    # Print a report of what we found
    if verbose:
        unreal.log('Found {} pose assets:'.format(len(pose_assets)))
        for asset in pose_assets:
            unreal.log('  - ' + asset.get_name())

    # Update PoseAsset as needed
    num_errors = 0
    if len(pose_assets) > 0:
        with unreal.ScopedEditorTransaction("Update {} Poses".format(len(pose_assets))) as trans:
            with unreal.ScopedSlowTask(len(pose_assets), 'Updating Poses...') as slow_task:
                for asset in pose_assets:
                    if slow_task.should_cancel(): break
                    slow_task.enter_progress_frame(1)
                    if not update_pose(asset, verbose):
                        num_errors += 1

    # Output a summary of what we did and whether there were errors
    if num_errors > 0:
        unreal.log_warning('Encountered {} total errors, check log for details.'.format(num_errors))
    unreal.log('Updated {} pose assets with {} errors.'.format(len(pose_assets), num_errors))

if __name__ == "__main__":
    main(True)
4 Likes