I’ve decided to try and construct an interface builder for Panda3D. In essence, it would be a GUI tool for creating (loading and saving) language-independent interface description files. I could use the tool to visually create my user interfaces, saving the precious development time that goes into my current “script interface, load it, look at it, decide I hate the look, move everything around, hand-copy the new positions back into the script, quit, load, etc.” process.

To that end, I’m trying to figure out how to “freeze” a DirectGUI component, along the lines of the way that one can “freeze” a subsection of the NodePath by calling nodePath.writeBamFile. The strategy I’m exploring now is to use the DirectGuiBase.configure() method to retrieve all the options on a widget (and a a recursive analysis to get the options on the subcomponents), then distill them into a dictionary of the non-default options—factoring in the special-case options that cannot be changed after initial definition (pos and scale, for instance). The dictionary would be suitable for back-feeding into the constructor to re-create the object.

Does anyone know of a better way to do this than the approach I’m taking? Is there an API call to freeze a DirectGUI object that I’ve missed, or can someone think of a similar but superior method to my approach?

I’m having a bit of trouble with automatically walking the render tree to pull in my widgets for serialization. The problem is that when I use getChild() to retrieve the children, they’re coming up as class NodePath (so I don’t have access to their configure functions, etc.). I’ve run into this problem in other places; the solution I usually use is a terribly hackish (but effective!) preparation step: I use setPythonTag to stash a tag named ‘this’ that is a weakref to the object itself. Then when I end up getting a plain-vanilla NodePath back, I snap the “this” reference to get the fully-featured class instance. That’ll require me to remember to do that to all my DirectGUI widgets, but hey, if this works, I won’t be constructing them by hand anymore, so the serializer routines can take care of it for me!

If there’s a way to do a downcast back to my original type in one step, that’d be awesome; otherwise, I’ll go with this solution.

If there’s a way to do a downcast back to my original type in one step, that’d be awesome; otherwise, I’ll go with this solution.

yep, i would also be interested in such a thing.

i am deriving from NodePath and parent my objects to the render scene, but working with the tree will always return me a NodePath and i also need to use the setPythonTag “trick” to get to my “real” instance. which creates a circular reference (though i do not yet use a weakref… hm…).

i assume that the internal code is doing some NodePath copy/clone action and thus looses the “original” instance information?

i assume that the internal code is doing some NodePath copy/clone action and thus looses the “original” instance information?

cheers,
kaweh

I don’t think a copy/clone step is necessary to lose the original instance information… though the other possible reason may be worse, depending upon your point of view

I’m not 100% certain because I don’t have the source code right in front of me, but I know that as of (I think) Panda3D 1.1.0, NodePath became a type, not a Python class. It’s functionally almost the same, but the big difference is that the NodePath type is backed by a C++ class (whereas a NodePath class would be a Python object), so it’s quite a bit faster. But since C++ is a statically-typed language, we’re probably losing the child specialization information because getChild is defined to return only a NodePath (and when you operate on a parent type in C++, the runtime “hides” all of the child type’s information except for overrides to parent-type properties and methods that were declared virtual). Ah, static typing… the bane of using class specialization to extend functionality!

I suspect that unless one wants to extend the NodePath API to provide a downcasting mechanic to the Python layer, “tying off” the class specializatoin using setPythonTag might be the most efficient solution. It’s a bit of a bear, but it’s at least possible. I use the weakref just to make sure that the tie-off doesn’t interfere with reference counting.

These are all good guesses, but actually, the reason for this problem runs a bit deeper than that.

The thing to remember is, Panda doesn’t store NodePaths in the scene graph. It stores PandaNodes. A NodePath is just a handle to a PandaNode.

Whenever you do something like render.find(’**/myNode’) or myNode.getChild(1) or, really, any Panda call that returns a NodePath, what it actually returns is a brand new NodePath instance to the node in question.

This new NodePath instance is just a NodePath. It isn’t an Actor, it isn’t a DistributedNode, it isn’t anything that you might have created in Python. There’s no downcasting possible, since it simply isn’t anything other than a NodePath.

You could try to work around this by inheriting your Python class from PandaNode instead of NodePath. Now, when you store a PandaNode in the scene graph, and you ask Panda for the node, it always gives you back the same PandaNode instance. This is a little closer. But it’s still doomed to failure, because of the way the whole C++/Python interface works. See, the PandaNode object you have in Python isn’t actually the C++ PandaNode object. It’s just a Python object that stores a pointer to the C++ object internally; and whenever you call a method on the Python object, it automatically passes that method call down the C++ object. But there’s always this level of indirection, because Python can’t directly manipulate C++ objects. (And while it’s true that we went from Python classes to Python objects in version 1.1, there was still the same layer of indirection before that.)

So, even though Panda is returning the same C++ PandaNode instance with each call, it has to create a new Python wrapper around this C++ object. And this new Python wrapper is–once again–just a PandaNode, not anything you might have specialized from PandaNode.

The bottom line is, the setPythonTag() interface is probably the best way to map the pointers you get back from C++ into a Python object. In fact, that’s precisely why we added the setPythonTag() interface in the first place. You do have to be careful, though, since when you use setPythonTag() in this way, you are creating a circular reference count–you are setting up a pointer to an object that points back to the object that holds the pointer. As long as you remember use weak reference pointers, though, it’s probably OK.