Trigger when Item is added to collection

Hey,
I am looking for a way to trigger a code when something is added to a collection property.

For Example.
Run a function everytime something is added to bpy.data.objects

My hopes are, that this is somehow possible with the messagebus system, but I wasnt succefull yet.
https://docs.blender.org/api/current/bpy.msgbus.html

The other way, this is possible is by adding a timer. However, the rimer runs permanently and checks the collection property every X seconds. This seems like an ugly collection to me.

Does anyone of you have any idea?

This is already being done for you with depsgraph_update, no need for a timer :slight_smile: just write a function that hooks into depagraph_update that checks what you’re looking for. Even something simple like len(bpy.data.objects) will give you exactly what you want

is depagraph_update capable of detecting changes other than Geometry/Objects etc. ?

Yes, it detects all changes to all data in the scene, as well as mode changes and selection changes

Hmm… cool. This will come usefull.
Thanks for the Info

import bpy


def test1(self,context):
    print("test1123")
    return

bpy.app.handlers.depsgraph_update_pre.append(test1)

unfotunately this isnt working, when I add a Marker.
It works on adding Objects, Materials, Particle Systems … but not markers :frowning:

I should have made this clear in the initial post, that it is also(!) about markers.

Markers are animation data so that should be working fine. Maybe you need a context override

could you explain how a context ovveride could make this work?

On second thought, change your handler from pre to post

Its the same. When I add a marker, nothing happens…
When I add an object, it works…


bpy.context.scene.timeline_markers

Try printing that instead. If that doesn’t work, you’re out of luck, there’s no active scene watching beyond depsgraph_update

Watching for collectionproperty changes is very difficult. Blender doesn’t use the msgbus system for that.

Looking at anin_markers.c, operators that modify markers add an ND_MARKERS event to the window manager, which, when later handled by the appropriate editor’s listener leads to a simple redraw of the region.

Which leads me to think: it might be easier to detect and operate on changes by using a draw handler for the editor you need this for. Even if a script modified markers via the api (i.e outside of the interface), it would still trigger a region redraw if an editor was opened, so that still would work.

The things to note is that using a draw callback is reactive, meaning that the change may already have occurred, and any response to this change would have to wait until the next redraw (unless you used wm.redraw_timer, but we don’t speak of it). That, and the fact that timeline is probably the worst editor to manage draw callbacks in, since it’s duplicated to the dope sheet, graph, and nla, all which are their own distinct editor types.

Depends how desperate you are :slight_smile:

1 Like

by now I start to grow very desperate :wink:

I am just experimenting with the timer method and this is dirty as hell.

If there only would be a way to detect when the operator that add markers (or any vanilla operator btw.) is being used…

My two cents would be to get all scene objects/collections every frame, store them in a list or dict, then compare them next frame to the current objects/collections to check if they still match and are relevant. You’d also need to add some code to check for additions and not deletions as well.

There may not be a clean way to do this. This example exploits the fact that modifying markers always sends a redraw to editors with a timeline. It doesn’t check if a marker was added, then deleted again (number of markers wouldn’t change).

It’s possible to compute a hash based on the marker’s frame, label etc. It really depends on how fine grained you need it to be.

import bpy

# Spaces that can modify the timeline
space_types = (
    bpy.types.SpaceGraphEditor,
    bpy.types.SpaceDopeSheetEditor,
    bpy.types.SpaceNLA
)

owners = []      # Handle for spaces
_last_len = [0]  # Cached length of markers


# A draw callback that compares number of markers since last check.
def listen(context):
    n_markers = len(context.scene.timeline_markers)
    if n_markers != _last_len[0]:
        print("markers changed:", n_markers)
        _last_len[0] = n_markers


def register():
    for space in space_types:
        owners.append(
            space.draw_handler_add(listen, (bpy.context,), "WINDOW", "POST_PIXEL"))

    # Redraw editors with a timeline
    for window in bpy.context.window_manager.windows:
        for area in window.screen.areas:
            if area.type in {"DOPESHEED_EDITOR", "GRAPH_EDITOR", "NLA_EDITOR"}:
                area.tag_redraw()


def unregister():
    # Remove the draw callback
    for owner, space in zip(owners, space_types):
        space.draw_handler_remove(owner, "WINDOW")
    owners.clear()


if __name__ == "__main__":
    register()
    bpy.context.scene.timeline_markers.new("123", frame=123)

The callback should, however, be fairly light-weight at 0.01ms per call, and it’s only called when the editor’s window region is redrawn.

2 Likes

Uh, that might be the most suitable solution yet.
I definitely try this!

Yes! This works perfectly fine… yet… :slight_smile: At least till now.
Thank you very mucho

I will report if it still works in the future.

I will add the sequencer, that this should do the job.

1 Like