OpenBlox scene graph library (obengine.scenegraph)

New in version 0.7.

Overview

obengine.scenegraph provides a simple, SQLite [1]-based scene graph implementation. Before OpenBlox 0.7, the only “scene graph” available was obengine.datatypes.AttrDict, and that class had several shortcomings:

  • Scene nodes were required to use unique names - this was unacceptable for OpenBlox 0.7, with the release of BloxWorks (with which users would make large [> 2,000 element] worlds with many elements sharing the same name)
  • If scene nodes had duplicate names, they overwrote each other, without any errors or warnings whatsoever
  • There was no concept of parenting, or nested nodes

To address these issues, this module, obengine.scenegraph, was born.

Concepts

Each node on the scene graph has its own unique ID number (node ID/NID) - no two nodes (no matter when or where they were created) have identical ID numbers. So, each node’s primary means of identification is through its NID. The benefits of using node IDs to access your nodes are:

  • O(1) time when finding the owning node of a NID from an obengine.scenegraph.SceneNode
  • Each NID will never change - you can refer a node by its NID for the duration of its existence
  • Each NID is globally unique
  • You can have several scene nodes with identical names, and you can still differentiate between them using their NID

There are 2 main weaknesses with NIDs:

  • The main problem with NIDs is that since each NID is automatically generated, you must hold a reference to the owning scene node to retrieve its NID (or you can look up its NID in BloxWorks)
  • They aren’t very expressive - constants that stand for NIDs are about as good as it gets with respect to readability

Because of these shortcomings, each scene node also has a user-defined name, which can also be used to access a scene node. The advantages of using scene node names are:

  • They are extremely expressive and easy to remember
  • You already know the node’s name, since you defined it; this is related to the first advantage

However, node names also have some problems:

  • Since it’s a possibility that more than 1 node can have the same name (especially at the root level), node names cannot be used to access a node where there is more than 1 node with the same name
  • Searching for nodes using names with obengine.scenegraph.SceneNode.get_child_by_name takes O(N) time

It is up to you to decide which identification method better suits your purposes. The best strategy right now looks like this:

  • Use NIDs for nodes (especially bricks, scripts, and the like) that you’ll never access from a Lua script
  • Use a unique name for each node you want to access from a Lua script

Examples

Basic usage:

>>> from obengine.scenegraph import *
>>> sg = SceneGraph()
>>> n1 = SceneNode('Node 1')
>>> sg.add_node(n1)
>>> print sg.get_node_by_name('Node 1').name
Node 1

Nodes can have names, but they must be unique within their scope, i.e, their parent can have no other children with the same name, if you want to be able to use obengine.scenegraph.SceneNode.get_child_by_name:

>>> n2 = SceneNode('Node 2')
>>> n3 = SceneNode('Node 2')
>>> sg.add_node(n2)
>>> n3.parent = n1
>>> print n1.get_child_by_name('Node 2').name
Node 2

But, if we try to use obengine.scenegraph.SceneGraph.get_node_by_name, what happens?

>>> print sg.get_node_by_name('Node 2').name
Traceback (most recent call last):
   ...
AmbiguousNameException: Node 2

This occurred because the scene graph wasn’t able to figure out which node with the name Node 2 you wanted: n1 or n2.

Module reference

Inheritance diagram of obengine.scenegraph

exception obengine.scenegraph.AmbiguousNameException
Raised when a requested name has multiple occurences, i.e, there are multiple nodes with the requested name.
exception obengine.scenegraph.NoSuchIdException
Raised when a requested ID doesn’t exist.
exception obengine.scenegraph.NoSuchNameException
Raised when a requested name doesn’t exist.
exception obengine.scenegraph.NodeIdExistsException
Raised when a node’s ID already exists on the scene graph, and the error_on_exists argument given to SceneGraph.add_node is True.
class obengine.scenegraph.SceneGraph(owner=None)

A SQLite-based scene graph.

add_node(node, error_on_exist=True)
Adds node to the scene graph. All of node’s children are added, as well. If error_on_exist is True, then NodeIdExistsException is raised if node’s NID is already used.
find_node_by_name(name, starting_nid=-1, fail_on_ambiguous=False, exc_on_non_existent=True)

Example:

>>> sg = SceneGraph()
>>> n1 = SceneNode('Node 1')
>>> n2 = SceneNode('Node 2')
>>> sg.add_node(n1)
>>> n2.parent = n1
>>> print sg.find_node_by_name('Node 2').name
Node 2
get_node_by_id(nid)
Retrieves the node with NID nid from the scene graph. If the requested NID couldn’t be found, NoSuchIdException is raised.
get_node_by_name(name)

Retrieves the node named name from the scene graph. NoSuchNameException is raised if the requested name couldn’t be found; AmbiguousNameException is raised if more than one node with the given name was found.

NOTE: AmbiguousNameException is raised if there are more than one nodes with the given name on the entire scene graph; i.e, the entire scene graph is queried at once. So, if you have a node named “Name”, and a child node also named “Name”, then if you call SceneGraph.get_node_by_name(‘Name’), then AmbiguousNameException is raised.

remove_node_by_id(nid)

Removes a node (with NID nid) from the scene graph. It also recursively removes all its children.

If the requested NID isn’t found, NoSuchIdException is raised.

remove_node_by_name(name)

Removes a node (with name name) from the scene graph. It also recursively removes all its children.

If the requested name couldn’t be found, NoSuchNameException is raised. Also, if more than one scene graph node is named name, then AmbiguousNameException is raised.

exception obengine.scenegraph.SceneGraphException
Base class for scene graph-related exceptions.
class obengine.scenegraph.SceneNode(name, parent=None)

Generic scene node. You’ll probably want to subclass this class, instead of using it directly.

find_child_by_name(name, fail_on_ambiguous=True, exc_on_non_existent=True)

Recursively searches this node’s children for the first node named name. If multiple nodes named name are found at the same depth, if fail_on_ambiguous is False, then the first match is returned (be very careful with this). Otherwise (if fail_on_ambiguous is True, the default), AmbiguousNameException is raised. This method also raises NoSuchNameException if no node named name was found, and exc_on_non_existent is True.

Example:

>>> sg = SceneGraph()
>>> n1 = SceneNode('Node 1')
>>> n2 = SceneNode('Node 2')
>>> n3 = SceneNode('Node 3')
>>> n3.parent = n2
>>> n2.parent = n1
>>> sg.add_node(n1)
>>> print n1.find_child_by_name('Node 3').name
Node 3
get_child_by_name(name)

Returns the child named name from this node’s list of children, If this node doesn’t have a child named name, then NoSuchNameException is raised.

Otherwise, if this node has more than one child named name, then AmbiguousNameException is raised.

WARNING: This method can be slow if this node has a lot of children (> 10,000).

Example:

>>> sg = SceneGraph()
>>> n1 = SceneNode('Node 1')
>>> n2 = SceneNode('Node 2')
>>> sg.add_node(n1)
>>> n2.parent = n1
>>> print n1.get_child_by_name('Node 2').name
Node 2
name

The name of this scene node. Example:

>>> sg = SceneGraph()
>>> n1 = SceneNode('Node 1')
>>> sg.add_node(n1)
>>> print sg.get_node_by_name('Node 1').name
Node 1
>>> n1.name = 'Node 1 - changed name'
>>> print sg.get_node_by_name('Node 1 - changed name').name
Node 1 - changed name
>>> print sg.get_node_by_name('Node 1')
Traceback (most recent call last):
    ...
NoSuchNameException: Node 1
remove_all_children()
Removes all this node’s children, and recursively removes all their children, as well.
remove_child(nid)

Removes child with NID nid from this node’s list of children. It also recursively removes all that child’s children, as well.

If this node doesn’t have a child with the given NID, then NoSuchIdException is raised.

Footnotes

[1]http://sqlite.org - the SQLite website

Table Of Contents

Previous topic

OpenBlox’s plugin system

Next topic

Testing OpenBlox

This Page