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.
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.