Studierstube scripting mentor

Complex Behavior

Context sensitive traversal

Another advanced scripting technique is the use of context sensitive traversal: The traversal state is extended with a custom element, SoContextElement*, containing a map datastructure that allows to store string-typed key-value pairs. Nodes in the scenegraph can write to or read from the context during traversal, and behave differently depending on the values stored in the context element.

The nodes provided by Studierstube to be used to manipulate and evaluate the traversal context are:

Name Description Fields
SoContext* modifies the SoContextElement in the state of the current action SoSFEnum mode
SoMFName index
SoMFString value
SoContextReport* reports values from the SoContextElement in the state of the current action SoMFName index
SoMFString value
SoMFString defaultValue
SoContextSwitch* switch that traverses differently based on the context element SoSFName index
SoSFInt32 defaultChild
SoContextMultiSwitch* allows to define subsets of its children and to use the SoContextElement to select during traversal which sets of children will be traversed SoSFBool ordered
SoSFName index
SoMFInt32 numChildren
SoMFInt32 defaultChildren
SoContextSeparator* Separator that stops notification that occur during traversal SoSFBool blockNotify

Context-sensitive traversal can be used to parameterize scenegraphs. While it is possible to reference a scenegraph multiple times with the DEF / USE mechanism, the scenegraph will be the same with each traversal. Using context-sensitive traversal, the context can be changed before each reference, allowing the developer to set fields in the scenegraph depending on the context entries. For example, we could extend the simple coordinate system presented earlier to add labels to each axis, defined by the context:

SoContext {                            # initialize context with first name
index "axis-name"
value "Y"
}
DEF AXIS Separator {
Transform {
translation 0 0.15 0
}
Cylinder {
radius 0.003
height 0.3
}
# ... omitting geometry definitions
DEF axisname SoContextReport { # this will report the name to its "value" field
index "axis-name"
}
Text3 {
string = USE axisname.value # use the value field to create the label
}
} Material { diffuseColor 1 0 0 } # change color
Rotation { rotation 0 0 1 -1.57 } # change orientation
SoContext { # change label
index "axis-name"
value "X"
}
USE AXIS # render 2nd axis Material { diffuseColor 0 0 1 } # change color
Rotation { rotation 1 0 0 1.57 } # change orientation
SoContext { # change label
index "axis-name"
value "Z"
}
USE AXIS # render 3rd axis

Note that inventor converts every datatype to and from strings. It is therefore possible to set other field types also by connecting them to the string values in the context.

Python scripting node

A powerful way to include complex behavior into your scene graph is to embed an SoPyScript node into your scene graph and define it's behavior by writing Python scripts. The following example shows how to to that:

DEF Concat SoPyScript {
    fields [ SoSFString a, SoSFString b, SoSFString out ]  # defines the fields present in the script node
a "Time "
b = SoTimeCounter { max 10 }.output
script "
# write any initialisation directly in the script
print 'This script concatenates the inputs a and b and outputs everything on out\n'

# handle_a is called, if a changed
def handle_a():
out.setValue( a.getValue().getString() + b.getValue().getString())

# handle_b is called, if a changed
def handle_b():
out.setValue( a.getValue().getString() + b.getValue().getString())
"
}

Translation { translation 0.1 0.1 0 }
Text2 {
string = USE Concat.out
}

The script node has only two fixed fields. The other fields are defined directly in the file format. The first line always has to start with fields and then contain a description of the additional fields as a list of typename and fieldname. The list is enclosed using [] and can also span several lines.

After the definition of the field names, you can use the fields like any other Open Inventor fields. They can take default values or can be connected to and from.

The Python script itself is put into the field script. It can span several lines and needs to be formatted following the Python indentation rules. Therefore it always has to start in the first column, regardless of the indentation of your Inventor nodes. Within the script you can use any Python code. Code that does not define objects will be executed after the script node is loaded or when the contents of the script field have changed.

Within the script code the fields are available in variables with the same name as the field. The whole Open Inventor API is supported via the Pivy interface. See the Pivy website for details on how to use it. Basically all C++ API calls are available directly and the operators are overloaded as well. There are no shortcuts implemented, therefore for example you have to use getString() to get a char * and consequently a Python string from an SbString object.

A couple a special functions can be defined within the script node to link the fields to functions. If a function with the name handle_fieldname is defined, it is called whenever the field of the given name changes. In our example the function handle_b is called whenever the field b receives a new value from the SoTimeCounter engine.

Another possibility is to override the different methods called from action traversals. If a function with the same name as a virtual action method of the node is found, it is called whenever the corresponding action traverses the script node. For example the function GLRender(action) is called whenever the node is rendered.