Listening for node add and removal events


#1

Is there an option to know when a node is being created or deleted?
Using base.messenger.toggleVerbose() shows there are no events for this.

What I am trying to do: change the scene graph view when the scenegraph is changed.


#2

I don’t think this feature exists. PandaNode sub-classes do get to know about it, but only limited to that part of the scene graph.

Panda does mark the bounding volumes as stale when you change the node hierarchy; you can call node.isBoundsStale() to see if this has happened. However, I’m not sure that this is reliable; the bounds can become un-stale if they are recomputed by some operation, and furthermore, bounds are also marked stale when something like a transformation changes or when a node is hidden or shown.

If you find no workaround, it might be possible to add this feature, though: PandaNode maintains a counter that is incremented whenever the bounds are marked stale, and if we exposed this counter, you could store it and keep comparing it until they don’t match. Again, though, it would be overzealous, as any change in a node’s position also triggers the bounds to be marked stale.

Failing that, we could create a new system to track changes in the scene graph, but I would prefer not to do this unless there were no alternative.


#3

Thanks for a very quick answer.

PandaNode maintains a counter that is incremented whenever the bounds are marked stale, and if we exposed this counter, you could store it and keep comparing it until they don’t match.

Do you mean I need to create a task that checks for the getBounds() of the root node?
Thanks, I will try it. Actually, I’ve already tried and it works. Maybe it will be a performance hit, but not yet.

EDIT: it works, but only if the bounding volume changes. If I load the same model in the same place, it does not, as the bounding volume is not changed.

EDIT2: Ah, sorry, misunderstood you. You mean the counter than can be accessed in the engine code, not the Python API. Where do I look for this counter in the C++ code?

About an alternative. I haven’t looked through the C++ source, but I guess if I want to implement this, I will need to send an event when an operation is performed on a node?


#4

If you look at the PandaNode source, you’ll see that it checks two counters to see if they match:

_next_update gets incremented whenever the bounding volume is marked stale (for example due to a hide(), transform or parenting change), and every time Panda recalculates the bounding volume, it sets _last_bounds_update to _next_update. If you want to know if something has changed in this part of the scene graph, I think we could just create a new getter that returns the _next_update value, so that you can check yourself whether this value has changed. I think this is probably the best way to implement this; this propagates up the scene graph automatically, so you only need to check the value at the root of the scene graph.

If you want to modify the source to explicitly throw an event when the structure changes at all, you can add a call to something like throw_event("scene-changed"); in both new_connection() and sever_connection() in pandaNode.cxx. However, this places more strain on the event loop because it will add an event every single time a connection is made or broken, and there could be many occurrences of that in a single frame.


#5

Thanks, rdb, I’ll see how this works out.
EDIT:

However, this places more strain on the event loop because it will add an event every single time a connection is made or broken, and there could be many occurrences of that in a single frame.

I think it is OK, since I want it only for debug/development mode.


#6

Update.

From what I’ve learned, PandaNode::new_connection method is NOT called every time the structure is changed. For example, it is not called when inserting “things” that do not consist of multiple nodes, e.g. cameras, models consisting of a single mesh.

Instead, as far as I understand, PandaNode::reparent looks like it is called when a PandaNode is actually inserted in the scene graph, and a new NodePath is created.

I’m now throwing and event after this call and this inspection turned out to be very useful for me.
For example, I’ve learned that there are also other root objects such as frame_rate_root and shadow.

How can I get the full list of such objects?


#7

I don’t think you can get a complete list of all node trees in existence, but you can get a list of all node trees that are being rendered using something vaguely like:

roots = set()
for window in base.graphicsEngine.windows:
    for region in window.display_regions:
        if region.camera:
            roots.add(region.camera.get_top())

#8

Thanks, rdb. I guess I can’t get the reference to the “shadow” tree, which seems to do some stuff behind the scenes?


#9

I did not even know this existed. I grepped in the source code and I think it may be a temporary node created by TextAssembler when assembling text with shadow. If you do not have text with shadow enabled, then the node stays empty (other than an empty GeomNode child) and is not attached to anything. I don’t think you need to worry about this.


#10

Perhaps this is a bit late, but when I wanted to tag every model/actor as clickable, I wrapped the loader functions with a pattern like this:

holder = loader.loadModel

def wrapper(*args, **kwargs):
    val = holder(*args, **kwargs)
    # do special stuff here
    return val

loader.loadModel = wrapper

You just have to do the pattern for each add/remove on the node class, I believe.


#11

Hi, thanks for the suggestion. I’ve already solved my problem by adding events in the panda core when the node is inserted in the graph. This allows me to use direct API as it is. Also, I think your solution will work only with the model and it’s child nodes, but not other types, won’t it?


#12

I should have been more clear.

Not in your case. All of the nodes inherit from PandaNode, so you’d wrap the methods like panda_node.addChild(). This does assume that each subclass calls its super method.

https://www.panda3d.org/reference/python/panda3d.core.PandaNode


#13

That doesn’t work because Panda’s C++ layer won’t call a Python method just because you’ve reassigned it in the Python type dictionary for the PandaNode class.