HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Parameters

Topics

Basics

A lot of the leg work involved in creating a new operator is setting up the array of PRM_Template objects. These templates are used to create the parameter list (PRM_ParmList) for the operator as well as the parameter dialog that appears in the parameter pane for the given node type.

Let's look at an example from SOP_Star.C

static PRM_Name negativeName("nradius", "Negative Radius");
static PRM_Default fiveDefault(5);
static PRM_Default radiiDefaults[] = {
};
SOP_Star::myTemplateList[] = {
PRM_Template(PRM_XYZ_J, 2, &PRMradiusName, radiiDefaults),
PRM_Template(PRM_TOGGLE, 1, &negativeName),
};

First notice that PRM_Include.h is included for the various PRM_Template classes and types. Then, we have some static data that is used by the main array of PRM_Template's (also static). For each PRM_Template object, we initialize it with the desired information. There are many forms of the PRM_Template::PRM_Template() constructor along with various default arguments. Here are the main function parameters:

  • thetype
    Determines the storage format and how it should be presented in the user interface.
  • thetype_ext
    Optional additional UI options for the parameter of type PRM_TypeExtended.
  • thevectorsize
    Number of data elements (or components) in the parameter. For example, to represent an RGB color, the vector size should be set to 3.
  • thenameptr
    Pointer to a PRM_Name describing the parameter name. Each PRM_Name object contains a token and a label. The token has many purposes. It is used for saving/loading the parameter. It is also used to determine the channel names for animating parameters. Think of it as a symbolic name. Because of its uses, there are some restrictions on what can be used as a token name. It should not contain any special characters (i.e. standard C variable names should be ok), and should not contain any spaces. The label is used for UI purposes and should be as descriptive as possible (without being too long). It is the actual text that is displayed to a user.
  • thedefaults
    Array of PRM_Default objects, one per component. PRM_Default has two parts: a float and a string. For floating point parameters, if the default string is defined, then the parameter will start off being animated and have the channel expression defined by the string. Otherwise, the floating point default is used for float and integer types, while the string is used to initialize string types.
  • thechoicelistptr
    Pointer to PRM_ChoiceList object for supplying the menu choices for a parameter. This should only be supplied for ordinal or string parameters.
  • therangeptr
    Pointer to PRM_Range object for defining the range of valid integer or float values. The range can also specify whether it is UI limited or hard limited.
  • thecallbackfunc
    Pointer to a PRM_Callback function pointer to be executed when the parameter is modified via the UI.
  • thehelptext
    Supplies an optional string that is used as the pop-up tooltip in the parameter dialog. This is useful for giving users quick tips about the parameter.
  • theexportlevel
    Export level specifying in which dialogs the corresponding parameter may appear. If given, this must be the second argument to PRM_Template::PRM_Template().
  • thespareptr
    Optional data that is used by the various parameter types.

In the example above there are a number of common parameter names, defaults, and ranges that start with the PRM prefix. For a detailed list of these see the PRM_Shared.h file (included by PRM_Include.h).

See Also
Working with Parameters

Parameter Types

There are numerous predefined PRM_Type objects covering most of the commonly use parameter types declared in PRM_Type.h. They follow a common naming convention. If it ends in _E, then it can contain non-animated expressions. If it ends in _J, then it can be animated. If it ends with neither of these suffixes, then it is non-animatable. Some commonly used ones are:

  • PRM_FLT
    A parameter containing float components. The channel name suffixes will be numbers like 1, 2, 3, etc.
  • PRM_ANGLE
    A single value representing an angle. A 2D circle representation is shown in the UI.
  • PRM_RGB
    A parameter specifying a floating point triplet with channel suffixes r, g, b, and a.
  • PRM_XYZ
    A parameter specifying a floating point triplet with channel suffixes x, y, and z.
  • PRM_UVW
    A parameter specifying a floating point triplet with channel suffixes u, v, and w.
  • PRM_DIRECTION
    A 3 component vector parameter used to indication a direction. It has channel suffixes x, y, and z.
  • PRM_INT
    A parameter that contains integer values but is stored in floating point. It uses numbers for channel suffixes like 1, 2, 3, etc.
  • PRM_FLT_MINMAX, PRM_INT_MINMAX, PRM_ANGLE_MINMAX
    A 2 component floating point parameter with channel suffixes min and max. For angle parameters, it will show a 2D circle representation.
  • PRM_STARTEND
    A 2 component floating point parameter with channel suffixes start and end.
  • PRM_BEGINEND
    A 2 component floating point parameter with channel prefixes start and end.
  • PRM_STRING
    A simple string parameter.
  • PRM_FILE, PRM_PICFILE, PRM_GEOFILE
    These types define different file path types. Each type expects a string to be used and that string will represent a file. Therefore, in the UI, there will be a file browser available for the parameter. However, for geo files, the filter on the file prompter will be set to only show geometry files. For pictures, only image files will be displayed, etc. To set specific file extensions to browse for, see the spare data token PRM_SpareData::getFileChooserPatternToken. In addition, the file browser can also be set to either read-only, write-only, or read-and-write mode, by adding PRM_SpareData::fileChooserModeRead, PRM_SpareData::fileChooserModeWrite, or PRM_SpareData::fileChooserModeReadAndWrite, respectively.
  • PRM_ORD
    A parameter containing integer components. This is also used for index-based menus.
  • PRM_TOGGLE
    A parameter that is a checkbox
  • PRM_CALLBACK
    Button parameter that runs a callback function when pressed.
  • PRM_BUTTONSTRIP
    A strip of text buttons for an ordinal bitmask parameter (up to 32 bits). A PRM_ChoiceList should be supplied for the button text names.
  • PRM_SWITCHER
    Used to define a page or folder tab within the parameter list. In this case, the default float values determine the number of parameters per page, while the string component specifies the title for the folder tab. The vector size of the parameter specifies the number of pages contained. The parameters directly following this parameter make up the parameters in the folder tabs. By default, changing switcher parameters will not create undos or cause the owner node to re-cook. If you want to do this, you should use PRM_SWITCHER_REFRESH instead.

In addition to these types, one may bitwise or one of the following flags:

  • PRM_TYPE_JOIN_NEXT
    Join the next parameter with the current one onto the same line
  • PRM_TYPE_LABEL_NONE
    Omit the label

A detailed breakdown for a PRM_Type follows. It has three basic types (PRM_Type::PRM_BasicType):

  • FLOAT
    Floating point parameters
  • STRING
    String parameters
  • ORDINAL
    Integer parameters

For each of these types, there are several possibilities to give it more semantic meaning (PRM_Type::PRM_FloatType, PRM_Type::PRM_OrdinalType, PRM_Type::PRM_StringType, PRM_Type::PRM_PathType). This allows the parameter pane to present the parameter in a way that is more suited to its real purpose. For example, if you have a string parameter, you might mark it as being a file path of some sort so that it will have a button in the parameter pane that brings up a file browser. For file paths, you can additionally specify them as particular file types so that the file browser has certain default file extensions. Additionally, there are some other modifiers on PRM_Type:

  • PRM_ChannelType
    Specifies the suffix used for channel names. For example, the PRMradiusName parameter above has the name "rad" with an XYZ suffix. This means that the resulting channel names of the parameter will be "radx", "rady", and "radz".
  • PRM_InterfaceType
    The main flag here is PRM_TYPE_CHANNEL which specifies whether this parameter can be animated. This field also contains some extra UI options for how the parameter should be displayed (such as PRM_INTERFACE_LABEL_NONE and PRM_INTERFACE_JOIN_NEXT).
  • PRM_BehaviorType
    This specifies additional options for when the parameter's value is modified. Important flags here are: PRM_BEHAVIOR_NOREFRESH (does not trigger UI refresh, will not create undos) and PRM_BEHAVIOR_NOCOOK (will not cause nodes to recook).

In addition to PRM_Type, one also supply an optional PRM_ExtendedType. The most widely used options for PRM_ExtendedType are:

  • PRM_TYPE_DYNAMIC_PATH
    Specifies a node path parameter with a node chooser icon button. If this is used, then you should additionally specify the desired node type for the path parameter via one of the predefined PRM_SpareData objects such as PRM_SpareData::objPath or PRM_SpareData::sopPath.
  • PRM_TYPE_DYNAMIC_PATH_LIST
    Similar to PRM_TYPE_DYNAMIC_PATH except it accepts a list of node paths.
  • PRM_TYPE_LOGARITHMIC
    Specifies a logarithmic scale for floating point sliders.

Switchers

Switcher parameters types (PRM_SWITCHER, PRM_SWITCHER_NOREFRESH) allows one to organize the parameters that follow it into a set of folders. The number of folders is specified as the vector size. The folder labels, and the number of parameters in the folder are given by the array of defaults. Note that if there are switchers inside switchers, the nested switcher inside and all its child parameters only count as 1 entry in the PRM_Default.

Here is a simple example that creates a switcher that has 2 folders where the first folder has 1 parameter, and the second folder has no parameters:

static PRM_Name switcherName("shakeswitcher");
static PRM_Default switcherList[] = {
PRM_Default(1, "Shake"), // 1 is number of parameters in tab
PRM_Default(0, "Other"), // actually have no parameters here
};
static PRM_Name jitterName("jitter", "Jitter scale");
static PRM_Template templateList[] =
{
PRM_Template(PRM_SWITCHER, // parm type
sizeof(switcher)/sizeof(PRM_Default), // number of folders
&switcherName, // name
switcherList), // folder labels
PRM_Template(PRM_XYZ_J, 3, &jitterName),
PRM_Template() // sentinel
};

Multi-Parms

Multi-parms are dynamically sized parameters. Each multi-parm has a variable number of child instances. Each multi-parm child instance itself consists of a fixed number of parameters (defined by an array of PRM_Template objects).

A multi-parm has a separate PRM_Template constructor so it cannot be confused with a standard parameter. Here's the constructor: (there is also a version with a PRM_Export parameter)

PRM_Template *templates,
int vectorsize,
PRM_Name *name,
PRM_Default *defaultsize = 0,
PRM_Range *defaultrange = 0,
PRM_SpareData *spare = 0);
  • type
    The type of multi-parm. Currently one of:
    • PRM_MULTITYPE_LIST
      The simplest multi-parm. The instances are listed in one big list, expanding the dialog vertically as much as needed. Good for multi-parms that are nested in scrolled multi-parms, as they can share the scroll region. Also good for multi-parms with small ranges (max 20 or so).

      The vectorsize parameter is not used.
    • PRM_MULTITYPE_SCROLL
      The instances are listed in a UI container with scroll bars.

      The vectorsize determines the size of the scroll region (in vertical UI inches).
    • PRM_MULTIPARM_SWITCHER
      The instances are put in folder tabs. This type is good for instances which have large numbers of parameters (like full transforms).

      The vectorsize determines the number of instances assigned to one tab.
  • type templates
    A PRM_Template list, which is almost identical in structure to the normal OP_Parameters template lists. All the parameter names in this list must contain a pound (#) character for each of its multi-parm parents.
  • vectorsize
    Used in different ways by different multi types (see above).
  • name
    Name of the multi-parm.
  • defaultsize
    The default number of instances upon node creation.
  • defaultrange
    The allowed range for the # of instances. PRM_RANGE_RESTRICTED must be used to define a limit. The limits must both be non-negative (>=0).
    PRM_Range(PRM_RANGE_RESTRICTED, 4,
    PRM_RANGE_RESTRICTED, 10) // 4-10 allowed
  • spare
    Optional data which can specify the index from which we start counting. The default, unless otherwise specified here, is one. For example, for a child parameter named "parm#", the first instance of it will be named "parm1". Often it suffices to use one of the PRM_SpareData objects predefined for this purpose, either PRM_SpareData::multiStartOffsetZero or PRM_SpareData::multiStartOffsetOne.

Multi-parm template lists are almost identical to a normal template list. The differences are:

  • The token names should have a # character in them to determine where to put the instance number. If using a generic vector, like PRM_FLT_J (vectorsize 2), then the # shouldn't be at the end of the token. This is because the component number for the subindex is appended to the token for the channel name, resulting in rather confusing channels like pos11, pos12, pos21, pos22, etc.). If a # is not found, the instance number will be appended to the token.
  • Labels can have a # character, but it is not required. If the # is not present, the label appears as-is.
  • Switchers of any kind are not allowed in multi-parm template lists.
  • Multi-parm templates ARE allowed in multi-parm template lists (see below).

Here is an example that creates a series of 2D points with a RGBA color.

static PRM_Name names[] = {
PRM_Name("points", "Number of Points"),
};
static PRM_Name pnames[] = {
PRM_Name("pnt#pos", "Point #"),
PRM_Name("pnt#color", "Color"),
};
static PRM_Template thePointTemplates[] =
{
PRM_Template(PRM_RGB_J, PRM_TYPE_COLOR_SWATCH, 4, &pnames[1],
};
OP_Sample::myTemplateList[] =
{
PRM_Template(PRM_MULTITYPE_SCROLL, thePointTemplates, 2, &names[0]),
};

You can access the value of any multi-parm by name, or by using the PRM_Name with the # in it. You should not access multi-parms by index, nor should you lookup the parameter by name and store the index. Child parameters of a multi-parm are NOT guaranteed to be stored in any particular order (especially if 2 multi-parms or nested multi-parms are used in an OP_Node).

You may access any parameter before a multi-parm by index, but parameters after a multi-parm must be accessed by name.

As a convenience, you can use the eval...Inst() and set...Inst() functions to evaluate parameters. From the sample above, this code evaluates the point position of a series of points in the multi-parm:

int num = evalInt("points", 0,0.0f);
for(int i = 1; i <= num; i++)
{
fpreal px = evalFloatInst("pnt#pos", &i, 0, t);
fpreal py = evalFloatInst("pnt#color", &i, 0, t);
}

Note that indexing multi-parms starts at 1, not 0 by default. This so that user-friendly instance names are generated. You can override this behavior by changing the start offset in the parameter template constructor. However, this should only be done for exceptional cases or for backwards compatibility. The address of i is taken in the above example because the instance is actually a list; by default the list is 1 element long, but nested multi-parms require longer lists (you may have noticed the last parameter is 'int nestlevel = 1')

Nested multi-parms are slightly more complicated, simply because more levels of instance numbers are required. Consider the example:

// Node:
// Curve 1
// Points...
// Curve 2
// Points...
// :
static PRM_Name pnames[] = {
PRM_Name("point#pos", "Point #"),
};
static PRM_Template thePointTemplates[] =
{
PRM_Template(PRM_XYZ_J, 2, &pnames[0]),
};
static PRM_Name cnames[] = {
PRM_Name("curve#def", "Curve # Definition"),
};
static PRM_Template theCurveTemplates[] =
{
PRM_Template(PRM_MULTITYPE_LIST, thePointTemplates, 1, &cnames[0]),
};
static PRM_Name names[] = {
PRM_Name("curves", "Number of Curves"),
};
OP_Sample::myTemplateList[] =
{
PRM_Template(PRM_MULTITYPE_SCROLL, theCurveTemplates, 2, &names[0]),
};

Each curve contains a series of 2D points, and the node itself has a series of curves, each of which are a variable size.

To access a specific point, you need to specify both the curve index and the point index.

// Eval all the points on all the curves.
int ref[2];
int i,j;
int num_curves = evalInt("curves", 0, 0.0f);
int num_points;
fpreal px, py;
for(i=0; i<num_curves; i++)
{
ref[0] = i;
// only want nest level 1 to access the curve multiparm itself, to
// determine the number of points in that curve.
num_points = evalIntInst( "curve#def", ref, 0, 0.0f, 1);
for(j=0; j<num_points; j++)
{
ref[1] = j;
// note the 2 at the end of the evalFloatInst - it's the nest level
px = evalFloatInst( "curve#point#pos", ref, 0, 0.0f, 2);
py = evalFloatInst( "curve#point#pos", ref, 1, 0.0f, 2);
}
}

Finally, note that you can't export multi-parms to the Toolbox (PRM_EXPORT_TBX). They're just too big.

Ramps

Although ramp parameters are internally represented as multi-parms, they have a simplified declaration using the multi-parm PRM_Template constructor. You choose the PRM_MultiType as either PRM_MULTITYPE_RAMP_RGB (color) or PRM_MULTITYPE_RAMP_FLT (float) ramps. Then you pass NULL for the template list. The vector size must be 1 and the first PRM_Default entry specifies default number of ramp points (which itself must be at least 1).

PRM_SpareData theColorRampSpare(PRM_SpareArgs()
<< PRM_SpareToken("rampshowcontrolsdefault", "0")
<< PRM_SpareToken("rampcolordefault",
"1pos ( 0 ) 1c ( 0 0 1 ) 1interp ( linear ) "
"2pos ( 1 ) 2c ( 1 0 0 ) 2interp ( linear )"));
// NOTE: For float ramps:
// - Use "rampfloatdefault" instead of "rampcolordefault"
// - Use "value" instead of "c" for marker tokens
static PRM_Template templateList[] =
{
// Color ramp
PRM_Template(PRM_MULTITYPE_RAMP_RGB, NULL, 1, &PRMrampColorName,
// Float ramp
PRM_Template(PRM_MULTITYPE_RAMP_FLT, NULL, 1, &PRMrampAlphaName,
// Color ramp with hidden controls by default and custom color key values
PRM_Template(PRM_MULTITYPE_RAMP_RGB, NULL, 1, &PRMrampColorName,
PRMtwoDefaults, 0, &theColorRampSpare),
};

Menus

To create a parameter with a menu, a PRM_ChoiceList object needs to be supplied. The PRM_ChoiceList constructor takes various PRM_ChoiceListType flags:

  • PRM_CHOICELIST_REPLACE
    Chosen choice replaces the existing parameter value.
  • PRM_CHOICELIST_TOGGLE
    Chosen choice toggles the string token in the existing parameter value
  • PRM_CHOICELIST_APPEND
    Chosen choice's string token is appended to the existing parameter value
  • PRM_CHOICELIST_WILD
    Used with PRM_CHOICELIST_TOGGLE to indicate * as a valid value to mean all choices.
  • PRM_CHOICELIST_SINGLE
    Chosen choice is exclusive and replaces the existing parameter value. A PRM_ChoiceList object should only be used for PRM_ORD or PRM_STRING type parameters. For PRM_ORD parameters, PRM_ChoiceListType::PRM_CHOICELIST_SINGLE should always be used. For the other flags with PRM_STRING parameters, a string value will be presented with a separate icon for making menu choices.

The example below demonstrates a simple static menu setup for ordinal and string parameters.

static PRM_Name sopOrdinalName("ordmenu", "Ordinal Menu");
static PRM_Name sopOrdChoices[] =
{
PRM_Name("choice1", "Choice 1"),
PRM_Name("choice2", "Choice 2"),
PRM_Name("choice3", "Choice 3"),
};
static PRM_ChoiceList sopOrdinalMenu(PRM_CHOICELIST_SINGLE, sopOrdChoices);
static PRM_Name sopStringName("strmenu", "String Menu");
static PRM_Name sopStrChoices[] =
{
PRM_Name("strchoice1", "Str Choice 1"),
PRM_Name("strchoice2", "Str Choice 2"),
PRM_Name("strchoice3", "Str Choice 3"),
};
static PRM_ChoiceList sopStringMenu(PRM_CHOICELIST_TOGGLE, sopStrChoices);
SOP_MyNode::myTemplateList[]=
{
PRM_Template(PRM_ORD, 1, &sopOrdinalName, 0, &sopOrdinalMenu),
PRM_Template(PRM_STRING, 1, &sopStringName, 0, &sopStringMenu),
};

Menus with items generated dynamically are done with a different form of the PRM_ChoiceList constructor that is given a PRM_ChoiceGenFunc callback. The callback fills out a PRM_Name array along with a trailing sentinel item. Care should be taken to NOT generate more than listsize items.

For group menus, one can also use SOP_Node functions like in the example below.

static void
sopBuildRestGroup(void *data, PRM_Name *choicenames, int listsize,
const PRM_SpareData *spare, const PRM_Parm *parm)
{
SOP_Node *sop = CAST_SOPNODE((OP_Node *)data);
if( sop )
{
sop->buildInputGroups( SOP_REST_INPUT, choicenames, listsize,
PRIM_GROUP, 0, true, parm );
}
else
{
choicenames[0].setToken(0);
choicenames[0].setLabel(0);
}
}
static PRM_ChoiceList sopRestGroupMenu(PRM_CHOICELIST_TOGGLE,
sopBuildRestGroup);

Disabling and Hiding Parameters

To disable or hide parameters, one should override the OP_Parameters::updateParmsFlags() method. This method is called by the parameter pane to give the node a chance to disable or hide parameters in the UI which will not used when cooked. Parameters are usually disabled or hidden due to the values of other parameters which render them unused.

The updateParmsFlags() implementation typically evaluates some parameters to determine whether different set of parameters should be used. Then either the OP_Parameters::enableParm(), or the OP_Parameters::setVisibleState() methods should be called with a true or false value. Note that enableParm() should always be called for any parameter that might be enabled, or disabled. Likewise, setVisibleState() should always be called for any parameter that might be hidden, or made visible again. Both methods return true if the parameter's state actually changed. updateParmsFlags should keep track of those return values and return true also if any of the modified parameters' state changed.

It's also a good policy to always call the updateParmsFlags function of the base class and keep a track of its return value, in much the same way as the return value of the parameter modifiers. Otherwise, any parameters that the new operator might inherit, will not have their UI state properly updated.

bool
SOP_Cop2Raster::updateParmsFlags()
{
bool changed = SOP_Node::updateParmsFlags();
bool use_path;
use_path = !evalInt("usedisk", 0, t);
changed |= enableParm("coppath", use_path);
changed |= enableParm("copcolor", use_path);
return changed;
}

Obsolete Parameters

As new features are added to your custom operator, you may find a need to change the behavior of how some of the old parameters work. However, there might be legacy scene files that you need to support which relies on a particular value of the old parameter behaving a particular way. The usual way to solve these type of problems is to create a new parameter that works similarly to the old parameter but has a different name. The old parameter is then moved into the obsolete parameter list for special handling at load time.

As Houdini loads a scene file, an obsolete parameter list is created for each node that it is loading. If it encounters a parameter on a node that does not match any of the existing parameters of that node, it will first attempt to find a matching obsolete parameter for loading. When the node is done loading, the OP_Node::resolveObsoleteParms() method is called to convert the values of the obsolete parameters into values of the current parameter list that will result in the old behavior. Finally, the obsolete parm list is destroyed.

To use obsolete parameters, first define an obsolete PRM_Template array similar to how it is done with regular parameter templates. Then when you register your operator, call OP_Operator::setObsoleteTemplates() with your obsolete template list.

void
{
op = new OP_Operator("myoperator", "My Operator", /* ... */);
op->setObsoleteTemplates(OP_MyNode::myObsoleteList);
table->addOperator(op);
}

Then implement OP_Node:resolveObsoleteParms() in your subclass. The important thing to note is that the obsolete parameter list is like any parameter list with its default values. The only way to know if a particular obsolete parm was found, is to test if the value of the parameter is at its default value. The example below illustrates the coding pattern for resolveObsoleteParms.

void
SOP_MyNode::resolveObsoleteParms(PRM_ParmList *obsolete_parms)
{
// If there are none defined, we don't do any conversions...
if (!obsolete_parms)
return;
PRM_Parm *parm = obsolete_parms->getParmPtr("obsolete_parm_name");
if (parm && !parm->isFactoryDefault())
{
int old_val = obsolete_parms->evalInt("obsolete_parm_name", 0, t);
int new_val;
// ... convert old_val into some new_val
setInt("new_parm", 0, t, new_val);
// ... may also need to set other parameters according to old_val
}
// delegate to base class
}