This document describes the (partial) VRML97 implementation provided by the OpenGLContext and vrml packages. The VRML97 ISO standard is a large and complex specification, and the OpenGLContext implementation supports only a small subset of its functionality, hopefully a useful subset. You can load, render and re-save scenegraph's from VRML97 files, as well as render a number of the more common nodes.
OpenGLContext is only a partial implementation of VRML97.
It provides a subset targeted at the output of common 3-D modelers. In
particular, the nodes which are implemented (at least partially) by
OpenGLContext are as follows:
In addition, OpenGLContext provides a partial implementation of the
VRML97 NURBs
extension proposal by Blaxxun Interactive:
And OpenGLContext provides a number of specialized nodes which are
not part of the VRML97 specification:
Despite the small number of implemented nodes in OpenGLContext, the
parser and scenegraph structures of the vrml package support the entire
VRML97 specification, so although particular nodes may not be rendered,
many scenes may be loaded which include nodes other than those in the
lists above.
Basic operation of the VRML97 loader is as
follows:
from OpenGLContext.loaders.loader import Loader
scenegraph = Loader.load( myurl, baseURL=None )
The URL can be either a single or multi-value string (i.e. a list of
strings). If the base URL is not None, then the urls in myurl
will
be interpreted relative to the base URL. If the URL is a
filename,
the file will be opened, read, potentially un-gzipped, parsed, and
converted to a scenegraph instance.
The VRML97 parser is based on SimpleParse 2.0.0, and uses the "cut" production to raise SyntaxError's when a file isn't properly formed. You can catch those, as well as IOErrors around the call to "load". Note, however, that for certain resources (image textures at the moment), the actual loading of the textures is done in separate worker threads, which will print messages if errors are encountered during loading.
You can register a new prototype for use by the VRML97 loader by
calling the standardPrototype function in the loader module with your
new prototype as an argument:
vrml97.standardPrototype( myNewPrototype )
The prototype's name, as reported by
vrml.protofunctions.name(prototype) will then be bound to your
prototype
during loading. Note that you can replace any built-in prototype
with this mechanism as well.
Basic operations with a scenegraph...
# Get a particular node-instance by it's VRML DEF name
myNamedNode = scenegraph.getDEF( "My-Named-Node" )
# Give an instance a new VRML DEF name
scenegraph.regDefName( "My-New-Name", myNamedNode )
# Get a prototype from the scenegraph's namespace
prototype = scenegraph.getProto( "Transform" )
The protofunctions module provides for "safe" manipulation and query of node and prototype structures. Although you can normally access a node's DEF name as node.DEF, it's root scenegraph as node.root, and it's field definition as node.__class__.fieldname, it is possible for a prototype to define a field "DEF" or "root" which shadows those convenience properties. And using node.__class__ just isn't recommended anywhere.
# get the root scenegraph for a node
scenegraph = protofunctions.root( node )
# get a particular field definition for a node/prototype by name
field = protofunctions.getField( node, 'name' )
field = protofunctions.getField( proto, 'name' )
# get all fields for a node/prototype
fields = protofunctions.getFields( node )
fields = protofunctions.getFields( proto )
Sample of interacting with a VRML node...
from OpenGLContext.scenegraph import basenodes
transform = basenodes.Transform (
translation = [0, 1,0],
rotation = [0, 1,0,3.14159],
children = [
basenodes.Group ()
],
)
print "Transform Translation", transform.translation
transform.translation = [2,3,4]
print "After Alteration", transform.translation
for child in transform.children:
print child
Field-type equivalents. Note that there is coercian support...
VRML FieldType |
SFField Python Data Type |
MFField Python Data Type |
---|---|---|
[SF/MF]Int32 |
int |
Numeric-Python int array |
[SF/MF]Float |
float |
Numeric-Python double array |
[SF/MF]String |
string (interpreted as UTF-8
unicode) |
list of strings |
[SF/MF]Time |
float (time-module float) |
Numeric-Python double array |
SFBool |
int (0/1) |
|
[SF/MF]Vec2f |
2-item Numeric-Python double array | x*2 Numeric-Python double array |
[SF/MF]Color, [SF/MF]Vec3f |
3-item Numeric-Python double
array |
x*3 Numeric-Python double array |
[SF/MF]Rotation |
4-item Numeric-Python double
array |
x*4 Numeric-Python double array |
SFImage |
Numeric-Python int array |
Most rendering nodes in OpenGLContext are written to take advantage
of the built-in scenegraph cache. The cache is used to store
partial rendering solutions (for example, tessellations of vertex-based
geometry) in order to allow per-frame operations to proceed as quickly
as possible. The cache operates by watching for changes to fields
on nodes to invalidate the cached data. Each node can have any
number of cached pieces of data.
If you are wanting to write your own rendering node, the
IndexedLineSet node provides a good example of using the cache for
storing a display-list where the cache should be invalidated based on a
number of different fields of two different nodes.
If you would like to create a new node-type (prototype), you can
either define the prototype using a VRML97 prototype or create it
directly in Python. Prototypes are implemented as Python classes
inheriting from the vrml.node.Node class. Normally you will also
want to inherit from one of the marker classes in
vrml.vrml97.nodetypes, which is what tells the scenegraph what roles
your node can play in the graph.
You can find a very straightforward example of defining new
node-types in OpenGLContext.scenegraph.extrusions, where three
(non-standard) geometric node-types are added to the system.
To make your new node-type available to the VRML97 loader, you can
call OpenGLContext.loaders.vrml97.standardPrototype( cls ) on your
prototype class. This allows nodes in your VRML97 files to
reference the nodes as built-ins.
There is a lot of sample code for creating your own scenegraph nodes
in the scenegraph sub-package. If you run into difficulties, feel
free to contact Mike about
them.
OpenGLContext has a more limited event model than that specified in
VRML97. It allows for routing, and provides basic event
setting/getting with the ability to define handlers for the events, but
it does not attempt to implement the non-deterministic VRML behaviour.
Each field on each node sends a PyDispatcher event on setting,
deletion or routing to an event. The field and event classes are
both descriptors. They provide all sorts of support machinery for
working with the field, including support for copying the node, and
generating prototype descriptions. The field class has a very
small C accelerator function which replaces the fget method (when
available), without this performance will noticeably suffer.
You can define a method to be called before an event (but not a
field) is set simply by defining a method named on_fieldname where fieldname is the name of the
field. There are few examples of this in OpenGLContext, as this
is a new feature in version 2.0.0 final. You will see it in the
OpenGLContext.scenegraph.interpolators module.
ROUTE objects can be constructed either in VRML97 files, or by
constructing the routes directly. ROUTEs are just regular nodes
with 4 fields, source, sourceField, destination, and
destinationField. On creation they are bound (i.e. they start
routing between the source and destination).
Note that a ROUTE does not do a regular set on a field to
which it is routed. It sets the value, but instead of sending a
('set',destinationField) message, it sends a ('route',destinationField)
message. This allows code to distinguish between a user sending a
message by setting a field and an event cascade setting the
field. The ROUTE machinery watches for the
('route',destinationField) messages in order to do continued forwarding.