OpenVDB
12.0.0
|
This cookbook provides code snippets that illustrate the use of some new tools that simplify the construction of operators in Houdini. It also shows how to write operators that use OpenVDB.
This section gives usage examples for some general helper classes that aid in the construction of Houdini operators. These helper classes are independent of OpenVDB and can be used in the implementation of any type of operator.
The ParmFactory provides a simplified interface to define the parameters of an operator. Invoking the get method on a ParmFactory produces a new PRM_Template describing a single parameter. For example,
Note that, using a ParmFactory, one need only specify those attributes of a parameter (setDefaults, setTooltip, etc.) that have non-default values.
By default, a parameter’s tooltip is used to describe the parameter on the Help page for its operator. More detailed documentation in Houdini’s wiki markup format can be added with setDocumentation, as in the example above. Call setDocumentation with a null pointer or with an empty string to exclude a parameter from the Help page.
ParmFactory objects may be added directly to a ParmList, which among other things ensures that the list of templates is properly null-terminated. Typically, the ParmList is populated at the time the operator is registered, as in the following example:
The ParmList provides a convenient way of defining switchers (tab menus):
The above generates the following UI:
Switchers can also be nested:
Multi-parms are dynamically-sized parameters that consist of a variable number of child instances. Each child instance is defined by a second ParmList that itself consists of multiple parameters. These parameters’ tokens must include a #
character, which is typically placed at the end of the token.
The above generates the following UI:
A multi-parm’s parameters are accessed by iterating through each child instance:
Note that evaluating the multi-parm gives the number of child instances, and that the instances are numbered starting from one.
The OpFactory is used in conjunction with a ParmList to register a new operator by adding it to the OP_OperatorTable. Among other things, the OpFactory ensures that the operator’s type name follows a consistent naming scheme, that its Help URL is set correctly and that its inputs are labeled. Continuing with the earlier newSopOperator example,
The first argument to the OpFactory constructor is an instance of the OpPolicy class (or a subclass thereof). OpPolicy objects allow for customization of certain behaviors of the OpFactory. The base class specifies a policy for converting an English operator name like "My SOP"
, which appears in menus and other UI elements, into an operator type name. The default policy is simply to call UT_String::forceValidVariableName on the English name.
If the policy does not specify the URL of a Help page, or if the OpFactory is constructed without an OpPolicy, then documentation for the operator in Houdini’s wiki markup format can be provided with setDocumentation. By default, documentation for the operator’s parameters is generated automatically and appended to the text provided with setDocumentation.
When a particular OpPolicy is to be used to register multiple operators, it might be convenient to subclass OpFactory itself and provide a constructor that automatically initializes the base class with the desired policy.
If an operator ever needs to be renamed, call OpFactory::addAlias with the old name. This will help to ensure that scene files in which the operator was saved with the old name can still be read:
If the operator name changed as a result of an OpPolicy change, supply the old operator type name directly, with addAliasVerbatim:
Among other OpFactory features, OpFactory::setObsoleteParms accepts an additional ParmList of parameters that are no longer in use but that might still exist in older scene files:
Override OP_Node::resolveObsoleteParms to convert the values of obsolete parameters into values of current parameters where appropriate.
A ScopedInputLock locks the inputs to an operator and then automatically unlocks them when it (the lock object) goes out of scope, even if an exception is thrown.
OpenVDB SOPs are derived from the SOP_NodeVDB base class which, among other things, adds guide geometry and node-specific info text.
A typical SOP will have at least one group name parameter for each of its inputs. These parameters should be defined as follows (note the use of spare data to specify the input):
Associated with each parameter is a menu of primitive group names. Users can select one or more groups from the menu, or create new groups on the fly using Houdini’s @attr=value
syntax. For example, entering @name="density*"
as the group name creates a new group comprising all input primitives whose name begins with density
. Entering @vdb_value_type=float
creates a new group of input grid primitives whose data type is float
. Users may enter multiple space-separated group names or grouping expressions.
In cookMySOP, the string value of a group parameter is used to construct a GA_PrimitiveGroup. (SOP_NodeVDB provides a convenience method, SOP_NodeVDB::matchGroup, to simplify this step and to handle errors in a standard way.) The GA_PrimitiveGroup so constructed may be iterated over using either a VdbPrimCIterator, for read-only access, or a VdbPrimIterator, for read/write access:
Recall that a Grid is a container for a transform, metadata and a Tree, and that the Tree holds voxel data of a specific type (bool
, float
, vec3s
, etc.). Whenever possible, try to write generic grid processing code. That is, write code that can handle grids of more than one type (int
, float
and double
, say, instead of just float
) or, ideally, grids of arbitrary type.
Writing generic code can be tricky, but convenience functions exist to make the job easier. Use GEOvdbProcessTypedGrid to call a method or methods on a primitive’s grid, regardless of its type:
GEOvdbProcessTypedGrid accepts a primitive and a functor (an object for which the call operator, operator()()
, is defined). The functor’s call operator must be templated on a single type (the grid type) and must accept a single argument (a reference to a grid of the template type). The operator’s return value is ignored, so it’s best to declare it void
. The following is a simple example of a functor that satisfies these conditions:
PruneOp can call the prune method on a grid of any type, but it’s necessary to know the specific type, because prune takes an optional tolerance argument whose type is the type of the grid’s voxel values. (Note that grids also have a pruneGrid method that doesn’t require knowledge of the voxel value type.)
Because a functor is an object, it can have member variables. This makes it possible for the functor to process more than one grid at a time, even though it gets called with only one grid. This example shows how one might compute the CSG union of two level set grids, A and B, of the same type:
Certain operations might make sense only for grids with scalar or with vector voxel values. Variants of GEOvdbProcessTypedGrid exist to handle those cases. For example, GEOvdbProcessTypedGridScalar and GEOvdbProcessTypedGridVec3 invoke a functor only on grids of scalar or 3-vector types, respectively, and they return false
for and ignore grids of all other types.