plugin cookbook recipe #2

show or hide description elements

Sometimes you want to hide a description element or elements and reveal others, usually depending on the value of other parameters in the description. So for example, you might want to hide certain description elements when a checkbox is turned off, because they are no longer relevant, or show/hide groups of elements depending on the option selected in a dropdown menu.

This is not as straightforward as it could be. One reason is that it uses an undocumented function call in the SDK (but this is the accepted Maxon way of doing this). So exactly why this technique works isn't known (by me, anyway), just that it does.

the description

Let's say you have a description, part of which looks like this:

// part of description resource
BOOL SHOW_ELEMENT_1 { }
LONG ELEMENT_1 { MIN 0; MAX 100; }
LONG DROP_BOX { CYCLE { ENTRY_1; ENTRY_2; } }

REAL ELEMENT_2 { MIN -1000.0; MAX 0.0; UNIT REAL; }
// description continues

What you want is for ELEMENT_1 to be shown if the checkbox SHOW_ELEMENT_1 is checked, and to hide it if the box is unchecked. For ELEMENT_2, you want it to be revealed or hidden depending on the entry in the DROP_BOX element selected by the user. To do these things you need to implement the virtual function GetDDescription().

GetDDescription()

If implemented, this function is polled by C4D and the description updated as required. Here's the sample code, using the description shown above:

BoolMyPlugin::GetDDescription(GeListNode *node, Description *description, DESCFLAGS_DESC &flags)
{
    BaseContainer *data, *bc;
    const DescID *singleid;
    DescID cid;

    if(!description->LoadDescription(node->GetType()))
        return FALSE;

    data = ((BaseList2D*)node)->GetDataInstance();
    singleid = description->GetSingleDescID();    // <---- undocumented function call

     // for ELEMENT_1, check the state of SHOW_ELEMENT_1 and show/hide the parameter as required
    cid = DescLevel(ELEMENT_1, DTYPE_LONG, 0);
    if(!singleid || cid.IsPartOf(*singleid, NULL))
    {
        bc = description->GetParameterI(cid, NULL);
        bc->SetBool(DESC_HIDE, data->GetBool(SHOW_ELEMENT_1);
    }

    // for ELEMENT_2, check the value in the dropdown menu DROP_BOX and show or hide the element as required
    cid = DescLevel(ELEMENT_2, DTYPE_REAL, 0);
    if(!singleid || cid.IsPartOf(*singleid, NULL))
    {
        bc = description->GetParameterI(cid, NULL);
        if(data->GetLong(DROP_BOX) == ENTRY_1)
            bc->SetBool(DESC_HIDE, TRUE);
        else
            bc->SetBool(DESC_HIDE, FALSE);
    }

    flags |= DESCFLAGS_DESC_LOADED;
    return TRUE;
}

 

how it works

The first thing is to load the description from disk using the LoadDescription() call. This takes the ID of the object whose description is to be loaded, which we get by calling GetType() on the node. Then we get the node's base container so we can access the description element values. Then comes the undocumented function call (it's marked 'Private' in the SDK) which returns a pointer to a DescID.

Having done that, it's just a matter of working through each element in the description we want to show or hide. For each element we create a DescID, which requires the element ID value and a value indicating what type of element it is - here we have a DTYPE_LONG for ELEMENT_1 and a DTYPE_REAL for ELEMENT_2. Then there is the check for the existence of the DescID pointer returned earlier and the 'IsPartOf' call (that one IS documented, but it's difficult to understand what it does in this context). Then we get the base container for the element itself.

In each case we need to set a Bool in the base container with the ID value DESC_HIDE. For ELEMENT_1, we directly set the value we get from the checkbox SHOW_ELEMENT_1. For ELEMENT_2, we get the selected value from the drop menu and set the Bool accordingly. If DESC_HIDE is set to TRUE, the element will be hidden; if it's set to FALSE, the element will be shown.

Finally, note the important last-but-one line in the code. You MUST set the DESCFLAGS_DESC_LOADED bit in the flags parameter before returning from the function. This is what tells Cinema to update the description. If you don't do this, nothing will change.

And that's all there is to it. There is one other point. Suppose you have a lot of elements to show or hide. You don't necessarily have to work through this process with each of them. In the description, put them all inside a GROUP element then show or hide the group itself. You need to use DTYPE_GROUP when creating the DescID.