Addon development question- attaching callback to buttons

I’m trying to make a certain graphing addon for blender, useful for rigging, but I’m stuck in the very first steps. How do I attach callback functions to buttons? I created button using this code:

class RigLayers(bpy.types.Panel):
bl_idname = ‘POSE_PT_RigLayers’
bl_label = “Rig Layers”
bl_space_type = “VIEW_3D”
bl_region_type = “UI”
bl_context = “posemode”
bl_category = CUSTOM_CATEGORY

bone_groups ={
            
            'Root bone': 0,
            'FK bones': 1,
            'IK bones': 2,
            'Facial bones':3,
            'Manual spine':14
}

control_layers = [0,1,2,3,14]
total_layers = 20  

def draw(self, context):
column = self.layout.column() #items are placed under each other in a column

    column.prop(context.scene, "switch_mode")
    contexts = []
    if context.scene.switch_mode==False:
        for item in self.bone_groups.keys():

            column.prop(context.active_object.data, 'layers',
             index=self.bone_groups[item],
             toggle=True, text=item, 
             emboss=True)  

now there are basically 5 buttons created inside the for loop. But, I’d want to associate each button with a specific callback in addition to it’s behaviour of toggling the bone layers. Any help would really be appreciated.

If a property is created by you in your script, you can attach an update function. For existing RNA properties (like bone layers), the RNA message bus API needs to be used. The msgbus is an under-documented part of the Blender Python API that allows subscribing to changes in RNA properties. In your specific case with bone layers I’m not sure on how to determine which button was pressed, as they are all tied to one internal array.

Here is a link that shows how to use the RNA msgbus: https://developer.blender.org/P563

In your specific case, it might be useful to subscribe to each armature individually. The code to do this would be:

# Callback function for bone layer changes
def bone_layer_update_callback(ob):
    # Do something here
    print("Bone layers changed on object: ", ob.name)


# Subscribe to bone layers for a specific armature
def subscribe_to_bone_layers(ob):
    if ob.type != 'ARMATURE':
        return
    
    subscribe_to = ob.data.path_resolve("layers", False)

    bpy.msgbus.subscribe_rna(
        key=subscribe_to,
        # owner of msgbus subcribe (for clearing later)
        owner=ob,
        # Args passed to callback function (tuple)
        args=(ob,),
        # Callback function for property update
        notify=bone_layer_update_callback,
    )


# Ensure only armatures are passed to this function
subscribe_to_bone_layers(bpy.context.object)

A full working example of this integrated with the panel snippet you shared can be found here: https://gist.github.com/natecraddock/150bde4e879d71ec57e2b54b2c56ee4e

To clear the msgbus handler, call this function on each object that you passed to subscribe_to_bone_layers

bpy.msgbus.clear_by_owner(ob)

Maybe someone else knows how to subscribe to individual layers, but I don’t think that is possible. You may need to add custom properties to each armature in code to track changes to specific layers. Hope this is helpful!

2 Likes

I’m sorry but, it seems I have yet to know much about the architecture, I didn’t really understand terms like msgbus, RNA message bus API, there seems to be some sort of architecture going under the hood. Can you give me a link or some reference to what these are and how these are laid out in blender?

After looking back over my previous answer I can see how it could be confusing. Within Blender’s C source code is defined the RNA and DNA. I do not understand the system completely, but simply put the DNA data is stored in the .blend file. This is any data that needs to be saved permanently (objects, window layout, materials, settings, etc). The RNA allows this data to be accessed from Python.

There are some older documents on the architecture of Blender. I have not found any newer sources but things seem relatively unchanged on a higher-level. Some design documents are found on the Blender Dev Wiki Archive and there is an old BlenderNation post on DNA and RNA.

I’ve written many addons without knowing the inner workings of Blender, so unless you are curious I wouldn’t worry too much about understanding this perfectly.

Essentially the message bus (shortened to msgbus) allows subscribing to changes in certain properties already defined within Blender. These are properties like object locations, names, materials, scene gravity, render settings… any property that is already exists in a .blend file. The link in my other reply is the best documentation I can find on how to use the msgbus. Because you want a function to trigger when bone layers are changed, you need to use the msgbus.

An alternative might be to create an operator for each button, which would be a more straightforward method of triggering code on a button press.

1 Like

Thanks, but one last question. What does it specifically mean by ‘subscribing to changes’?

Sorry for such a late reply. I wasn’t subscribed to notifications in this topic.

Each time the property subscribed to is changed, the update function you set is called.

So if you have a function update_func attached to the scene gravity, each time the scene gravity is changed update_func will be called.

1 Like

@natecraddock Thank you for sharing the information about DNA and RNA. I would like to add the following link which talks about the publish/subscribe design pattern which the bpy.msgbus module provides us.

2 Likes