Working with the Scene Graph.
The scene
module provides a small but powerful set of functions for finding, filtering, getting and setting data from and to the scene graph.
Every node in the scene graph has a name
, a type
, and a set of plugs
. Each plug consists of a stack of Operators. This stack of operators will be evaluated, providing the result for each plug, that is then used in the display of each node.
Querying the scene graph, the find
and filter
functions.
Both find
and filter
provide ways to search through the scene graph. find
will always return one result (or null
), whereas filter
will always return an array of results (that might be empty).
Each query function can be passed an object that describes what node or nodes we are looking for. When searching, we are either looking for a node, in which case the id of the node will be returned, or we are looking for an operator or the property of an operator, in which case an array representing the path to that property will be returned.
For example, to find a node with the name of 'Box':
// Returns the id of the node named 'Box', or `null`:
var boxId = scene.find({name: 'Box'});
However, if we are looking to get or set the translation property of the Box, we would ask for the operator with the translation property:
var path = scene.find({name: 'Box', plug: 'Transform', property: 'translation'});
// path is now ['uuid', 'Transform', 0, 'translation']
If we are search for all results that match, we will use the filter
function:
// All nodes named 'Box':
var boxes = scene.find({name: 'Box'});
// All nodes named Box with a Transform plug and the translation property
var paths = scene.filter({name: 'Box', plug: 'Transform', property: 'translation'});
We can also pass in a regular expression instead of a string, or use *
to build dynamic queries:
scene.find({name: 'Box*'}); // All nodes that start with Box
scene.find({name: /^box/i}); // Case insensitive query for nodes that start with box.
Get and Set data
These paths can then be used to pass to get
, getAll
, set
, or setAll
. For instance, to get
the value of that translation
property:
var path = scene.find({name: 'Box', plug: 'Transform', property: 'translation'});
var translation = scene.get(path);
// Move the node higher, by increasing the translation along the y axis:
scene.set(path, {x: translation.x, y: translation.y+1, z: translation.z});
Or find all of the node names of type 'PolyMesh':
var paths = scene.filter({type: 'PolyMesh', property: 'name'});
var names = scene.getAll(paths);
// names is an Object with the shape: {[id]: name};
Similarly, we can pass the result of filter
directly to setAll
to set a number of nodes at once.
// Find all PolyMesh nodes that are set to invisible
var paths = scene.find({type: 'PolyMesh', plug: 'Properties', properties: {visible: false});
// And make them visible
scene.setAll(paths, true);
We can pass the query objects directly to get
, getAll
, set
, and setAll
, simplifying the code a little:
// get the translation of `Box`
scene.get({name: 'Box', plug: 'Transform', property: 'translation'});
// hide all meshes
scene.setAll({type: 'PolyMesh', plug: 'Properties', property: 'visible'}, true);
Using the string shorthand for querying.
When building queries through code, passing in Object literals makes it easy. However, when composing queries manually, this can be a bit cumbersome. The query interface therefore supports the old ctx
string based queries.
- Use a plain string will find by node name
- Add '%' to filter by type (eg. %PolyMesh)
- Add '#' to search inside a plug
- Add [property=value] to search for an Operator with a specific property value (eg.
[name=Transform]
,[visible=true]
). - Add [property] to return the path to that property (which must exist).
For example:
find('Box') // returns first node named 'Box'
filter('Box*') // returns a list of all nodes starting with Box.
filter('Box*%PolyMesh') // Only PolyMesh nodes starting with Box
find('Box#Transform[translation]') // Finds the translation property on the Transform operator
find('Box#PolyMesh[name=Twist][angle]') // Finds a mesh operator named Twist, and returns the path to the `angle` property.
Finding evaluated results.
While often there is a one-to-one mapping between operator data and the rendered data, keep in mind that each plug has an operator stack, and an operator further down the stack might modify that result. So an operator might shift a translation value, so getting the translation from the Transform operator might not actually be where that node is.
To get the correct value, we need to get data from the evaluated scene graph. We can query for evaluated results with the evalPlug
key.
var transform = scene.get({name: 'Box', evalPlug: 'Transform'});
With a string based query, the prefix for the evalPlug
is !
:
var translation = scene.get('Box!Transform[translation]');
Matching children
from
By default, find
and filter
will search heirarchically from the active scene node. You can start the search from a different node by setting the from
property on the search query.
For example, to find the first child light from a Null named 'My model':
scene.find({ from: { type: 'Null', name: 'My model' }, type: 'Light' });
from
is a query, and can therefore be a query object, a string query, or the result of a previous find. So the above query can also be performed as follows:
var nullNode = scene.find({ type: 'Null', name: 'My model' });
scene.find({ from: nullNode, type: 'Light' });
scene.find({ from: 'My model%Null', type: 'Light' });
includeParent
By default, filter
will include the scene node in it's match list, unless you ask to start the search from a different node. You can control whether the root node is matched with the option includeParent
:
// return all `Null` nodes, including the parent null named 'My Model':
scene.find({ from: { type: 'Null', name: 'My model' }, type: 'Null', includeParent: true });
shallow
Filter will also include the full heirarchy by default. You can ask for only the direct children of a node with shallow
:
scene.filter({ from: { name: 'My model' }, shallow: true });
By combining this with setAll
, you can hide all of a Null
nodes children:
scene.setAll({from: 'My Null', plug: 'Properties', property: 'visible'}, false);