578 lines
19 KiB
Plaintext
578 lines
19 KiB
Plaintext
/**
|
|
|
|
@page houdini Houdini Cookbook
|
|
|
|
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.
|
|
|
|
|
|
@section sHoudiniContents Contents
|
|
- @ref sUIConstruction
|
|
- @ref sParmFactory
|
|
- @ref sOpFactory
|
|
- @ref sScopedInputLock
|
|
- @ref sOpenVDBOperators
|
|
- @ref sListOfIncomingGrids
|
|
- @ref sIteratingOverGrids
|
|
- @ref sProcessingTypedGrids
|
|
|
|
|
|
@section sUIConstruction General operator construction
|
|
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.
|
|
|
|
|
|
@subsection sParmFactory ParmFactory and ParmList
|
|
The @hulink::ParmFactory ParmFactory@endlink provides a simplified
|
|
interface to define the parameters of an operator.
|
|
Invoking the @hulink::ParmFactory::get() get@endlink method on a
|
|
@b ParmFactory produces a new @b PRM_Template describing a single parameter.
|
|
For example,
|
|
|
|
@code
|
|
#include <houdini_utils/ParmFactory.h>
|
|
...
|
|
|
|
PRM_Template groupParm =
|
|
houdini_utils::ParmFactory(PRM_STRING, "group", "Group")
|
|
.setTooltip("Specify a subset of the input VDB grids to be processed.")
|
|
.setDocumentation(
|
|
"The subset of VDB grids to be processed"
|
|
" (see [specifying volumes|/model/volumes#group] for details"
|
|
" on selecting grids)")
|
|
.setChoiceList(&houdini_utils::PrimGroupMenu)
|
|
.get();
|
|
|
|
PRM_Template toleranceParm =
|
|
houdini_utils::ParmFactory(PRM_FLT_J, "tolerance", "Pruning Tolerance")
|
|
.setDefault(0.01)
|
|
.setRange(PRM_RANGE_RESTRICTED, 0, PRM_RANGE_UI, 1)
|
|
.get();
|
|
@endcode
|
|
|
|
Note that, using a @b ParmFactory, one need only specify those
|
|
attributes of a parameter (@b setDefaults, @b 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
|
|
<A HREF="http://www.sidefx.com/docs/houdini/help/format">wiki markup</A>
|
|
format can be added with @hulink::ParmFactory::setDocumentation()
|
|
setDocumentation@endlink, as in the example above.
|
|
Call @b setDocumentation with a null pointer or with an empty string
|
|
to exclude a parameter from the Help page.
|
|
|
|
@b ParmFactory objects may be added directly to a
|
|
@hulink::ParmList ParmList@endlink, which among other things ensures that
|
|
the list of templates is properly null-terminated.
|
|
Typically, the @b ParmList is populated at the time the operator is registered,
|
|
as in the following example:
|
|
@code
|
|
#include <houdini_utils/ParmFactory.h>
|
|
|
|
void
|
|
newSopOperator(OP_OperatorTable* table)
|
|
{
|
|
houdini_utils::ParmList parms;
|
|
|
|
// Define a string-valued group name pattern parameter and add it to the list.
|
|
parms.add(houdini_utils::ParmFactory(PRM_STRING, "group", "Group")
|
|
.setTooltip("Specify a subset of the input VDB grids to be processed.")
|
|
.setChoiceList(&houdini_utils::PrimGroupMenu));
|
|
|
|
// Define a menu of verbosity levels.
|
|
parms.add(houdini_utils::ParmFactory(PRM_ORD, "verbose", "Verbosity")
|
|
.setDefault(PRMoneDefaults)
|
|
.setChoiceListItems(PRM_CHOICELIST_SINGLE, {
|
|
"quiet", "Quiet", // token, label
|
|
"verbose", "Verbose",
|
|
"verbose2", "More Verbose",
|
|
}));
|
|
|
|
// Define a menu from a dynamically-constructed list of items.
|
|
{
|
|
std::vector<std::string> items;
|
|
for (int i = 0; i < openvdb::NUM_GRID_CLASSES; ++i) {
|
|
openvdb::GridClass cls = openvdb::GridClass(i);
|
|
items.push_back(openvdb::GridBase::gridClassToString(cls)); // token
|
|
items.push_back(openvdb::GridBase::gridClassToMenuName(cls)); // label
|
|
}
|
|
|
|
parms.add(houdini_utils::ParmFactory(PRM_ORD, "gridclass", "Grid Class")
|
|
.setDefault(PRMzeroDefaults)
|
|
.setChoiceListItems(PRM_CHOICELIST_SINGLE, items));
|
|
}
|
|
|
|
...
|
|
}
|
|
@endcode
|
|
|
|
@subsection Switchers
|
|
The @b ParmList provides a convenient way of defining switchers (tab menus):
|
|
|
|
@code
|
|
parms.beginSwitcher("switcher");
|
|
|
|
parms.addFolder("Tree Topology");
|
|
|
|
parms.add(houdini_utils::ParmFactory(PRM_HEADING, "nodes", "Nodes"));
|
|
parms.add(houdini_utils::ParmFactory(PRM_TOGGLE, "viewnodes", "View"));
|
|
...
|
|
|
|
parms.addFolder("Isosurface");
|
|
|
|
parms.add(houdini_utils::ParmFactory(PRM_HEADING, "surfacing", "Surfacing"));
|
|
parms.add(houdini_utils::ParmFactory(PRM_TOGGLE, "extractmesh", "Extract Mesh"));
|
|
...
|
|
|
|
parms.endSwitcher();
|
|
@endcode
|
|
|
|
The above generates the following UI:
|
|
|
|
@image html tabmenu.png
|
|
|
|
Switchers can also be nested:
|
|
|
|
@code
|
|
parms.beginSwitcher("switcher");
|
|
parms.addFolder("A");
|
|
parms.beginSwitcher("nested_switcher");
|
|
parms.addFolder("1");
|
|
parms.addFolder("2");
|
|
parms.addFolder("3");
|
|
parms.endSwitcher();
|
|
parms.addFolder("B");
|
|
...
|
|
parms.endSwitcher();
|
|
@endcode
|
|
|
|
@subsection Multi-Parms
|
|
Multi-parms are dynamically-sized parameters that consist of a variable
|
|
number of child instances.
|
|
Each child instance is defined by a second @b ParmList that itself consists
|
|
of multiple parameters.
|
|
These parameters’ tokens must include a @c # character, which is
|
|
typically placed at the end of the token.
|
|
|
|
@code
|
|
houdini_utils::ParmList parms;
|
|
...
|
|
|
|
// Build the multi-parm's parameter list.
|
|
houdini_utils::ParmList multiParms;
|
|
multiParms.add(houdini_utils::ParmFactory(PRM_STRING, "gridname#", "Grid name")
|
|
.setTooltip("Specify a name for this grid."));
|
|
...
|
|
|
|
// Create the multi-parm itself.
|
|
parms.add(houdini_utils::ParmFactory(PRM_MULTITYPE_LIST, "gridlist", "Grids")
|
|
.setMultiparms(multiParms)
|
|
.setDefault(PRMoneDefaults));
|
|
...
|
|
@endcode
|
|
|
|
The above generates the following UI:
|
|
@image html multiparm.png
|
|
|
|
A multi-parm’s parameters are accessed by iterating through each
|
|
child instance:
|
|
|
|
@code
|
|
UT_String gridNameStr;
|
|
|
|
for (int i = 1, N = evalInt("gridlist", 0, 0); i <= N; ++i) {
|
|
evalStringInst("gridname#", &i, gridNameStr, 0, time);
|
|
...
|
|
}
|
|
@endcode
|
|
|
|
Note that evaluating the multi-parm gives the number of child instances,
|
|
and that the instances are numbered starting from one.
|
|
|
|
|
|
@subsection sOpFactory OpFactory
|
|
The @hulink::OpFactory OpFactory@endlink is used in conjunction with a
|
|
@hulink::ParmList ParmList@endlink to register a new operator by adding it
|
|
to the @b OP_OperatorTable.
|
|
Among other things, the @b 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 @b newSopOperator example,
|
|
|
|
@code
|
|
#include <houdini_utils/ParmFactory.h>
|
|
|
|
void
|
|
newSopOperator(OP_OperatorTable* table)
|
|
{
|
|
...
|
|
|
|
// Register this operator.
|
|
houdini_utils::OpFactory(houdini_utils::OpPolicy(), "My SOP",
|
|
MySOP::factory, parms, *table)
|
|
.addInput("VDB grids to process") // input 0
|
|
.addOptionalInput("Reference geometry"); // input 1
|
|
}
|
|
@endcode
|
|
|
|
The first argument to the @b OpFactory constructor is an instance of the
|
|
@hulink::OpPolicy OpPolicy@endlink class (or a subclass thereof).
|
|
@b OpPolicy objects allow for customization of certain behaviors of
|
|
the @b OpFactory.
|
|
The base class specifies a policy for converting an English
|
|
operator name like <tt>"My SOP"</tt>, which appears in menus and other
|
|
UI elements, into an operator type name.
|
|
The default policy is simply to call @b UT_String::forceValidVariableName
|
|
on the English name.
|
|
|
|
If the policy does not specify the URL of a Help page, or if the
|
|
@b OpFactory is constructed without an @b OpPolicy, then documentation
|
|
for the operator in Houdini’s
|
|
<A HREF="http://www.sidefx.com/docs/houdini/help/format">wiki markup</A>
|
|
format can be provided with @hulink::OpFactory::setDocumentation()
|
|
setDocumentation@endlink.
|
|
By default, documentation for the operator’s parameters is generated
|
|
automatically and appended to the text provided with @b setDocumentation.
|
|
|
|
When a particular @b OpPolicy is to be used to register multiple operators,
|
|
it might be convenient to subclass @b 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
|
|
@hulink::OpFactory::addAlias() OpFactory::addAlias@endlink
|
|
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:
|
|
|
|
@code
|
|
houdini_utils::OpFactory("My Renamed SOP", MySOP::factory, parms, *table)
|
|
.addInput("VDB grids to process")
|
|
.addAlias("My SOP"); // old name
|
|
@endcode
|
|
|
|
If the operator name changed as a result of an @b OpPolicy change, supply
|
|
the old operator type name directly, with
|
|
@hulink::OpFactory::addAliasVerbatim() addAliasVerbatim@endlink:
|
|
|
|
@code
|
|
houdini_utils::OpFactory(houdini_utils::MyNewOpPolicy(), "My SOP",
|
|
MySOP::factory, parms, *table)
|
|
.addAliasVerbatim("My_Old_SOP_Type"); // old operator type name
|
|
@endcode
|
|
|
|
Among other @b OpFactory features,
|
|
@hulink::OpFactory::setObsoleteParms() OpFactory::setObsoleteParms@endlink
|
|
accepts an additional @b ParmList of parameters that are no longer in use
|
|
but that might still exist in older scene files:
|
|
|
|
@code
|
|
#include <houdini_utils/ParmFactory.h>
|
|
|
|
void
|
|
newSopOperator(OP_OperatorTable* table)
|
|
{
|
|
// This boolean "draw" parameter has been replaced with a multi-state
|
|
// "drawmode" parameter.
|
|
houdini_utils::ParmList obsoleteParms;
|
|
obsoleteParms.add(houdini_utils::ParmFactory(PRM_TOGGLE, "draw", "Draw"));
|
|
|
|
houdini_utils::ParmList parms;
|
|
|
|
parms.add(houdini_utils::ParmFactory(PRM_ORD, "drawmode", "Draw")
|
|
.setChoiceListItems(PRM_CHOICELIST_SINGLE, {
|
|
"none", "Don't Draw",
|
|
"wireframe", "Draw Wireframe",
|
|
"shaded", "Draw Shaded",
|
|
}));
|
|
|
|
// Register this operator.
|
|
houdini_utils::OpFactory("My SOP", MySOP::factory, parms, *table)
|
|
.addInput("Input geometry")
|
|
.setObsoleteParms(obsoleteParms);
|
|
}
|
|
@endcode
|
|
|
|
Override @b OP_Node::resolveObsoleteParms to convert the values of
|
|
obsolete parameters into values of current parameters where appropriate.
|
|
|
|
|
|
@subsection sScopedInputLock ScopedInputLock
|
|
A @hulink::ScopedInputLock ScopedInputLock@endlink 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.
|
|
|
|
@code
|
|
#include <houdini_utils/ParmFactory.h>
|
|
|
|
OP_ERROR
|
|
MySOP::cookMySop(OP_Context& context)
|
|
{
|
|
try {
|
|
houdini_utils::ScopedInputLock lock(*this, context);
|
|
//
|
|
// do cook work
|
|
//
|
|
} catch (std::exception& e) {
|
|
addError(SOP_MESSAGE, e.what());
|
|
}
|
|
return error();
|
|
}
|
|
@endcode
|
|
|
|
|
|
@section sOpenVDBOperators OpenVDB SOP construction
|
|
OpenVDB SOPs are derived from the @hvdblink::SOP_NodeVDB SOP_NodeVDB@endlink
|
|
base class which, among other things, adds guide geometry and node-specific
|
|
info text.
|
|
|
|
@code
|
|
#include <houdini_utils/ParmFactory.h>
|
|
#include <openvdb_houdini/Utils.h>
|
|
#include <openvdb_houdini/SOP_NodeVDB.h>
|
|
#include <UT/UT_Interrupt.h>
|
|
|
|
class SOP_DW_OpenVDBTemplate: public openvdb_houdini::SOP_NodeVDB
|
|
{
|
|
public:
|
|
SOP_DW_OpenVDBTemplate(OP_Network*, const char* name, OP_Operator*);
|
|
~SOP_DW_OpenVDBTemplate() override {};
|
|
|
|
static OP_Node* factory(OP_Network*, const char* name, OP_Operator*);
|
|
|
|
// Return true for a given input if the connector to the input
|
|
// should be drawn dashed rather than solid.
|
|
int isRefInput(unsigned idx) const override { return (idx == 1); }
|
|
|
|
protected:
|
|
OP_ERROR cookVDBSop(OP_Context&) override;
|
|
bool updateParmsFlags() override;
|
|
};
|
|
@endcode
|
|
|
|
|
|
@subsection sListOfIncomingGrids Selecting grids
|
|
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):
|
|
|
|
@code
|
|
houdini_utils::ParmList parms;
|
|
|
|
// Define a string-valued group name pattern parameter.
|
|
parms.add(houdini_utils::ParmFactory(PRM_STRING, "group", "Group")
|
|
.setTooltip("Specify a subset of the input VDB grids to be processed.")
|
|
.setChoiceList(&houdini_utils::PrimGroupMenu));
|
|
|
|
// Define a group name parameter associated with this operator's second input.
|
|
parms.add(houdini_utils::ParmFactory(PRM_STRING, "group", "Group")
|
|
.setTooltip("Specify a subset of the input VDB grids to be processed.")
|
|
.setChoiceList(&houdini_utils::PrimGroupMenu)
|
|
.setSpareData(&SOP_Node::theSecondInput));
|
|
@endcode
|
|
|
|
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 <tt>\@<i>attr</i>=<i>value</i></tt> syntax.
|
|
For example, entering <tt>\@name="density*"</tt> as the group name
|
|
creates a new group comprising all input primitives whose name begins
|
|
with <tt>density</tt>. Entering <tt>\@vdb_value_type=float</tt>
|
|
creates a new group of input grid primitives whose data type is @c float.
|
|
Users may enter multiple space-separated group names or grouping expressions.
|
|
|
|
|
|
@subsection sIteratingOverGrids Iterating over grids
|
|
In @b cookMySOP, the string value of a group parameter
|
|
is used to construct a @b GA_PrimitiveGroup.
|
|
(SOP_NodeVDB provides a convenience method,
|
|
@hvdblink::SOP_NodeVDB::matchGroup() SOP_NodeVDB::matchGroup@endlink,
|
|
to simplify this step and to handle errors in a standard way.)
|
|
The @b GA_PrimitiveGroup so constructed may be iterated over using
|
|
either a @hvdblink::VdbPrimCIterator VdbPrimCIterator@endlink, for
|
|
read-only access, or a @hvdblink::VdbPrimIterator VdbPrimIterator@endlink,
|
|
for read/write access:
|
|
|
|
@code
|
|
OP_ERROR
|
|
MySOP::cookVDBSOP(OP_Context& context)
|
|
{
|
|
try {
|
|
houdini_utils::ScopedInputLock lock(*this, context);
|
|
|
|
const fpreal time = context.getTime();
|
|
|
|
// This does a deep copy of native Houdini primitives
|
|
// but only a shallow copy of VDB grids.
|
|
duplicateSource(0, context);
|
|
|
|
// Get the group of grids to process.
|
|
UT_String groupStr;
|
|
evalString(groupStr, "group", 0, time);
|
|
const GA_PrimitiveGroup* group = matchGroup(*gdp, groupStr.toStdString());
|
|
|
|
// Get other UI parameters.
|
|
int verbose = evalInt("verbose", 0, time);
|
|
|
|
UT_AutoInterrupt progress("Processing VDB grids");
|
|
|
|
// For each VDB primitive in the selected group...
|
|
for (openvdb_houdini::VdbPrimIterator it(gdp, group); it; ++it) {
|
|
|
|
if (progress.wasInterrupted()) {
|
|
throw std::runtime_error("processing was interrupted");
|
|
}
|
|
|
|
GU_PrimVDB* vdbPrim = *it;
|
|
|
|
// Optionally get the primitive's name (or, if the name is empty,
|
|
// the primitive's index).
|
|
const UT_String gridName = it.getPrimitiveNameOrIndex();
|
|
|
|
// If this primitive's grid is shared with other primitives, make
|
|
// a deep copy of it. If the grid is not going to be modified
|
|
// in place (or when using GEOvdbProcessTypedGrid()--see below),
|
|
// skip this step.
|
|
vdbPrim->makeGridUnique();
|
|
|
|
openvdb_houdini::Grid& grid = vdbPrim->getGrid();
|
|
|
|
// Process the grid.
|
|
// (Your code goes here.)
|
|
|
|
// In cases where it is not possible to process the primitive's
|
|
// grid in place, replace the grid with a new grid:
|
|
// openvdb_houdini::GridPtr outputGrid = ...;
|
|
// vdbPrim->setGrid(*outputGrid);
|
|
}
|
|
} catch (std::exception& e) {
|
|
addError(SOP_MESSAGE, e.what());
|
|
}
|
|
return error();
|
|
}
|
|
@endcode
|
|
|
|
|
|
@subsection sProcessingTypedGrids Processing grids of different types
|
|
Recall that a @b Grid is a container for a transform, metadata and a
|
|
@b Tree, and that the @b Tree holds voxel data of a specific type
|
|
(@c bool, @c float, @c vec3s, etc.).
|
|
Whenever possible, try to write generic grid processing code.
|
|
That is, write code that can handle grids of more than one type
|
|
(@c int, @c float and @c double, say, instead of just @c float)
|
|
or, ideally, grids of arbitrary type.
|
|
|
|
Writing generic code can be tricky, but convenience functions
|
|
exist to make the job easier.
|
|
Use @b GEOvdbProcessTypedGrid to call a method or methods on
|
|
a primitive’s grid, regardless of its type:
|
|
|
|
@code
|
|
MyGridProcessor proc; // functor (see explanation below)
|
|
// Call a method on the primitive's grid, regardless of the grid's type.
|
|
// Note that by default, GEOvdbProcessTypedGrid() calls makeGridUnique()
|
|
// on the primitive if it is non-const.
|
|
GEOvdbProcessTypedGrid(*vdbPrim, proc);
|
|
@endcode
|
|
|
|
@b GEOvdbProcessTypedGrid accepts a primitive and a functor
|
|
(an object for which the call operator,
|
|
<tt>operator()()</tt>, 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 @c void.
|
|
The following is a simple example of a functor that satisfies these conditions:
|
|
|
|
@code
|
|
struct PruneOp
|
|
{
|
|
template<typename GridT>
|
|
void operator()(GridT& grid) const { grid.prune(); }
|
|
};
|
|
@endcode
|
|
|
|
@b PruneOp can call the @b prune method on a grid of any type, but it’s
|
|
necessary to know the specific type, because @b prune takes an optional
|
|
tolerance argument whose type is the type of the grid’s voxel values.
|
|
(Note that grids also have a @b 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, <i>A</i> and <i>B</i>, of the same type:
|
|
|
|
@code
|
|
#include <openvdb/tools/Composite.h> // for csgUnion()
|
|
|
|
struct CSGUnionOp
|
|
{
|
|
// Pointer to the B grid
|
|
// (non-const, because CSG operations modify both the A and B grids)
|
|
openvdb_houdini::Grid* bGridBase;
|
|
|
|
template<typename GridT>
|
|
void operator()(GridT& aGrid) const
|
|
{
|
|
// Cast the generic B grid pointer to point to a grid of
|
|
// the same type as the A grid.
|
|
if (GridT* bGrid = UTvdbGridCast<GridT>(bGridBase)) {
|
|
// Compute the union, storing the result in the A grid
|
|
// and emptying the B grid.
|
|
openvdb::tools::csgUnion(aGrid, *bGrid);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
OP_ERROR
|
|
MySOP::cookVDBSop(OP_Context& context)
|
|
{
|
|
...
|
|
// Retrieve the A and B grids from primitives on the input detail(s)
|
|
// (non-const, because CSG operations modify both the A and B grids).
|
|
openvdb_houdini::Grid
|
|
&aGrid = aPrim->getGrid(),
|
|
&bGrid = bPrim->getGrid();
|
|
|
|
// CSG operations require the A and B grids to have the same type.
|
|
if (aGrid.type() != bGrid.type()) {
|
|
addError(SOP_MESSAGE, "grids have different types");
|
|
} else {
|
|
CSGUnionOp proc;
|
|
|
|
// Hand the functor a generic pointer to the B grid
|
|
// (whose concrete type we don't know yet).
|
|
proc.bGridBase = &bGrid;
|
|
|
|
// Apply the CSG operation, overwriting grid A (and emptying grid B).
|
|
if (!GEOvdbProcessTypedGrid(*aPrim, proc)) {
|
|
addError(SOP_MESSAGE, "failed to compute CSG union");
|
|
}
|
|
...
|
|
}
|
|
}
|
|
@endcode
|
|
|
|
Certain operations might make sense only for grids with scalar or with
|
|
vector voxel values.
|
|
Variants of @b GEOvdbProcessTypedGrid exist to handle those cases.
|
|
For example, @b GEOvdbProcessTypedGridScalar and @b GEOvdbProcessTypedGridVec3
|
|
invoke a functor only on grids of scalar or 3-vector types, respectively,
|
|
and they return @c false for and ignore grids of all other types.
|
|
|
|
|
|
<!--
|
|
To do:
|
|
- iterating over multiple inputs
|
|
- VDB source nodes
|
|
-->
|
|
|
|
*/
|