NodePath subclassing


#1

I examined the actor class to see how it subclasses NodePath and extracted it into a python class that you can easily subclass from. I’m not sure how robust this subclassing method is though. Hypnos warned me of problems with collisions but I’ve gotten this working with one of the collision samples in the manual.

This snipplet goes to the public domain

This first class lets you easily subclass NodePath instead of having an attribute for a NodePath in your class.

from pandac.PandaModules import NodePath, PandaNode, GeomNode

class SubNodePath(NodePath):
    """
    A convenience class for sub classing NodePath. Derived from how the Actor class subclasses NodePath.
    """

    def __init__(self, name=None):
        """A convenience class for subclassing NodePath"""
        # Initialise nodepath essence
        NodePath.__init__(self)
        
        # Creates a flattenable nodepath
        if name:
            root = PandaNode(name)
        else:
            root = PandaNode('SubNodePath')

        self.assign(NodePath(root))
        self.__geomNode = NodePath(self)
        
        if name:
            NodePath.setName(self, name)

    def removeNode(self):
        
        # remove all its children
        if(self.__geomNode):
            self.__geomNode.removeChildren()
            self.__geomNode.removeNode()
            self.__geomNode = None
        if not self.isEmpty():
            NodePath.removeNode(self)

This second class is an example of a NodePath subclass that has been given a pythonic position property.

class PyNodePath(SubNodePath):

    def __init__(self, name=None):
        """
        A NodePath subclass with pythonic positions attributes.
        
        NodePath.pos will give you the node's world position while
        NodePath.pos = 1, 2, 3 will set it. NodePath.pos = NodePath, 1, 2, 3
        sets the position relative to the given nodepath. The other properties
        are NodePath.x, NodePath.y, NodePath.z.
        """
        super(PyNodePath, self).__init__(name)
    
    def __setProperty(self, funcName, args):
        """Used for property setting functions that could 
        have more than one argument"""
        func = getattr(NodePath, funcName)
        if type(args) is tuple:
            return func(self, *args)
        else:
            return func(self, args)

    # Position attributes
    def __getPos(self):
        return NodePath.getPos(self)
    def __setPos(self, args):
        return self.__setProperty('setPos', args)
    pos = property(__getPos, __setPos)
    
    def __getX(self):
        return NodePath.getX(self)
    def __setX(self, args):
        return self.__setProperty('setX', args)
    x = property(__getX, __setX)
    
    def __getY(self):
        return NodePath.getY(self)
    def __setY(self, args):
        return self.__setProperty('setY', args)
    y = property(__getY, __setY)
    
    def __getZ(self):
        return NodePath.getZ
    def __setZ(self, args):
        return self.__setProperty('setZ', args)
    z = property(__getZ, __setZ)

EDIT NOTE
I’ve edited this post to address pro-rsoft’s comment below.


#2

Treeform has done this before, but by altering the NodePath class directly. Search the forums about that.

However, keep in mind that a NodePath’s position is not a property. You are actually transforming the PandaNode’s transform when you call setPos, I believe. So this can be pretty misleading.

Also, you can create a new PandaNode by just supplying the name in the init of NodePath.


#3

I guess I distracted the main point of this snippet by putting the pythonic getter/setter in the main class. I’ve separated the classes now. The main point of this class being that if you want to have an object class that subclasses NodePath, here’s an easy way to do it.

Also the NodePath.init takes the python class instead of a name so I can’t quite do PandaNode().


#4

Uhm, this works for me:

class SubNodePath(NodePath):
  def __init__(self, name = None):
    if name == None:
      NodePath.__init__(self)
    else:
      NodePath.__init__(self, name)

Or, even better (works with all variants, there are more than 2):

class SubNodePath(NodePath):
  def __init__(self, *args, **kwargs):
    NodePath.__init__(self, *args, **kwargs)

I’m also not quite sure where you’re heading with the geomNode thing.

Sorry if I’m ruining your thread but subclassing NodePath is as easy as subclassing any other regular Python class.


#5

Oh. I was under the impression that you had to do some strange jiggery pokery to subclass NodePath since you told me to look at how Actor subclassed NodePath. I just assumed that you needed those extra bits from the Actor class to properly use it as a subclass so I made this snippet.


#6

the problem i mentioned, is not that collisions dont work. but collisions yield the object’s that collided, while it will return you the nodepath which collided, it will not yield any inherited class. it will only return the nodepath you inherited from, this has been discussed before if you need more informations.


#7

Thats why there is setPythonTag so you can set those classes back. I wonder if the system could insert python subclass data into nodepath in form of python tag and that get it back to user seamlessly. C++ functions that return nodepath to python could inspect for special python tag and return that instead.


#8

The trouble is that np.setPythonTag(‘tag’, np) causes a reference count leak: you have just added a pointer to np to itself. This means that np will never be destructed by Python, unless you break the cyclical reference explicitly when you are done with np.

So, this means that it’s a bad idea to set these cycles up automatically. There might be a better way to automatically re-Pythonize these derived objects, but it would be complicated.

For reasons like this, I’m a big fan of not subclassing NodePath. I think it was a design mistake when we made Actor do this.

David