Inheriting from NodePath

Is there a usable way to make a Python class that inherits from NodePath? I did just that, but my 3D object picking code always returns a simple NodePath object, not my subclass.

Is this because NodePaths are C++ objects, or something?

That, and the fact that NodePath is a simple wrapper class not meant to be inherited from.

Most people create a circular reference, storing the custom class pointer as a python tag.

That’s the way my code was set up until I tried this experiment. I was hoping to be able to update my model when the 3d world changed (i.e. set position attributes of an object when something on the screen was moved). Ideally I wouldn’t have to call something along the lines of .update() each time something moves…do you have any suggestions?

The system I use for this involves python tags, like rdb mentioned.

For the class that would otherwise inherit from NodePath, I create an empty NodePath I use as the root for any models used by the class. I add a python tag to the collision NodePaths called “owner” in which I store self, a reference to the class instance. Then, if I want to update a position based on a collision, like with mouse picking, I can get the class instance from the python tag and update the position of the root NodePath.

Here’s a simple example:

class myClass:
  def __init__(self):
    self.root = render.attachNewNode("myClass Root")
    self.colN = CollisionNode("myClass Collision Node")
    #add collision solids, bitmasking, etc
    self.colNP = self.root.attachNewNode(self.colN)
    self.colN.setPythonTag("owner", self)

Then, for the collision handling code:

  def handleCol(self, colEntry):
    intoClassInstance = colEntry.getIntoNodePath().getPythonTag("owner")
    intoClassInstance.root.setPos(#new position)

Keep in mind that that code has a memory leak. Because you’re creating a circular reference, the nodes in the scene will never destruct even after you’ve cleared all of the references to them.

To work around this, be sure to remove the python tag before clearing the last reference to it, or (but only if you really know what you’re doing!) manually manipulate the reference count with unref() and ref().

Yeah, the python tag does need to removed to remove the class instance.

Thank for the advice. It’s strangely low-level, but it gets the job done.


I kind of thought subclassing NodePath was a bad idea, but it’s been working so far. “NodePath is a simple wrapper class not meant to be inherited from”… suggests I better fix those classes ASAP.
I don’t exactly get the problem though and the pythontag approach above seems worse with a potential memory leak.
The difference I see above is:

class MyClass(NodePath):
(all my class specific stuff)

class MyClass():
self.root = a NodePath()
(all my class specific stuff)

so I get obj.setPos() etc in case 1 vs obj.root.setPos() in case 2

I’m not doubting case 2 is the right way. I just want to understand why. what breaks? where does it blow up? Thanks

In a managed language like Python, your objects are automatically deleted from memory when you no longer have any references to these objects (the reference count gets zero), as you know. But if you do something like this:

self.setPythonTag("self", self)

Then you’re letting that object store a reference to itself. This is called a circular reference. The problem with these kinds of circular references is that the reference count of these objects will never get zero; this is because every instance will hold a reference to itself, and if you don’t clear that reference before you’re dropping your own references to the object, then that object will stay in memory forever with no way for you to access it.
Therefore, if you have to create such a circular reference, you must always make sure that before you’re dropping your last reference to the object, you’re unsetting the Python tag to break the circular reference. This is something that’s easy to overlook, creating the risk of memory leaks that can get really big.

Sorry, I may have misunderstood your question. As for why inheriting from NodePath (without using circular references) is usually not a good idea; this actually really depends on the specific situation, and it’s largely a conceptual issue.

A NodePath is just a pointer to a PandaNode. A good analogy would be to inherit from a pointer in C++, which is silly. As there can be many different pointer objects pointing to the same instance in memory, there can be many different NodePath objects pointing to a particular node in the scene graph; whereas inheritance is supposed to imply that that particular node represents the same object as the corresponding instance of your class.

Therefore, it would usually make more sense to inherit from PandaNode instead, or CollisionNode, or whatever.

“A NodePath is just a pointer to a PandaNode. A good analogy would be to inherit from a pointer in C++, which is silly.”

That actually makes sense, conceptual or not. Thanks. I had thought about inheriting from PandaNode, but NP was convenient :slight_smile: