Detect when the active brush changes

I want to make a “brush history” panel, and to do that I need to detect when the active sculpting brush gets swapped out with a new brush. But I can’t figure out how to do it.

The obvious solution is the Message Bus, so I used the given example for my own script.

The active brush is stored in context.tool_settings.sculpt.brush so I’m subscribing to (bpy.types.Sculpt, "brush").

The script just doesn’t work, while it works fine when I subscribe to (bpy.types.Object, "location") like in the example on the message bus page :man_shrugging:

import bpy

owner = object()

subscribe_to = (bpy.types.Sculpt, "brush")

def msgbus_callback(*args):
    print("Brush has changed!", args)

bpy.msgbus.subscribe_rna(
    key=subscribe_to,
    owner=owner,
    args=(1, 2, 3),
    notify=msgbus_callback,
)

Figured out why the subscribing doesn’t work. You can only subscribe to properties, and the brush in (bpy.types.Sculpt, "brush") is a data-block (bpy.types.ID).

The only other method I could think of is to use the depsgraph_update_post handler and constantly check if scene.tool_settings.sculpt.brush has changed. The problem is that changing the brush (at least with the new asset popover thing) doesn’t update the depsgraph :expressionless:

“brush” is not a data-block, it’s a pointer property pointing to data-block. So it shouldnt’ be an issue.

E.g. it’s possible to subscribe to “active” for bpy.types.LayerObjects to track active object.

There’s also the timer… but it can be too much.


Another idea - maybe it’s possible to msgbus subcribe to specific instance of bpy.types.Sculpt (e.g. context.tool_settings.sculpt) instead of subscribing to the entire type.