All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
VOP Concepts

VOPs Overview

Unlike many other contexts in which various data objects flow from one node to another, VOP nodes do not pass anything to each other. Instead, each node generates a piece of code which is then merged together with code from other nodes to form a complete function.

The code is assembled starting from the nodes with no inputs and proceeding onto the nodes they are connected to, and so on. Each input on a node becomes a VEX or an RSL variable, and is assigned a value computed by the code of the node connected to that input. Each subnet gets its own code block within the generated function.

When each VOP_Node cooks, its VOP_Node::getCode() method is invoked, and the string returned by it is appended to the code being generated.

Please note that the final generated code is cached. While in most cases the cache will be reset automatically, if it can also be reset manually:

// Reset VOP cache for a given VOP_Node *vop:
OP_Network *creator = vop->getCreator();
VOP_CodeGenerator *codegenerator = creator ? creator->getVopCodeGenerator() : NULL;

Debug and Bypass Modes

Each VOP_Node can potentially store a set of information objects on a per-output basis. If such an object exists for any given output, and the currently cooked node is in either debug or bypass mode, the output may not generate its normal value, and will instead look at the properties set in VOP_OutputInfo to determine its behavior.

Please note that if all of node's outputs are overridden, it will not generate its normal code at all in debug or bypass modes.

Retrieving Cooked Data

Since VOPs do not cook a data object like most other contexts, but generate a piece of code instead, there is no useful way to get an equivalent of other node types "cooked data" in VOPs. Instead, it is possible to get the generated code as illustrated below:

void getNodeCode(OP_Node* node, UT_String& vfl_code_out)
// Get our creator.
if( node->getOpTypeID() == VOP_OPTYPE_ID )
node = node->getCreator();
if( node->getVopCodeGenerator() )
UT_String fullpath, opfile;
// Generate the full path to the node.
opfile = "op:";
opfile += fullpath;
// Get the actual code
os << ends;
vfl_code_out = os.str();
vfl_code_out = "";

VOP Struct Data Types

It is possible for HDK plugins to provide own VOP struct type definitions that can be used as input and output data type in VOP networks. The hub for managing custom struct types is VOP_LanguageManager class that has methods for registering and unregistering the custom types.

You can register a new type using VOP_LanguageManager::registerTypeDefinition() method. You will need to specify the unique type name and a VOP_TypeDefinitionSource object that provides the actual type definition for that type name. The definition source is responsible to return the type definition when the manager requests it, and thus it must persists, otherwise, if deleted, its base class will automatically unregister the type. E.g.,

class hdk_MyCustomTypeSrc : public VOP_TypeDefinitionSource
/// Returns a custom struct type definition.
virtual VOP_TypeDefinitionHandle getTypeDefinition( const char *type_name )
my_struct->setTypeName( "my_custom_type" );
my_struct->setStructName( "my_custom_type" );
VOP_TypeInfo( VOP_TYPE_INTEGER ) , "my_int_member" );
return VOP_TypeDefinitionHandle( my_struct );
hdk_MyCustomTypeSrc src;
VOPgetLanguageManager().registerTypeDefinition( "my_custom_type", src );

To unregister the custom type from the manager, use unregisterTypeDefinition() or unregisterTypeDefinitionSource(). The first one will unregister a single type, while the second one will unregister all types associated with the given source. The base class destructor will automatically call the second method to unregister any types for which it used to provide actual definitions. E.g.,

If for some reason the VOP type definition changed (e.g., a new member has been added to the struct), you can dirty the definition in the manager, and the manager will ask for a fresh definition when it needs it. E.g.,

If your code needs to respond to any changes in the VOP type definition, you can register an observer and the manager will send notifications whenever the type changes. E.g.,

class my_class {
f( this, &my_class::handleStructDefinitionChange );
addObserver( myNotifierList, f );
void handleStructDefinitionChange(
if( event.getVopTypeName() == "my_custom_type" )
// handle the definition change
UT_NotifierList myNotifierList;