pyInterface

Code

Introduction

The class pyInterface defines the basic interface between Ygdrasil and Python, and is used by pyNode, pyTransform, and pySelector. Each of these Ygdrasil node classes uses a pyInterface object to handle common messages and the app() function.

pyInterface defines a Python module "ygdrasil", which contains several functions corresponding to basic Ygdrasil functions. It also defines a Python class "ygNode", which provides several member functions corresponding to the C++ ygNode's member functions.

All Ygdrasil/Python scripts are expected to define a class which is derived from ygNode or one of the other Ygdrasil/Python classes (currently ygTransform & ygSelector). An object of this class will be created when the script is loaded, and its app() member function will be called by ygInterface on each frame.

Example

Here is a very simple example scene and Python script. The script keeps a counter that increments each frame; when it reaches 1000, the counter resets and the event "overflow" is generated. The script also includes a function "ping()" that can is called by a WandTrigger.

Scene file:

    pyNode nodeA (script(example.py), when(overflow, print(overflow occurred)))
    wandTrigger (when(button1, nodeA.ping))

Script file example.py:

      class example(ygNode):
           def __init__(self):
                self.val = 1
           def app(self):
                self.val += 1
                if (self.val > 1000):
                     self.eventOccurred('overflow')
                     self.val = 1
           def ping(self):
                print name(), 'pinged;  val =', self.val

Messages

These messages are available in all Ygdrasil/Python node classes:

script(filename [, classname])
Executes the Python script filename, and creates a Python object of class classname. If no classname is given, the file name, minus its extension, is used (e.g. for "foo.py", and object of class "foo" is created). The Python script is searched for in the standard Ygdrasil search path.
The Python variable for the created object has the same name as the Ygdrasil node. The variable ygNodeName within the object is also assigned the node's name as its value.

e.g., if a scene contains "pyNode nodeA (script(foo.py))", the following actions are performed:
        [C++:]  PyRun_SimpleFile("foo.py")
        [Python:]  nodeA = foo()
        [Python:]  nodeA.ygNodeName = 'nodeA'
  
And then on each frame, the Python command "nodeA.app()" is run.
reload
Causes the script previously loaded by script() to be re-executed, and the node's Python object to be re-created.
autoReset([bool])
If bool is true (or omitted), tells pyInterface to automatically reset the Ygdrasil node any time that the Python script is changed.
On each frame, pyInterface will check the last-modified time on the script file; if this value changes, the message "reset" is sent to the Ygdrasil node. This will cause the script to be reloaded, assuming it was included in the node's initialization messages in the scene file (i.e. not passed to the node by a later message).
Note: I chose to use the "reset" message, rather than just calling the reload() function, for the case where call() messages are also used in the scene file (such as to set values of variables in the script). If reload() were used instead, these call()s would not be re-run, and the new instance of the Python node would not be completely initialized. This is probably an area for further improvement (perhaps using a "scriptChanged" event instead).
This feature is off by default.
call(function)
Executes the Python function function on the node's Python object.
e.g., the message "call(xyzzy(1,2))" sent to pyNode "nodeA" translates into the Python command "nodeA.xyzzy(1,2)".
Note that because of the way this is implemented, it can also be used to assign values to object member variables. e.g. the message "call(plugh=3)" sent to "nodeA" translates into the Python command "nodeA.plugh=3".

ygNode Python class

The Python class ygNode has the following member functions:

name ( )
Returns the name of the node, which is the same as the name of the Ygdrasil node that owns this Python object.
eventOccurred (eventName [,args])
Equivalent to the C++ function ygNode::eventOccurred(). eventName is the name of the event, and the optional argument args is a space-separated string of event arguments, of the form "name=value".
numChildren ( )
Returns the number of child nodes of the corresponding Ygdrasil node.
childName (i)
Returns the name of child node #i of the Ygdrasil node.
parentName ( )
Returns the name of the parent of the Ygdrasil node.
origin ([otherNode])
Returns the origin of the node. If otherNode is omitted, the position is in world coordinates; otherwise, it is relative to the node named otherNode. Equivalent to the C++ function ygNode::origin(); the C++ pfVec3 is translated into a Python 3-tuple.
app ( )
Does nothing. This is just a placeholder function for any Python node classes that don't need to define their own app() functions.

Note that some of the functions (childName, parentName, origin) deal with the names of nodes, where the corresponding C++ ygNode functions use pointers to actual node objects. This is done because there is no guarantee that the other Ygdrasil node being referred to is of a Python-based node class; hence, only the name of the node is used, rather than a Python ygNode object.

ygdrasil Python module

pyInterface also defines the module ygdrasil, which consists of a set of functions that provide the actual connection between Python and Ygdrasil. (This is separate from the ygNode class because of some of the ugly details of Python's embedding/extending system.)

Some of these functions correspond to ygWorld functions, and so were not included as Python ygNode functions. Other functions are primarily used behind the scenes in the Python ygNode class; however, these can also be called directly if desired, such as to allow a Python node to get data from some other non-Python Ygdrasil node.

The functions defined in the ygdrasil module are:

sendMessage (message)
Sends an Ygdrasil message. message is a string containing the message to be sent, such as "nodeA.position(1 2 3)".
frameTime ( )
Returns the current frame time, in seconds, from ygWorld::FrameTime.
frameDeltaTime ( )
Returns the current frame delta time (i.e. the length of the previous frame), in seconds, from ygWorld::FrameDeltaTime.
eventOccurred (nodename, event [,args])
Signals that an Ygdrasil event has occurred for node nodename.
numChildren (nodename)
Returns the number of child nodes of Ygdrasil node nodename.
childName (nodename, i)
Returns the name of child node #i of the Ygdrasil node nodename.
parentName (nodename)
Returns the name of the parent of the Ygdrasil node nodename.
origin (nodename [,otherNode])
Returns the origin of the Ygdrasil node nodename, relative to otherNode. If otherNode is omitted, the position is in world coordinates. The position is returned as a 3-tuple.

Bugs

Illegal node names are possible:
Note: this bug has been corrected in Ygdrasil 0.1.7 (except for cases where a scene file explicitly assigns a node an illegal name).
Because pyInterface uses an ygNode's name for the name of the corresponding Python variable that it creates, the node name needs to be something that will be a legal variable name. Normally this is not a problem, but in the case where the node has not been given a name in the scene, it is assigned one by Ygdrasil. By default, this name is built from the host name (plus process ID); some systems are configured to return their fully qualified internet name, rather than just the basic machine name - e.g. hostname returns "mediastudy1.fal.buffalo.edu" instead of just "mediastudy1". This causes pyInterface to try to create a variable with dots in its name, which will fail.
Use the environment variable YG_DUMMYNAME_BASE, or explicity name all Python nodes, to avoid this for the time being.
Note that really this is a flaw in Ygdrasil, not pyInterface - node names with dots in them could cause problems elsewhere.


Last updated 26 October 2002.
home page