HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Working with Nodes

Topics

Paths

Every node in Houdini has a unique path. To obtain the full path of a node, use the OP_Node::getFullPath() function. To obtain the relative path a node to some ancestor node, use OP_Node::getRelativePathTo().

OP_Node * base;
OP_Node * node;
node->getFullPath(path); // get full path of 'node'
node->getRelativePathTo(base, path); // get path of 'node' relative to 'base'

Given a node path, we can find the corresponding OP_Node via the OP_Node::findNode() function.

OP_Node * node;
node = OPgetDirector()->findNode("/obj/geo1"); // find via absolute path
node = node->findNode("file1"); // find via relative path
node = node->findNode("/obj/geo2/file1"); // OK as well

Often times, you will want obtain a specific type of the OP_Node instead. This is defined by the OP_Node::findFOONode() functions where FOO is the desired node type. If the path doesn't reference a node of the desired type, then these functions return NULL.

OBJ_Node * obj_node = OPgetDirector()->findOBJNode("/obj/geo1");
SOP_Node * sop_node = OPgetDirector()->findSOPNode("/obj/geo1/file1");

Creating Nodes

To create a node, you create it as a child of some parent node. Thereafter, you should call its creation script. Upon success, you can now manipulate the node such as connecting its inputs and outputs, setting parameters (Working with Parameters), and/or positioning the node.

bool
someFunction(OP_Context &context)
{
float t = context.getTime();
OP_Network * parent;
OP_Node * node;
OP_Node * input;
// create node
parent = (OP_Network *)OPgetDirector()->findNode("/obj");
node = parent->createNode("cam", "cam1");
if (!node)
return false;
// run creation script
if (!node->runCreateScript())
return false;
// set parameters
node->setFloat("t", 0, t, 1.0f); // set tx to 1.0
node->setFloat("t", 1, t, 2.0f); // set ty to 2.0
node->setFloat("t", 2, t, 0.0f); // set tz to 0.0
// connect the node
input = parent->findNode("null1"); // find /obj/null1 as relative path
if (input)
{
node->setInput(0, input); // set first input to /obj/null1
}
// now that done we're done connecting it, position it relative to its
// inputs
return true;
}

Traversing Nodes

There are several ways to traverse the node hierarchy explicitly as well. One way is to iteratively walk along a node's inputs using OP_Node::getInput() or OP_Node::getInputReference(), or to walk its outputs using OP_Node::getOutputNodes(), OP_Node::getOutputItem(), or using and OP_OutputIterator object. To iterate over the child nodes, one can use OP_Node::getChild(). For an example of how to do this, see the traverse.C example. A higher-level method to do this is to use supply a callback to the OP_Node::traverseInputs() and OP_Node::traverseChildren() methods.

bool
nodeCallback(OP_Node &node, void *data)
{
const char * prefix = (const char *)data;
node.getFullPath(path);
cout << prefix << ":" << path << endl;
}
void
printChildren(OP_Node *parent, const char *prefix)
{
parent->traverseChildren(&nodeCallback, (void *)prefix, true);
}

Flags

Users can set various flags on nodes from the network view pane. Although these are defined at the OP_Node level, which ones get used depend on the network type. For example, although the render flag exists at the OP_Node level, OBJ_Node's ignore them. Here are the main ones:

For certain network types (eg. OBJ_Node), it can only have one child node (eg. SOP_Node) that has its display or render flag set. To obtain the child display or render nodes use OP_Network::getDisplayNodePtr() or OP_Network::getRenderNodePtr() respectively.

Selections

Node selections in Houdini are implemented via the "picked" flag on OP_Node. To add a node to the selection, call OP_Node::pickRequest(false). To replace the global node selection with just a single node, call OP_Node::pickRequest(true). To manipulate the entire node selection, OP_Director provides higher level functions.

OP_NodeList picked_nodes;
node1->pickRequest(true); // clear node selection and then select node1
node2->pickRequest(false); // add node2 to the selection
OPgetDirector()->clearPickedItems(); // just clear the selection
OPgetDirector()->getPickedNodes(picked_nodes); // obtain all selected nodes
// get the last selected node
// last selected child of parent
OP_Node *node4 = parent->getCurrentNodePtr();

Grouping Nodes in Bundles

As mentioned in the Node Organization, an OP_Bundle abstracts a set of nodes contained inside that bundle. To add, modify, or delete an OP_Bundle, you will need to work with the OP_BundleList class, which owns all the bundles in Houdini. There is only one global list of bundles in Houdini and it can be obtained by:

Standard Bundles

Once you obtain an OP_BundleList pointer, you can then add and remove bundles in this list, and the list will take care of sending notification and other necessary processing. For example:

// create a new bundle called 'my_bundle'
OP_Bundle * new_bundle = list->createBundle( "my_bundle" );
// remove the bundle we just created
list->destroyBundle( "my_bundle" );

Once you obtain an OP_Bundle pointer you can modify the contents of the bundle. However, it is best to use the OP_BundleList methods for modifying a bundle because the list creates undo blocks and also sends notifications to interested parties that the bundle contents has changed. For example:

OP_BundleList * bundles;
OP_Bundle * bundle;
// obtain the bundle named "my_bundle"
bundles = OPgetDirector()->getBundles()
bundle = bundles->getBundle( "my_bundle" );
// obtain the currently selected nodes
// add or remove the nodes to or from the bundle
if( add_nodes )
bundles->bundleAddOps( bundle, nodes );
else
bundles->bundleRemoveOps( bundle, nodes );

Finally, you can obtain the member nodes using OP_Bundle::getMembers().

Globbing

OP_Bundle class can search for any node in Houdini scene based on the node path or a name using globbing. You can use this ability any time you want to get a complete set of nodes based on some name pattern. Then you can obtain the list of nodes from the bundle for processing. For example, the viewport may want to display only the object nodes that match a visible mask pattern. The user may specify "mynode*" as such a pattern, which would match any node that starts with "mynode" prefix. In such case, the OP_BundleList::getPattern() may be utilized for that purpose:

OP_Bundle * bundle;
OP_Network * mgr;
UT_String mask( "mynode*" );
UT_String bundle_name;
// get a manager that contains objects
mgr = OPgetDirector()->getManager( "obj" );
// get a bundle that contains nodes matching the mask pattern. The 'creator'
// argument specifies the root at which tree search for nodes begins. The
// 'relative_to' specifies a node with respect to which relative references in
// the pattern are specified. The 'filter' names the built-in filter that
// accepts or rejects the nodes; in this case the "!!OBJ!!" specifies a built-in
// filter that accepts only object nodes.
bundle = OPgetDirector()->getBundles()->getPattern( bundle_name,
/*creator=*/ mgr,
/*relative_to=*/ mgr,
/*pattern=*/ mask,
/*filter=*/ "!!OBJ!!");
// get the node list for processing
bundle->getMembers( nodes );
// ... processing ...
// release the internal bundle created by getPattern()

Parameter Bundles

Very often an operator has a parameter that specifies a node path or node names. It is very useful to allow the node names to contain globbing characters, so that many nodes can be matched with a simple pattern. When the operator node cooks, it needs to obtain the nodes that match the pattern specified in a parameter. There is a utility method OP_Node::getParmBundle() which returns a bundle containing the nodes matching a given pattern. In addition, this method registers an interest in the bundle, so that the operator is dirtied when the bundle contents changes, for example, when a new node is added and it matches the pattern, or when a member node is renamed and it no longer matches the pattern. Also, when the operator gets deleted, the bundle is properly dereferenced.

The following code demonstrates how to use OP_Node::getParmBundle() method.

HDK_MyOperator::getNodesToProcess( float t )
{
OP_Bundle * bundle;
const char * parameter_name = "nodelist";
int vector_index = 0;
evalParameterOrProperty( parameter_name, vector_index, t, mask );
return getParmBundle( parameter_name, // name of the parameter
vector_index, // component index i
// .. (for vector parameters)
mask, // globbed pattern
getCreator(), // root at which to look for nodes
NULL); // name of an additional filter;
// .. NULL means no extra filtering
}

Casting Nodes

To convert an OP_Node pointer to a specific type, there are CAST_FOONODE() defines where FOO is the desired type. They are a wrapper around the OP_Node::castToFOONode() methods that take into account the possibility of the node being NULL.

OBJ_Node * obj_node = CAST_OBJNODE(node);
SOP_Node * sop_node = NULL;
if (obj_node)
sop_node = CAST_SOPNODE(obj_node->getChild("file1"));

Cooking Nodes

Although a node's purpose in life varies depending on the network type, once a node has been defined and instantiated in a network, the OP_Node::cook() method becomes the focal point for cooking the network and generating data. This method performs preliminary testing and preparation of inputs before actually calling the pure virtual OP_Node::cookMe() method which must be defined in the network type subclass to actually perform the cook. It is also worth noting that not all calls to the cook() method will result in calls to the subclass cookMe() method. The cook() method will avoid doing an actual "cook" if it deems it to be unnecessary. A cook might be unnecessary if the data is already cached and nothing critical (eg. parameters) has changed since the previous cook. If you want to force cook() to always perform a full cook, first call OP_Node::forceRecook().