This document is an introduction to using the mcf.vrml libraries for creating VRML processing scripts and systems. Because of the considerable flexibility of the system, this document only scratches the surface of possible uses. If you find anything in this document or the system which does not work properly, or which doesn't seem fast enough, please contact me, I can't fix what I don't know about. I also can't promise I can fix anything, but I do make an effort in most cases.
mcf.vrml will only load syntactically correct VRML 97 files. Normally, to load a VRML file you will use the convenience functions for file access located in the mcf.vrml.loader module:
from mcf.loader import loadersceneGraph = loader.load( 'c:\\temp\\testfile1.wrl' ) # note use of double backslashes if sceneGraph: loader.save( sceneGraph, 'c:\\temp\\testfile2.wrl' ) # file will be overwritten without warning
The loader.load function provides for url download, and gzip decoding of a url or filename. If an error is encountered during the parsing of the file, you may receive an exception. The most common errors are mcf.vrml.parser.UnfinisedError (a syntax error caused less than the full file to be read), AttributeError, and TypeError.
loader.save is the complement of loader.load. It attempts to save a file to disk (currently there is no url support for this) in UTF-8 format.
The code for creating an "identity transformation", the element-by-element re-creation of a sceneGraph is minimal:
from mcf.vrml.walker import customwalker, vrmlwalkerdef process( sg ): c = customwalker.CustomWalker( ) # default w = vrmlwalker.VRMLWalker( custom= c ) return w.walk( sg )
The process function takes a sceneGraph object as it's input.
It creates a CustomWalker object which describes how we want to
navigate the various nodes of the sceneGraph. It then creates a default sceneGraph walker which uses the
CustomWalker object to control its processing. Having created the walker object, the function tells it to walk
the sceneGraph and return the result to whatever called the
function.
Processing scripts can override the default action of the walker by specifying a "defaultrule" argument (which must be a callable object (see below for details)) to the VRMLWalker contructor.
def printer( curnode, **namedargs): ''' A trivial processing rule body which does not recreate the node ''' print curnode.__gi__ # __gi__ is the node-type of the node... return 1, None... w = vrmlwalker.VRMLWalker( custom= c, defaultrule=printer )
In doing this, the processing script takes over the responsibility for recreating this node if it needs the node recreated. The processing rule body (the defaultrule in this case) can call the CustomWalker object's methods to assist in the task of recreation:
def print_and_recreate( curnode, gi, app, parentlist, CustomWalker, transform, **namedargs): ''' Less trivial rule body which both prints the __gi__ of the node and asks the CustomWalker to recreate the object in the "default" way. ''' print curnode.__gi__ # as for the truly trivial version processchildren, newnode = CustomWalker.rebuild_node( parentlist, curnode, gi, transform) return processchildren, newnode
The CustomWalker also defines methods for calculating an object's "gi", and "childlist" and a "defaultrule" method which figures out what type of rebuilding mechanism to use for a particular type of node.
Note: the new node is returned as the second value. The system will register transform[ id(curnode)] = newnode if this value is not the None object. Without that registration, the default rebuilding process will fail.
Head rules are (strangely enough), rules with heads. A head is a condition which says "if x is true: choose my body as the only processing script to run when we first encounter this node." The rule bodies are the same as the rules used for default rule bodies. The rule heads are a list of required results to some set of (arbitrary) test rules which will be applied to every node in the DAG (A DAG is a more general version of a tree structure). You define both the tests and the required results based on your task-at-hand.
def get_gi( curnode, gi, **namedargs ): ''' A trivial, but very common test function which returns the node-type (gi) of the current node. ''' return gi # Note that it returns only the value to be matched!def depth_over_4( curnode, gi, parentlist, **namedargs ): ''' Returns whether the recursion depth of this node is greater than 4 ''' return len( parentlist ) > 4w = vrmlwalker.VRMLWalker( custom= c, \ defaultrule=c.defaultrule, testfuncs= (get_gi, depth_over_4 ), transformrulelist = [ ( 'PositionInterpolator', 0, print_and_recreate), ( 'ScalarInterpolator', 1, printer ), ( 'Group', None, print_and_recreate) ] )
What those two rules say is:
( 'PositionInterpolator', 0, print_and_recreate) -> PositionInterpolator nodes which are not more than 4 levels deep in the node hierarchy will have their gi printed as they are recreated
( 'ScalarInterpolator', 1, printer ) -> ScalarInterpolators more than 4 levels deep in the node hierarchy will have their gi's printed, but will not be recreated in the new sceneGraph (they have, effectively, been deleted) *see note
( 'Group', None, print_and_recreate) -> All Groups, regardless of their nesting level will have their gis printed as they are recreated
NOTE: The function printer (defined above) returns (1, None) . The 1 signals to the system that this node's children should not be processed. If we had returned a 0 instead, the system would attempt to process the children of the (deleted) node, and would raise exceptions when the default recreation functions looked for the parents of those nodes. Your code must trap such situations and implement whatever action you want taken when a parent cannot be found.
A post rule is a head rule which is executed when the processor finishes processing a node and all of its children. This allows you to be sure that the results of children's processing is available to the functions processing the parent node. Note, however, that, unless you also define a pre-rule which creates a target object, the children's rules will not have anything to which to append themselves. In practice this is not a huge limitation, as we can easily use a Python structure such as a list or dictionary which is created by the pre-rule and used by the post-rule (which looks it up in the transformation table) then discarded by registering a new target node.
The name of the argument for post-processing rules is "posttransformrulelist".
Splicing files together
Eliminating nodes and moving children to another node
Swapping the prototype of a node
The processing environment is designed to be easy-to-use. It allows you to declare only those arguments you require for a particular function.
variable |
function |
curnode | Pointer to the node currently being processed |
parentlist | A combination list/mapping structure which lets you do efficient "last parent of type y" queries. |
ancestorlist | As for parentlist, but only nodes whose gi is in your hierinterestedin mapping. |
Walker | Pointer to the active walker object |
CustomWalker | Pointer to the active CustomWalker object |
root | The root node of the DAG. If a value is to be returned, it must be set as the transformed value of the root node. (see transform below) -> transform[ id(root) ] = returnvalue |
app | Pointer to an "application" object as passed in to the walk method of the walker object. This object can be anything at all, as it is never used or altered by the processing system. |
transform | A mapping of original-node-id's to pointers-to-recreated-nodes. In other words, if you are recreating the scenegraph during processing, this mapping lets you take a pointer to the original node and find out what node in the new scenegraph corresponds to it. ID is a built-in function in Python id( object ) which returns a number which is unique for the lifetime of the object. To register a transformation, create a new entry in the mapping like this: transform[ id( oldobject)] = newobject Note: If you return a second value from your processing scripts which is not None, that value will be registered as the transform of the current node.. |
get_gi | Pointer to a callable object which can retrieve the gi of any node in the DAG |
get_childlist | Pointer to a callable object which can retrieve the childlist of any node in the DAG |
**env | Any number of application-specific objects passed in to the walk method of the walker object as the "env" argument. Each element of this environment can be accessed either as an argument or as part of the "namedargs" argument set. |
generation | The current node's generation (including the curnode) |
childno | The current node's index in the generation, i.e. generation[ childno] is curnode, while generation[ :childno] is the set of siblings preceding this node and generation[ childno+1:] is the set of siblings after this node. |