Lesson 6: Parameter Blocks

3ds Max refers to the data values associated with plug-ins as 'parameters'. The size of a geometric object and the angle of a bend modifier are examples of parameters. 3ds Max has a powerful mechanism to manage plug-in parameters: instead of keeping them as individual data members, they are encapsulated in "parameter blocks" (objects of the class IParamBlock2). Parameter blocks provide a standard method for 3ds Max to read from and write to plug-in parameter values, independent of the plug-in type. They have the following advantages:

The implementation of SimpleObject2::SetReference() and SimpleObject2::GetReference() is an example that shows how parameter blocks are treated as reference targets in the 3ds Max SDK. The class SimpleObject2 is a reference maker class and has only one reference (IParamBlock2* pblock2), to its parameter block. The implementations of those two functions are:

virtual void SimpleObject2::SetReference (int i, RefTargetHandle rtarg)[inline, protected, virtual] 
{
    pblock2 = (IParamBlock2*)rtarg; 
    SimpleObject::SetReference(i, rtarg);
}

RefTargetHandle SimpleObject2::GetReference (int i)[inline, virtual] 
{
    return (RefTargetHandle)pblock;
}

What you need to know about parameter blocks

The number of parameter blocks per plug-in, as well as the number of parameters within a parameter block is not limited. The parameter blocks in a plug-in are identified by their ID, which is usually zero for the first parameter block and increments for the next ones.

Developers do not directly call the parameter block's constructor to create the parameter blocks for their plug-in. Instead, they instantiate a static parameter block descriptor (a static object of type ParamBlockDesc2) by passing the appropriate arguments to its constructor for each parameter block. These arguments describe the parameters that you want to include in that parameter block. The constructor of ParamBlockDesc2 automatically creates the described parameter block (an object of type IParamBlock2) and registers the address of the newly-created parameter block in a table in the plug-in's class descriptor. The following figure demonstrates this procedure for a geometric object plug-in named Widget. The Widget plug-in project exists in the "how to" folder of the 3ds Max SDK at <3ds Max Installation folder>\maxsdk\howto\objects\widget.

The parameters in a parameter block are accessed using their index number or parameter ID's. For each of its parameters, the parameter block stores at least the parameter value, the parameter ID, an internal name (used by MAXScript), and the parameter type. There can also be additional data for a parameter, such as the default value, the range of the parameter, the UI type, and others. Developers must use the tags defined in the ParamTags enumeration in the call to the parameter block constructor if they want to include these additional data for a parameter. Declaring a parameter in a parameter block is primarily done in the constructor call of the parameter block descriptor, but it can also be done later on by calling the function ParamBlockDesc2::AddParam() as long as there have been no parameter blocks created using that descriptor object.

Parameters within a parameter block can be reference types (references to ReferenceTarget objects), scalar types such as TYPE_FLOAT, aggregated types such as Point3 or Matrix3, external assets to the scenes such as file names and so on. Refer to the documentation for ParamType2 for a complete list of valid parameter types. If a parameter is a valid type for a controller, a default animation controller is automatically assigned to it and the developer can change it later. If animated, you have to provide a value-time pair when you want to write to the parameters (using IParamBlock::SetValue()) and provide a valid time when you want to read from them (using IParamBlock::GetValue()). You can access a non-animated parameter using the current time. The next section describes in detail how a parameter block can be created and accessed.

Creating a parameter block for your plug-in

Suppose that your plug-in needs to have two float parameters, p0 and p1. You will need to instantiate an object of the class ParamBlockDesc2 which will describe your parameter block. You will also need to have an IParamBlock2* in your plug-in class. 3ds Max will automatically create the parameter block based on the arguments you provide in your call to the ParamBlockDesc2 constructor if you set the P_AUTO_CONSTRUCT flag there. The following is the minimum code you need for this:

static ParamBlockDesc2 myPBDesc (
    //Mandatory data
    // ---------------------------------------------------
    0,                   /* The parameter block ID. */
    _T("myParamBlock"),  /* The internal name exposed to MaxScript for this parameter. */
    0,                   /* The resource ID of a particular string in the string table */
                         /* that will be displayed in the track view for the parameter. */
    &myPluginDesc,       /* The address of your plug-in descriptor (ClassDesc2 object). */
                             
    //flags
    P_AUTO_CONSTRUCT     /* This flag makes 3ds Max automatically create the */
                         /* parameter block from this parameter block descriptor object*/

    //Conditional data, depends on the flags defined above
    // ---------------------------------------------------
    0,                   /* Required because P_AUTO_CONSTRUCT was flagged. This means that this */
                         /* parameter block will be the first reference target of your plug-in. */

    //Optional data - parameters definition starts here
    // ---------------------------------------------------
    0,                   /* Parameter ID. We are defining the first parameter here */
    _T("p0"),            /* Internal name of the parameter */
    TYPE_FLOAT,          /* Parameter Type. It will be a float parameter */
    P_ANIMATABLE,        /* A constant defined in iparamb2.h. Indicates that the parameter is animatable.*/
    p_end,               /* End of the first parameter definition.*/
    1,                   /* Parameter ID. This will be the second parameter*/
    _T("p1"),            /* Internal name of the parameter*/
    TYPE_FLOAT,          /* Parameter Type. It will be a float parameter*/
    P_ANIMATABLE,        /* A constant defined in iparamb2.h. Indicates that the parameter is animatable.*/
    p_end,               /* End of the second parameter definition. 'end' is an enumerated value defined in 
                         /* the 'ParamTags' enumerator.*/

    p_end                 /*End of parameter definition*/
	);

The above code calls the constructor of ParamBlockDesc2 to create the static object myPBDesc. The address of the static ClassDesc2 object in your plug-in that is provided in the list of parameters (&myPluginDesc) is used to register this parameter block descriptor in the static ClassDesc2 object . By the time the static ClassDesc2 object calls its ClassDesc2::Create() function, it has the address of all of its parameter blocks and also knows to which reference in the plug-in each parameter block must be assigned. However, this is not done automatically. It is the responsibility of the plug-in developer to assign the addresses of the parameter blocks (registered in the static ClassDesc2 object) to its IParamBlock2 pointers as references. This can be done in the constructor of your plug-in as shown below:

MyPlugin::MyPlugin()
{
    myPluginDesc.MakeAutoParamBlocks(this);
}

The function ClassDesc2::MakeAutoParamBlock() uses the address of the parameter blocks as well as the reference index in the plug-in to set them as the reference targets in the plug-in (remember that the class IParamBlock2 is extended from the class ReferenceTarget).

After calling ClassDesc2::MakeAutoParamBlock() in the constructor of your plug-in, you can set a parameter (for example, p1) at a specific time (for example, Interval& thisTime) for a plug-in instance (for example, myPluginObj) as follows:

myPluginObj.pblock2->SetValue(1, thisTime, 0.0f);

You can also read the value and validity interval of a parameter using IParamBlock2::GetValue() as follows:

float& f;
Interval& ivalid;
myPluginObj.pblock2->GetValue(1, getCOREInterface->GetTime(), f, ivalid );

Note that a controller of the appropriate type is automatically assigned to each animatable parameter in the parameter block.

While you can write to and read from the parameters p0 and p1 in the above code, there is no user interface yet to let the user observe or modify them. Using parameter blocks, it is very easy to create UI panels for parameters. All you need to do is to set the P_AUTO_UI flag in the ParamBlockDesc2 constructor, link to a rollout window you have previously defined in your .rc file, and link each parameter to that rollout window's components. The two latter links are created by using appropriate flags from the ControlType and EditSpinnerType enumerations in your constructor call.

The following example (copied from the Widget how-to example) demonstrates the code to create a more complex parameter block with three parameters and automatic UI creation. The parameter tags used in the constructor define a default value and a range for each parameter.

enum { widget_params }; 
enum { 
    widget_size,
    widget_left,
    widget_right
};

static ParamBlockDesc2 widget_param_blk ( 
    //Required arguments ----------------------------
    widget_params, _T("params"),  0, &WidgetDesc,
    //flags
    P_AUTO_CONSTRUCT + P_AUTO_UI,

    //Dependent arguments ---------------------------
    //required because P_AUTO_CONSTRUCT was flagged
    //This declares the number of rollouts
    PBLOCK_REF,

    //required because P_AUTO_UI was flagged. 
    //This is the Rollout description for the UI panel defined in the rc file
    IDD_PANEL, IDS_PARAMS, 0, 0, NULL,

    //Parameter Specifications ----------------------
    // For each control create a parameter:
    widget_size,         _T("Size"),         TYPE_FLOAT,     P_ANIMATABLE,     IDS_SPIN, 
        //Zero or more optional tags
        p_default,         1.0f, 
        p_range,         0.0f,1000.0f, 
        p_ui,             TYPE_SPINNER,        EDITTYPE_FLOAT, IDC_SIZE_EDIT,    IDC_SIZE_SPIN, 0.50f, 
        p_end,
    widget_left,        _T("Left"),         TYPE_FLOAT,     P_ANIMATABLE,     IDS_SPIN, 
        //Zero or more optional tags    
        p_default,         0.0f, 
        p_range,         0.0f,100.0f, 
        p_ui,             TYPE_SPINNER,        EDITTYPE_UNIVERSE, IDC_LEFT_EDIT,    IDC_LEFT_SPIN, 0.50f, 
        p_end,
    widget_right,        _T("Right"),         TYPE_FLOAT,     P_ANIMATABLE,     IDS_SPIN, 
        //Zero or more optional tags    
        p_default,         0.0f, 
        p_range,         0.0f,100.0f, 
        p_ui,             TYPE_SPINNER,        EDITTYPE_UNIVERSE, IDC_RIGHT_EDIT,    IDC_RIGHT_SPIN, 0.50f, 
        p_end,
    p_end
    );

The constants prefixed with IDC_ are the components of the IDD_PANEL rollout panel, and those prefixed with IDS_ are constants defined in the string table, both included in the .rc file.