/** @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 ... 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 wiki markup 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 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 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 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 "My SOP", 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 wiki markup 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 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 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 #include #include #include 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 \@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 @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, 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 @c void. The following is a simple example of a functor that satisfies these conditions: @code struct PruneOp { template 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, A and B, of the same type: @code #include // 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 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(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. */