The Tech Artist


Panda Editor – First Release

by on Nov.02, 2012, under Panda3D, Panda3D Scene Editor

It’s been just over a year since I started the editor project and I’ve finally got around to making a release. “Release early, release often” as they say – I seems to be struggling with both those concepts. I’ve found it difficult to shelve the idea of not releasing a polished piece of code in order to get some feedback, so I’ve pared some of the features which were still in early development and moved some of the other functionality into plugins that I will release later. In this way I hope to create a nice, solid core which can be expanded with plugins. Hopefully some other users will find it useful!

Code is available here.

4 Comments more...

Rubix Cube Game

by on Jun.10, 2012, under Panda3D, Panda3D Scene Editor

I’ve just uploaded my latest Panda3D project which you can play here – it’s a small Rubix Cube simulation. This was my first project to be completed with the editor I’m developing. It’s quite simple in scope but was valuable as an exercise as it dictated which features I added to the editor next. Needless to say developing an editor and a project with it in parallel is pretty exhausting!

Here’s a screenshot of how the editor looks in its current state with the Rubix Cube project loaded:

More details on the editor in my next post.

5 Comments more...

Attaching Scripts to Objects in Panda3D – Part II

by on May.06, 2012, under Musings, Panda3D, Panda3D Scene Editor

In my last post I showed how to attach a Python object to a node path in order to create a ‘hook’ in the Panda3D scene graph. In this post I’ll be showing how to dynamically add additional code to that object at runtime. Attaching code to objects in this way will be at the core of offering drag and drop scripting functionality in the same manner as Unity.

Once we have the PandaObject the next thing we could do is to subclass it in order to add additional code. Since I’m building an editor I want to offer the user an easy way to add, remove and combine different scripts for a node path, so in this case we’ll use the PandaObject as a hook only and then ‘hang’ other scripts from it.

In the context of our editor, the user will presented with a file browser displaying all the scripts in their project. This hierarchy will be representative of the directory structure on disk, and the user should be able to drag and drop any script onto any node in the scene. This presents an interesting problem as essentially we need to be able to instantiate a class from a file path the user selects at runtime. Thankfully python’s imp module offers some very handy tools for solving this kind of problem.

So now our PandaObject code looks like this:

import os
import sys
import imp

from direct.showbase.DirectObject import DirectObject

class PandaObject( object ):

    def __init__( self, np ):

        # Store the node path with a reference to this class attached to it = np 'PandaObject', self )

        self.instances = {}

    def Get( np ):

        # Return the panda object for the supplied node path
        return np.getPythonTag( 'PandaObject' )

    def Break( np ):

        # Detach each script from the object
        pObj = PandaObject.Get( np )
        if pObj is not None:
            for clsName in pObj.instances.keys():
                pObj.DetachScript( clsName )

        # Clear the panda object tag to allow for proper garbage collection
        np.clearPythonTag( 'PandaObject' )

    def AttachScript( self, filePath ):

        # If the script path is not absolute then we'll need to search for it.
        # imp.find_module won't take file paths so create a list of search
        # paths by joining the head of the script path to each path in sys.path.
        head, tail = os.path.split( filePath )
        if not os.path.isabs( filePath ):
            srchPaths = []
            for sysPath in sys.path:
                srchPaths.append( os.path.join( sysPath, head ) )
            srchPaths = [head]

        # Import the module. Pass imp.find_module the name of the module
        # and a list of paths to search for it on.
        name = os.path.splitext( tail )[0]
        modDetails = imp.find_module( name, srchPaths )
        mod = imp.load_module( name, *modDetails )

        # Get the class matching the name of the file, attach it to
        # the object
        clsName = name[0].upper() + name[1:]
        cls = getattr( mod, clsName )

        # Save the instance by the class name
        self.instances[clsName] = cls( )

    def DetachScript( self, clsName ):

        # Remove an instance from the instance dictionary by its class name.
        # Make sure to call ignoreAll() on all instances attached to this
        # object which inherit from DirectObject or else they won't be
        # deleted properly.
        if clsName in self.instances:
            instance = self.instances[clsName]
            if isinstance( instance, DirectObject ):
            del self.instances[clsName]

Note the two new methods, AttachScript() and DetachScript(). We will use these to add and remove additional code to our PandaObject. Code to be added will be its own class which I will refer to from here as a ‘behaviour’.

Behaviours are attached using the AttachScript method which takes a path to a python script as an argument. By using Python’s imp module we can import a module using a string, and it doesn’t have to be found on sys.path either. There is one caveat though – imp.find_module will only take a file name as its first argument, absolute or relative paths will not work. As I want this to handle relative paths I have to build a list of search paths to pass to find_module by joining the directory of the script to each of the sys.paths.

There is another restriction in that the name of the behaviour’s class must be named an uppercase leading match of the file that it lives in. While this goes against my ideas of dictating how the developer approaches their project, I don’t think it’s too restrictive and also matches the Python / Unity paradigm of naming classes in accordance to the files they live in.

Once we have found the class name we create an instance of it with the PandaObject’s node path as an argument. Any behaviour we add is probably going to modify the node path in some way, so we’ll need to pass this through. All instances are then stored in the PandaObject’s instances dictionary – something I’ll probably end up changing as currently it is not possible to attach more than one of the same type (named) of behaviour to a PandaObject.

We can remove instances that are attached to our PandaObject by passing the class name to DetachScript(). I’ve added a call to ignoreAll() for any behaviours that are inherited from DirectObject as they won’t be garbage collected without removing their reference from the messenger first.

Now to create some kind of basic behaviour. The following code will move the node path slowly up and down when started:

import math

from direct.showbase.DirectObject import DirectObject

class NewBehaviour( DirectObject ):

    def __init__( self, np ):

        # Set the node path this behaviour will control then bind Start and
        # Stop events. = np
        self.accept( 'StartNewBehaviour', self.Start )
        self.accept( 'StopNewBehaviour', self.Stop )

    def Update( self, task ):

        # This will move the node path up and down like a sine wave. self.initZ + math.cos( task.time ) )

        # Keep this task running so long as the node path is valid.
        if not
           return task.cont

    def Start( self ):

        # Get our initial z position then add an update task to be processed
        # every frame.
        self.initZ =
        self._task = taskMgr.add( self.Update, 'UpdateTask' )

    def Stop( self ):

        # Remove the update task from the task manager. Remember to set the
        # _task member to None or else the behaviour won't be garbage
        # collected!
        taskMgr.remove( self._task )
        self._task = None

Nothing too special going on here. We store the input node path and bind some events when the class is instantiated, and there’s some basic wrapping of the task manager going on there too.

Now to bring the whole thing together. We start by loading the default box model, parenting it under render and attaching a PandaObject. We then pull that object back out of the scene graph in a different scope and attach the behaviour to it by passing the file path to the script:

import direct.directbase.DirectStart

from pandaObject import PandaObject

def Foo():

    # Load a model, put it in the scene graph and attach a PandaObject hook to
    # it
    box = loader.loadModel( 'box' )
    box.reparentTo( render )
    PandaObject( box )

def Bar():

    # Pull the node path out of the scene graph and attach the new behaviour
    # to it. myNp could be obtained other ways; returned from a collision
    # picking event for example.
    myNp = render.find( '*box*' )
    pObj = PandaObject.Get( myNp )
    pObj.AttachScript( '' )


# Move the camera so we can see the box, then send the message to start the
# new behaviour. -10 )
messenger.send( 'StartNewBehaviour' )


Running the above code should show a box that floats up and down. To remove the node cleanly we have to make sure we stop the task running first, then we can remove it like normal:

messenger.send( 'StopNewBehaviour' )
myNp = render.find( '*box*' )
PandaObject.Break( myNp )

So while this seems overly complex in order to get a box to move, you can imagine how neat this works when attached to a script file browser. By binding some drag and drop events we can easily attach the user’s script to any node path in the scene.

2 Comments more...

Attaching Scripts to Objects in Panda3D – Part I

by on Jan.05, 2012, under Musings, Panda3D, Panda3D Scene Editor

Now that I have a basic scene editor capable of adding, transforming and editing the properties of nodes, the next thing to do is to get some scripted behaviour happening. Having used Unity for my last project I’ve found my design choices to be greatly influenced by this tool, and Unity takes the intuitive approach of allowing the user to attach scripts to nodes in the scene hierarchy. Any subsequent manipulation of the node or its components by these scripts can be done using reserved keywords in the body of the script.

This idea of packaging code close to the node path it’s responsible for seems very object oriented and appeals greatly to me, and there’s no reason why we can’t do a similar thing in Panda3D. The most logical choice is to start subclassing NodePath or PandaNode, and adding the additional behaviour and methods there. Consider the following code:

import direct.directbase.DirectStart
from panda3d.core import NodePath

class MyNodePath( NodePath ):

    def DoSomething( self ):
        print 'Hello world!'

def Foo():

    # Create an instance of our custom node path, stick it into the scene graph
    myNp = MyNodePath( 'myNodePath' )
    myNp.reparentTo( render )

def Bar():

    # We're in a different scope and have lost the original reference to myNp,
    # so we need to pull it out of the scene graph again
    myNp = render.find( 'myNodePath' )



However when we run this we get:

Hello world!
Traceback (most recent call last):
  File "", line 24, in
  File "", line 21, in Bar
AttributeError: 'libpanda.NodePath' object has no attribute 'DoSomething'

That’s odd – our method seems to have disappeared. Perhaps this is to be expected however, as a NodePath class is meant to serve as a path to a node – not a node itself. Maybe a reference to a node is just a wrapper generated during runtime. Let’s try a more concrete example by subclassing the PandaNode directly:

import direct.directbase.DirectStart
from panda3d.core import PandaNode

class MyPandaNode( PandaNode ):

    def DoSomething( self ):
        print 'Hello world!'

def Foo():

    # Create an instance of our custom node
    myPNode = MyPandaNode( 'myPandaNode' )
    render.attachNewNode( myPNode )

def Bar():

    # We're in a different scope and have lost the original reference to myNp,
    # so we need to pull it out of the scene graph again
    myNp = render.find( 'myPandaNode' )



This doesn’t seem to work as expected either:

Hello world!
Traceback (most recent call last):
  File "", line 24, in
  File "", line 21, in Bar
AttributeError: 'libpanda.PandaNode' object has no attribute 'DoSomething'

So what the Sam Hill is going on here?  I created an instance of MyPandaNode, stuck it in the scene graph but can’t access its method once I’ve pulled it back out again. This seems like an odd limitation and one that on searching the Panda3D forums seems to bite everyone sooner or later. It happens because Panda is essentially a C++ engine under the hood, and whenever you perform a query that returns a PandaNode or NodePath you are handed the C++ object with a thin Python wrapper around it. Unless you keep track of your custom NodePaths and never lose references to them you’ll always get a newly minted, default Python wrapper returned. Subclassing in Python merely subclasses the wrapper class, not the C++ class.

This makes our quest to attach code to object a lot more difficult, but there should still be a straightforward solution. If we can’t subclass NodePath, then surely we should be able to wrap it. This works fine but we need to get back our custom class once we have the NodePath. Thankfully NodePath has a great little method which can be used to attach a python object to a NodePath, this will stay attached even if we lose the initial reference to the NodePath and have to pull it out of the scene graph again. It’s called ‘setPythonTag’ and can be used to back reference the NodePath to your custom class:

class MyObject( object ):

    def __init__( self, name ): = NodePath( name ) 'base', self )

Now whenever we have a NodePath we can simply get back to our custom class by calling:

myNp.getPythonTag( 'base' )

Neat – we’ve essentially solved our problem! Yes, but we’re also introduced a new one. Consider the following:

import direct.directbase.DirectStart
from panda3d.core import NodePath

class MyObject( object ):

    def __init__( self, name ): = NodePath( name ) 'base', self )

    def __del__( self ):
        print 'deleted!'

    def DoSomething( self ):
        print 'Hello world!'

def Foo():

    # Create an instance of our custom class, stick the node path into the scene
    # graph
    myObj = MyObject( 'myNodePath' ) render )

def Bar():

    # We're in a different scope and have lost the original reference to myNp,
    # so we need to pull it out of the scene graph again
    myNp = render.find( 'myNodePath' )
    myNp.getPythonTag( 'base' ).DoSomething()

    # Try to remove the node from the scene graph, we should see the deleted
    # message from the destructor
    #myNp.clearPythonTag( 'base' )      # Uncommenting this line will result in the node path being destroyed properly



You can see from running the above script that you will never see the ‘deleted!’ message printed from the destructor, unless the python tag is cleared first. This is because essentially we’ve created a circular reference: The class contains a reference to the NodePath, which contains a reference back to the class as a Python tag. While this isn’t a huge problem if we remember to break the reference, it can cause memory leaks if not dealt with properly. If you detach a NodePath from the scene graph expecting it to be deleted and lose track of your instance of MyObject the two will never be garbage collected – and neither will you be able to access either of them to break the reference.

Can Weakref Help?

In short: no. At least, not obviously. I made the mistake of thinking that the problem could be solved by making the back reference a weak reference, which meant that Python’s garbage collection would properly destroy the NodePath once all other references had been removed. For example: 'base', weakref.proxy( self ) )

Unfortuantely you need to have at least one non-weak reference to an object or it will be removed as soon as you drop out of the scope in which it was created, leaving us back with the intial problem. Using weakref doesn’t look like it will be a magic bullet, you would have to use it in conjunction with another solution…

Other Potential Solutions

It would be possible to keep track of each node path’s custom object using a dictionary as part of a manager class, but I think you would eventually come up against the same problem. In order to remove a node path from the scene graph properly you would need to make sure you removed it from the dictionary in the manager object as well.

I considered other wacky solutions like storing values and functions as individual python tags on a node path, but you would need a manager class which knew how to access and run the code. In short, I don’t think it would be a very elegant solution.

So What is the Solution?

At the moment I’m leaning more to the circular reference idea, and adding an additional Break() method to break the circle and allow garbage to be collected normally. I’ve also opted to go with a static method to retrieve the PandaObject from the NodePath, as this keeps things nice and neat; another developer doesn’t have to know the name of the tag this class is hidden in either, so it looks rather clean:

class PandaObject( object ):

    def __init__( self, np ):

        # Store the node path with a reference to this class attached to it = np 'PandaObject', self )

    def Get( np ):

        # Return the panda object for the supplied node path
        return np.getPythonTag( 'PandaObject' )

    def Break( np ):

        # Clear the panda object tag to allow for proper garbage collection
        np.clearPythonTag( 'PandaObject' )

    def DoSomething( self ):
        print 'Hello world!'

This means calling custom methods on our NodePath will look something like this:

pObj = PandaObject.Get( render.find( 'myNodePath' ) )

If we want to completely remove a node, we have to remember to call Break() before detaching it to make sure the circular reference is broken.

In summary, setPythonTag() is your friend. Just make sure to break any circular references you create or otherwise your NodePaths won’t be collected as you might expect. In my next post I’ll show some other creative uses for the technique described above.

3 Comments more...

Full Pipeline Test

by on Jan.03, 2012, under Art, Panda3D, Panda3D Scene Editor, Tech Art

I’ve finally completed a full test of my pipeline, from creating the assets in Maya and Photoshop through to deployment on a webpage. There’s been a number of small issues I’ve had to solve along the way, and I hope to outline some of these in this post. The finished real time warehouse scene can be viewed here (you will be asked to approve a certificate saying that you allow my code to run on your computer after installing the plugin).

Asset Creation

When creating assets I often make use of Maya’s instancing feature. This works well aside from two main issues: the first being that each instance can have its own texture assignment. While I can think of ways where this might be useful, it tends to slow me down as I have to assign the shader to each instanced piece. Perhaps I should switch to the referencing system, but I’ve been bitten by it in the past so I’m probably going to try and avoid it.

The other issue with instances comes when applying a lightmap to the model. In order to get a good UV atlas for the lightmap Maya expects it to be one model; you can use automatic mapping across multiple nodes but Maya won’t lay the resulting shells out without overlap. It’s not a big deal to combine all the meshes together at this stage, but trying to combine a model with instanced pieces doesn’t work too well – they just tend to vanish. If Maya has a “make instance unique” feature then I haven’t found it yet, and if this feature doesn’t exist it would be trivial to write one.

Something else which takes up a lot of time is setting up shaders for lightmapping, then setting them up again for export. The Panda3D export plugin expects shaders to be set up a certain way (Maya Phong materials), and this is typically quite different to the setup I’m using to produce a nice lightmap (mia materials). Once the lightmaps are generated they have to be plugged into the Phong shader using a layered texture, which can be time consuming. I’m planning on automating this shader setup with a couple of MEL scripts to help remove the time between iterations.

Scene Layout and Packing for Deployment with PackP3D

This was the first time using the editor which I have been writing to actually publish something, and a few bugs have started to show. Overall though I was quite happy with the way it worked, although admittedly I was only using it to place a single model and player starting position.

Firstly, the map format writes full paths to the egg models in the xml, which breaks if you then pack / move the project. I’ll have to use relative paths if I don’t want to have to fix the xml before packing. The packing process also builds .bam files for all the .egg models, so I’ll need to change this extension too before packing if the models are to be found.

I also found out the hard way that cElementTree is not supported by the PackP3D process. I suppose this is because cElementTree is implemented in C (and is therefore faster – the main reason why I chose to use it) and relies on a bunch of .lib files located in python\libs. These don’t seem to get packed and aren’t found at runtime. This is trivial to fix however, as it’s easy enough to change:

import xml.etree.cElementTree


import xml.etree.ElementTree

I also had some trouble building some of my modules. I have a helper model which contains a lot of useful snippets and classes – including the wx classes I’m using to build the editor. If I try to pack the project without manually specifying to include the wx modules the resulting p3d file will complain about the missing module. I can include the wx modules but this seems to add bloat to the final file, or at least force the end user to download extra modules. Commenting out any imports which were bringing in wx modules will fix this, but it’s not a long term solution.

Lastly, in order to pack something and release it on the web you must bundle a certificate into your p3d file. This is basically a way where users can identify who is running code on their computer. I created mine using the OS X key chain utility, which made it very easy to create a self-signed certificate using the terminal.

Check out the interactive here!

Leave a Comment more...

Scene Editor Progress

by on Oct.25, 2011, under Panda3D, Panda3D Scene Editor

It’s been a while since my last post as I’ve been busy with work, but I’ve been chipping away at the editor whenever free time is available. I’ve managed to get the entire pipeline working; from creating models in Maya, exporting them to Panda, arranging them in the editor and then getting them to run in my “game”.

Other stuff that I’ve added recently:

  • Selected nodes display their bounding box instead of showing up as red
  • Ability to delete or duplicate nodes
  • Ability to add directional or point lights, as well as empty node paths
  • Basic scene graph outliner

In order to visualize non rendered nodes like lights and locators, I have built some proxy models so the user has something to click and interact with in the viewport. There are two styles of proxies I want to support: one behaves just like a normal model in 3D space, while the other appears 2D and maintains its size in screen space. These models are built using LineSegs, and are typically given collision capsules to roughly outline their shape. When made a child of a node they will inherit their parent’s transform and appear the correct shape on screen. Clicking any collision solid under the node will traverse back up the hierarchy and return the node by using getPythonNetTag() – which is obscenely useful. However I’ve had issues in designing both types of proxies, and have still to come to a solution that I’m entirely happy with.

Panda’s built in collision system does not check lines for collisions, so I need to find a different approach in order to have these nodes selectable. Some users have suggested rendering the entire scene to a texture, then doing a 2D lookup to see if the user has clicked on one of the proxies. Although this approach makes a lot of sense for wire objects, it will probably be too slow to render the entire frame buffer out to texture every time the user clicks on the screen. At the moment I’m using collision solids (capsules, mainly) to approximate the shape of the proxy, then scale the radius of the capsule based on its distance to camera to compensate for distance. This maintains the size of the clickable area for the node and stops the lines becoming increasingly difficult to select the further away the editor camera gets. This works until the user scales up the proxy (something I would like to be supported), and then the capsules end up too big and stop the user from selecting other nodes correctly. I could adjust the radius of the solids by using the inverse matrix of the proxy, but this won’t play nicely when the proxy is scaled non-uniformly. I’ll have to come up with a better idea.

For proxies that maintain screen space size (ambient light / point light) I’ve tried parenting the model to Panda’s 2D render node, aspect2D. Unfortunately this means there is no hierarchial relationship between the 3D light and the 2D proxy that represents it, so the proxy won’t move when the light is transformed. To get around this problem we can add a task to execute every frame which will project the node’s 3D position to its equivalent aspect2D position, and move the proxy to that. Unfortunately this doesn’t solve the problem of selecting the node – I would have to create a collision node in the 3D scene anyway, or implement some sort of 2D picking system. The other alternative is to billboard the model and scale it according to distance to camera. This is probably the better solution as the 2D proxy will never be drawn over a node it is behind, although there are still some issues with drawing the selection bounding box with this technique.

Aside from these and a few more issues, I have something that works! In the video below you can see me create a scene and load that up in first person mode (my ‘game’ – which is pretty much just using the very excellent ODE Middleware developed by a member of the Panda community) and walk around in it. Many thanks to my mate Pete for the use of his models and textures. You can find more of his work here!

5 Comments more...

Rendering Marathon Maps with Panda3D

by on Sep.28, 2011, under Panda3D

I’m still making strides with the editor, but I’ve been pleasantly distracted with something recently. One of my favorite games I used to play years ago was Marathon – a first person shooter I played on my Mac. By modern standards the game play is pretty simplistic, but at the time I was hooked – especially by the computer terminals which exposed the story to the player gradually as they progressed. For a time I got into creating maps and painting textures, but the limitations of a 2.5D engine eventually got to me and I moved onto other things. Several years later I noticed Marathon Rubicon, a rather ambitious 3rd party scenario, which quickly became my favorite scenario to date.

The Marathon community is still active, and many of the old tools have been revamped and updated. One of these, a project called ‘Weland’ is open source and available on SourceForge. I grabbed the level loading code, pythonized it, and plugged it into Panda. There are a few texture issues but the level comes across pretty well.

I’m not exactly sure what else I’m going to do with this; I might just shelve it for now and call it a fun / useful exercise / great way to kill some time. I will say that it’s hard not to imagine converting more content across to the engine – switches, platforms, terminal text, etc – then to light the entire thing with pixel lights and go exploring… 🙂

2 Comments more...

New Project

by on Sep.20, 2011, under Panda3D, Panda3D Scene Editor

I’ve started working on a new project, one of the main reasons I wanted to start this blog in the first place. Since starting off in this industry as an environment artist I’ve always loved the idea of building a space and then exploring and interacting with it. I’ve also been looking to start on something which would allow me to work on something with both artistic and technical elements.

The idea for my project is to use the Panda3D engine to build a scene editor, then using that to build an interactive space. Panda also has a web browser plugin, so I can publish these spaces on this website so people can explore these spaces I create. I figure that this ties a number of areas I want to continue pushing forward – my Python skills, my ability as an artist and a tools programmer.

The idea of developing a complete game editor is not something I’m entertaining at the moment; all I require is to be able to drop models and lights into a scene and then be able to serialze that out to xml. Running the scene on the game side would use the same loader code as the editor.

So far I have some basics working:

  • Mock-up UI done in wxPython, with nested Panda viewport
  • Adding models from file
  • Adding point lights
  • Selection and transform gizmos
  • Saving / loading scenes to / from file
  • Toolbar icons from the awesome Fugue set

With the editor in this basic form, the next step will be to create a simple environment with it. I’m planning on implementing the excellent ODE Middleware written by one of the Panda community members to take some of the load off the player camera, controls and movement so I can concentrate on the editor and making scenes with it.

Leave a Comment more...

Gizmo Update

by on Aug.16, 2011, under Panda3D, Tech Art

After a few long nights coding I think I’m about ready for another release of my gizmo project. I’ll be writing up a full post about it later, but if you’re keen to give it a go you can download the code here (you will need Panda3D installed to run). Or you can watch this thrilling demo:

Version 1.1 has the following fixes and features:

* Fixed intra module importing
* Moved constants out of and into
* Fixed local transforming for rotation / scale
* Added “complementary” scaling when ctrl-clicking an axis of the scale gizmo
* Added middle mouse functionality to continue transforming
* Fixed bug where moving the mouse from the rotation gizmo would stop transforming
* Fixed bug where quickly rotating in camera axis would spin the gizmo wildly
* Added support for attaching multiple nodes
* Added marquee selection for demo
* Fixed scale gizmo appearance during transform
* Added concept of default axis
* All transformations now done with matrices
* General code cleanup

Leave a Comment more...


by on Aug.06, 2011, under Panda3D, Tech Art

I was a little late in getting this blog started, so I might have to back-date a few posts to cover what I’ve been doing so far on this topic.

For the past couple of months I’ve slowly been chipping away at a project using Panda3D, an engine I decided to use due to its extensive Python integration. After a few dismal attempts in learning the api I decided to try and make something useful, and started work on recreating the manipulators from Maya. I’m still ironing out some bugs, but I’ve got something that looks and behaves just like the real thing.

Add an Image

Luckily Panda makes setting things up ridiculously easy; I’ve had no trouble writing basic controls for mouse picking, marquee selection and Maya-style camera controls. The shapes that make up the gizmos are created using Panda’s geometric classes – they aren’t models loaded from file.

I’ll be making another release soon, but you’re interested in Python and have the Panda3D sdk installed, you can get the first release here.

Leave a Comment more...

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!