UI list for actions?

Hello, I’m interested in creating a UI list similiar to the template in the text editor “ui_list_simple”, but instead of showing and selecting materials. I want it to show and select the list of actions (animation clips).
I have a bit of experience with blender’s python, but when I look into that template example, I’m really puzzled.
maybe someone can give me some pointers on how to turn this template into an action list instead of material?

It depends on what you wanna do… It’s pretty simple to show all actions in bpy.data.actions in a panel and allow the user to select select one (in the sense, that an integer property stores the index of the currently highlighted action in the template_list):

import bpy


class ACTION_UL_list(bpy.types.UIList):
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
        ob = data
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            layout.prop(item, "name", text="", emboss=False, icon_value=icon)
        elif self.layout_type in {'GRID'}:
            pass


class UIListPanelExample(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "UIList Panel"
    bl_idname = "OBJECT_PT_ui_list_example"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout
        ob = context.object
        layout.template_list("ACTION_UL_list", "", bpy.data, "actions", ob, "action_list_index")


def register():
    bpy.types.Object.action_list_index = bpy.props.IntProperty()
    bpy.utils.register_module(__name__)


def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Object.action_list_index

if __name__ == "__main__":
    register()

If you want the user to associate multiple actions with an object for instance, a custom data structure will be required. Namely, CollectionProperty of type bpy.types.PropertyGroup, of which you actually need the built-in “name” StringProperty only to store action names. Note that if you change action names, the connection will break (your collection will contain invalid action names from there on). A few operators are required to handle user interaction with the template_list (add, remove, move up, move down, …)

Interesting, I didn’t know that was possible. What I did was to do an IntProperty collection property instead, with an int pointing to each action.

Thanks A lot CoDemanX, it works great!
I also added the line

ob.animation_data.action = bpy.data.actions[ob.action_list_index]

before registration so that the current active action always correspond to the selected one in the list.
but its probably not the best way, since its now not possible to change the action in the action editor (it works only in 1 direction) :confused:
maybe there is a better way to apply it, so that the list and action editor are always co-responding to each other?

The registration is also different then how I’m used to, so maybe it has something to do with that as well?

I also didn’t understand what is the ob = data in the first class necessary for?

ob = data is a left-over, forgot to remove it.

Maybe this is what you rather wanna do?

import bpy

class UIListPanelExample(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "UIList Panel"
    bl_idname = "OBJECT_PT_ui_list_example"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout
        ob = context.object
        
        if ob.animation_data is not None:
            layout.prop(ob.animation_data, "action")
        else:
            layout.label("No animation_data.")


def register():
    bpy.utils.register_module(__name__)


def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()

Be careful with bpy.data.actions[ob.action_list_index], as the index can change as soon as you add another action. Maybe a callback for the IntProperty could solve this.

Actually the first widget template was exactly what i was looking for, I wanted to use it when I prepare a character for exporting that i could run quickly and intuitivly through all the animation list to see if all the animations work fine.

what do u mean by a callback to intproperty? currently, the index actually seems to be still similiar when i add an animation clip.
Is there some special property or python function that shows the index of each action (in bpy.data.actions) or do i have to loop through the whole actions with a counter for that?

is there a good way to check if an action was changed on the action editor itself or in the ui widget (the value of action_list_index in this case)? should i just register a new property with the old_action and compare it everytime with the current action selected?

Thanks again
Tal

Properties can call user-defined function on changes (update argument on property creation). Whenever you change the property in UI, the callback will be called and can do something else. Since the template_list index property changes as you change selection, the callback will be called for that exact event every time.

There’s no index property, so either use enumerate(…) or possibly bpy.data.actions.find(ActionName) to return the index or raise an AttributeError if not found.

awesome, it works great with the update function. now its possible to change the action both in the list and the action editor, and they influence each other in both directions, using the even handler (for influencing the list from the action editor)


import bpy

def action_editor_update(context):
    ob = bpy.context.object
    action_index = bpy.data.actions.find(ob.animation_data.action.name)
    if action_index != ob.action_list_index:
        ob.action_list_index = action_index
                
#select the new action when there is a new selection in the ui list and go to the first frame
def update_action_list(self, context):
    ob = bpy.context.object
    ob.animation_data.action = bpy.data.actions[ob.action_list_index]
    bpy.context.scene.frame_current = 1

class ACTION_UL_list(bpy.types.UIList):
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            layout.prop(item, "name", text="", emboss=False, icon_value=icon)
        elif self.layout_type in {'GRID'}:
            pass

class UIListPanelExample(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Action List"
    bl_idname = "OBJECT_PT_ui_list"
    bl_space_type = 'VIEW_3D'
    bl_region_type = "UI"
    bl_context = "object"

    def draw(self, context):
        layout = self.layout
        ob = context.object    
        layout.template_list("ACTION_UL_list", "", bpy.data, "actions", ob, "action_list_index")

bpy.app.handlers.scene_update_post.append(action_editor_update)

def register():
    bpy.types.Object.action_list_index = bpy.props.IntProperty(update=update_action_list)
    bpy.utils.register_module(__name__)


def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Object.action_list_index

if __name__ == "__main__":
    register()

I was trying in the beginning to see if bpy.context.object.animation_data.action.is_updated (instead of the condition that i eventually wrote)
but it was always getting False, also when the action changed. any idea what is this property actually used for?

another last question is - does the event handler have a big influence on blender`s performance? since it’s calling for the function on each even.

is_updated is part of the rendering API afaik and doesn’t necessarily behave as expected. Note that there’s also is_updated_data.

Update-callbacks are usually fine performance-wise, scene update handlers however are more costly I’d say. You should make sure that the function executes fast, because it is called very often, not only when changes occur.

I know, long time bump but just found this and is really helpful for my project too! thanks alot for sharing the script, I made it work on my side too!

Is there way to select multible actions from list and have delete button to delete them?

@Crystus I couldnt find a way to implement that in my addon, nor I find a way in blender, Currently I have a delete button, that is easy to use if there are a bunch of animations, just click it a series of time, it is easier than through the outliner, but still not optimal.

Hello! Is there a way for hide the label "Action: " to take advantage of more space?!

action

layout.prop(ob.animation_data, "action", text = "") should work.

Thanks! I had tried it before but it didn’t work … apparently it was a typo, haha.

1 Like