Creating linked animation sequence in python

Hi,

I am trying to create a linked animation sequence with python code so I can simply select a bunch of bindings and run a for loop over them that exports all selected sequencer bindings as linked animation sequences with one click rather than doing 1-by-1. This needs to be identical to the process of right clicking on a skeleton binding in Sequencer and saying “Create Linked Animation Sequence”.
Now, unfortunately their documentation on this is super limited with just unreal.SequencerTools.export_anim_sequence() and link_anim_sequence but I think these do the right things.
The issue is just creating the first animation sequence link. It doesn’t link the skeleton up or just crashes when doing it. I’m getting some of this code from my own and some from the example given.

import unreal
ll = unreal.LevelSequenceEditorBlueprintLibrary
ues = unreal.EditorLevelLibrary()
level_editor_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
world = ues.get_editor_world()
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
binding = ll.get_selected_bindings()[0]
def export_anim():
    #s.export_anim_sequence()
    anim_factory = unreal.AnimSequenceFactory()
    binding = ll.get_selected_bindings()[0]

    anim_seq_class = unreal.AnimSequence
    try:
        anim_sequence = asset_tools.create_asset("Anim_test", "/Game/", anim_seq_class, anim_factory)
    except:
        pass
    anim_sequence = unreal.load_asset("/Game/Anim_test", anim_seq_class)
    
    level_sequence = ll.get_focused_level_sequence()
    export_option = unreal.AnimSeqExportOption() 
    export_option.export_transforms = True
    # export_option.export_curves = True
    export_option.record_in_world_space = 1
    export_option.export_morph_targets = 1
    export_option.evaluate_all_skeletal_mesh_components = 1 

     
    s.export_anim_sequence(world, level_sequence, anim_sequence, export_option, binding, create_link=1)
    link = s.link_anim_sequence(level_sequence, anim_sequence, export_option, binding)
    print(link)
    return link

Does anyone have any hint on doing this properly? Thanks so much. I have been beating my head against the wall for days trying to get this to work. I’ve tried many different things and nothing is working. Later I’ll be automating the path given as a string but I’m trying this just as a base.

I got it working for the most part with some issues still remaining. But in case anyone else needs this:

import unreal
ll = unreal.LevelSequenceEditorBlueprintLibrary
ues = unreal.EditorLevelLibrary()
level_editor_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
world = level_editor_subsystem.get_current_level()
world = ues.get_editor_world()
eas = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
msbp = unreal.MovieSceneBindingProxy
mst = unreal.MovieSceneTrack
s = unreal.SequencerTools
from unreal import LevelEditorSubsystem
task = unreal.AssetExportTask()
ait = unreal.AssetImportTask()
ait.automated = 1
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()

binding = ll.get_selected_bindings()[0]
eul = unreal.EditorUtilityLibrary()
d = eul.get_selected_asset_data()



les = unreal.get_editor_subsystem(LevelEditorSubsystem)

def print_names(object):
    for i in object: print(i.get_display_name())
        
def export_anim(path="/Game/"):
    path = path.replace("/All/", "/")
    #s.export_anim_sequence()
    level_sequence = ll.get_focused_level_sequence()
    anim_factory = unreal.AnimSequenceFactory()
    bindings = ll.get_selected_bindings()
        
    for binding in bindings:
        object_binding_id = level_sequence.get_binding_id(binding) #finally got the binding id
        skmc = ll.get_bound_objects(object_binding_id)[0]
        skm = skmc.skeletal_mesh
        name = skm.get_name()
        sk = skm.skeleton
        
        
        anim_factory.preview_skeletal_mesh = skm
        anim_factory.target_skeleton = sk
        anim_factory.set_editor_property("asset_import_task", ait)
        anim_factory.set_editor_property("edit_after_new", False)
         

        anim_seq_class = unreal.AnimSequence
        anim_sequence = asset_tools.create_asset(name, path, anim_seq_class, anim_factory)

        # anim_sequence = unreal.load_asset("/Game/Anim_test", anim_seq_class)
        
        
        export_option = unreal.AnimSeqExportOption() 
        export_option.export_transforms = True
        export_option.record_in_world_space = True
        export_option.export_morph_targets = True
        export_option.evaluate_all_skeletal_mesh_components = True 

        export_option.export_attribute_curves = True
        export_option.export_material_curves = True
        export_option.export_transforms = True
        export_option.transact_recording = True
        
        s.export_anim_sequence(world, level_sequence, anim_sequence, export_option, binding, create_link=1)

Where the path I’m getting from the Blueprint from the Editor Utility Widget, just pasting it in and feeding it into the Evaluate Python Script function. Hope this helps someone. This was SO needlessly complicated. Make this easier Unreal, please. Still trying to figure out why some of the skeleton is erroring out when creating with script vs right clicking method. I get warnings. saying “LogScript: Warning: Accessed None trying to read property SkeletalMeshComponent”

Thank you for posting this. It really helped me. I was also trying to bake by the method that was in the documentation page but still I was getting errors like: “URigHeirarchy and Skeleton error”.
After reading your code I realized that we have to set the animation factory properties, i.e. specifying the skeletal mesh and the skeleton, before creating an empty animation sequence asset.

So glad this helped! I may put out youtube tutorials on the stuff I have found for unreal as python scripting in unreal is so complicated compared to other programs. Not many tutorials out there for this stuff I’m noticing. I’m not professional at all in it, but given the current resources out there something is better than nothing.

And yeah that’s exactly the error I ran into too until I realized how to extract and set the skeleton. Took me a while.

Hello, please could you post the working script for this? Having a very similar issue and not familiar enough with Python to resolve it.

Hi @BadookaSlam I thought I did above. This is what I use daily. I’ll give you exactly what I use that also exports out fbx(s) as well from linked sequences automatically and removes the annoying game folder in the directory as well. Notice that it requires another module reference to do it properly and I called it “path_config”

import unreal
import importlib
import os
import shutil
ll = unreal.LevelSequenceEditorBlueprintLibrary
ues = unreal.EditorLevelLibrary()
level_editor_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
world = level_editor_subsystem.get_current_level()
world = ues.get_editor_world()
eas = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
msbp = unreal.MovieSceneBindingProxy
mst = unreal.MovieSceneTrack
s = unreal.SequencerTools
from unreal import LevelEditorSubsystem

task = unreal.AssetExportTask()
task.automated = 1
task.prompt = 0
task.write_empty_files = 1
task.use_file_archive = 0
task.replace_identical = 1

ait = unreal.AssetImportTask()
ait.automated = 1
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()

binding = ll.get_selected_bindings()[0]
eul = unreal.EditorUtilityLibrary()
d = eul.get_selected_asset_data()

asset_registry = unreal.AssetRegistryHelpers.get_asset_registry()

import path_config as pc
importlib.reload(pc)

main_path = pc.main_path + '/animations'
level_sequence = ll.get_focused_level_sequence()
bindings = ll.get_selected_bindings()

ase = unreal.AnimSequenceExporterFBX()

"""
SceneComponent
asset_user_data 
get_child_component
get_children_components
unreal.SkeletalMesh.skeleton 
"""



les = unreal.get_editor_subsystem(LevelEditorSubsystem)

def print_names(object):
    for i in object: print(i.get_display_name())
        
def export_anim(path="/Game/"):
    path = path.replace("/All/", "/")
    #s.export_anim_sequence()
    anim_factory = unreal.AnimSequenceFactory()
    level_sequence = ll.get_focused_level_sequence()
    bindings = ll.get_selected_bindings()
    exported_assets = []
    
    anim_seqs = []
    cur_assets = asset_registry.get_assets_by_path(path)
        
    for binding in bindings:
        parent = binding.get_parent()
        parent_name = parent.get_name().replace("(", "_").replace(")", "_")
        
        object_binding_id = level_sequence.get_binding_id(binding) #finally got the binding id
        skmc = ll.get_bound_objects(object_binding_id)[0]
        skm = skmc.skeletal_mesh
        name = skm.get_name()
        # name = '_'.join(name.split("_")[0:2])
        name = parent_name + "_" +  name
        name = name.replace(" ", "_")
        count = 0
        for asset in cur_assets:
            if(name in str(asset.asset_name)):
                count += 1
        if(count > 0):
            name += "_{0}".format(count)
            
        sk = skm.skeleton
        
        
        anim_factory.preview_skeletal_mesh = skm
        anim_factory.target_skeleton = sk
        anim_factory.set_editor_property("asset_import_task", ait)
        anim_factory.set_editor_property("edit_after_new", False)
         

        anim_seq_class = unreal.AnimSequence
        anim_sequence = asset_tools.create_asset(name, path, anim_seq_class, anim_factory)
        asset = asset_registry.get_asset_by_object_path(anim_sequence.get_path_name())
        # Anim Sequence Object.. asset? idk
        
        # anim_sequence = unreal.load_asset("/Game/Anim_test", anim_seq_class)
        
        export_option = unreal.AnimSeqExportOption() 
        export_option.export_transforms = True
        export_option.export_morph_targets = True
        export_option.export_attribute_curves = True
        export_option.export_material_curves = True
        export_option.record_in_world_space = True
        export_option.evaluate_all_skeletal_mesh_components = True 
        export_option.transact_recording = True
        
        s.export_anim_sequence(world, level_sequence, anim_sequence, export_option, binding, create_link=1)
        # link = s.link_anim_sequence(level_sequence, anim_sequence, export_option, binding)
        exported_assets.append(asset)
        anim_seqs.append(anim_sequence)
    export_fbxs(exported_assets=exported_assets, anim_seqs=anim_seqs)    
    # export fbx for all exported_assets
    return  

def export_fbxs(exported_assets, anim_seqs):
    fe = unreal.FbxExportOption()
    bt = unreal.MovieSceneBakeType.BAKE_TRANSFORMS
    bn = unreal.MovieSceneBakeType.NONE
    fe.ascii = 0
    fe.bake_actor_animation = bn
    fe.bake_camera_and_light_animation = bt
    fe.collision = 0
    fe.export_local_time = 1
    fe.export_morph_targets = 0
    fe.export_preview_mesh = 1
    fe.export_source_mesh = 0
    fe.force_front_x_axis = 0
    fe.level_of_detail = 0
    fe.map_skeletal_motion_to_root = 0
    fe.vertex_color = 0
    asset_tools.export_assets([x.package_name for x in exported_assets], main_path) # Works but makes stupid path, trying to avoid
    move_game_files(main_path)

    
def move_game_files(path):
    base_dir = path
    game_folder = os.path.join(base_dir, "Game")
        
    # Recursively find all .fbx files in the Game folder
    for root, dirs, files in os.walk(game_folder):

        for filename in files:
            if filename.lower().endswith(".fbx"):
                # Construct full source and destination paths
                source_path = os.path.join(root, filename)
                dest_path = os.path.join(base_dir, filename)
                
                # Move the file
                shutil.move(source_path, dest_path)
                print(f"Moved: {filename} from {root} to {base_dir}")

    print("All .fbx files have been moved to the animations folder.")
    shutil.rmtree(game_folder)
    print(f"Removed the folder: {game_folder}")
    

My path config module called path_config.py

import unreal
import os
ll = unreal.LevelSequenceEditorBlueprintLibrary

def change_shot_name(shot):
    if("pal" in shot.lower()):
        return shot.replace("Pal", "shot")
    elif("dock" in shot.lower()):
        return shot.replace("Dock_sh", "SQ")
    else:
        return shot
    
    
def get_output_dir(shot_name):
    path = main_path
    if(os.path.exists(path)==False):
        os.makedirs(path)
    versions = os.listdir(path)
    if(len(versions)==0):
        os.makedirs(os.path.join(path, "v0001"))
        path = os.path.join(path, "v0001")
        return path
    else:
        sorted_versions = [int(v.replace("v", "")) for v in versions]
        latest_version = max(sorted_versions)
        latest_version += 1
        path = os.path.join(path, "v{:04d}".format(latest_version))
    return path

main_path = "L:/ALL HOUDINI FILES/ASC_INSPIRATION/{main_project}/{project}/{shot}/"
lev = ll.get_focused_level_sequence()
shot = lev.get_name()
# shot = '_'.join(shot.split("_")[0:2])
shot = change_shot_name(shot)
main_project, project = unreal.Paths.get_project_file_path().split("/")[-2].split("_") #FinalBattle2
if(main_project.lower() == "exo"): main_project = "Exodus"
main_path = main_path.format(main_project=main_project, project=project, shot=shot)