Building Action Tables
 
 
 

This section discusses the approach developers may use to build action tables and make them available to the system. In most cases building an action table is fairly easy. There is more work involved if you choose to implement your own class derived from ActionItem, but in most cases that isn't needed.

An action table is built by making a static table of action descriptions ( ActionDescription ) and passing it to the constructor for ActionTable. For example, here is the code that builds the action table for the FFD modifier:

#define NumElements(array) (sizeof(array) / sizeof(array[0]))
static  ActionDescription spActions[] = {
   ID_SUBOBJ_TOP,
   IDS_SWITCH_TOP,
   IDS_SWITCH_TOP,
   IDS_RB_FFDGEN,
   ID_SUBOBJ_CP,
   IDS_SWITCH_CP,
   IDS_SWITCH_CP,
   IDS_RB_FFDGEN,
   ID_SUBOBJ_LATTICE,
   IDS_SWITCH_LATTICE,
   IDS_SWITCH_LATTICE,
   IDS_RB_FFDGEN,
   ID_SUBOBJ_SETVOLUME,
   IDS_SWITCH_SETVOLUME,
   IDS_SWITCH_SETVOLUME,
   IDS_RB_FFDGEN,
};
 
ActionTable* BuildActionTable()
{
   TSTR name = GetString(IDS_RB_FFDGEN);
   HACCELhAccel = LoadAccelerators(hInstance,
            MAKEINTRESOURCE(IDR_FFD_SHORTCUTS));
 
   intnumOps = NumElements(spActions);
   ActionTable* pTab;
   pTab = new ActionTable(   kFFDActions,
              kFFDContext,
              name,
              hAccel,
              numOps,
               spActions,
              hInstance);
   GetCOREInterface()->GetActionManager()->RegisterActionContext (kFFDContext, name.data());
   returnpTab;
}

The constructor for ActionTable takes the ID of the table, the context id, a name for the table, a windows accelerator table that gives default keyboard assignments for the operations, the number of items, the table of operation descriptions, and the instance of the module where the string resources in the table are stored.

At the same time the table is built, you also need to register the action context ID with the system. This is done with the IActionManager::RegisterActionContext() method.

The next part of implementing an ActionTable is to derive a class from ActionCallback. This is an abstract class. You will need to pass an instance of the derived class to 3ds Max when you activate the ActionTable by calling IActionManager::ActivateActionTable(). Then when the system wants to execute an action, you will get a callback to ActionCallback::ExecuteAction().

For the FFD modifier, this looks like:

template <class T>
class FFDActionCB : public ActionCallback
{
   public:
     T* ffd;
     FFDActionCB(T *ffd) { this->ffd = ffd; }
     BOOL ExecuteAction(int id);
};
 
template <class T> BOOL FFDActionCB<T>::ExecuteAction(int id)
{
   switch (id)
   {
     case ID_SUBOBJ_TOP:
      ffd->ip->SetSubObjectLevel(SEL_OBJECT);
      ffd->ip->RedrawViews(ffd->ip->GetTime());
      return TRUE;
     case ID_SUBOBJ_CP:
      ffd->ip->SetSubObjectLevel(SEL_POINTS);
      return TRUE;
     case ID_SUBOBJ_LATTICE:
      ffd->ip->SetSubObjectLevel(SEL_LATTICE);
      return TRUE;
     case ID_SUBOBJ_SETVOLUME:
      ffd->ip->SetSubObjectLevel(SEL_SETVOLUME);
      return TRUE;
   }
   returnFALSE;
}

FFD uses a template class to implement several versions of this callback, but this is not required.

Finally, the system needs to activate and deactivate the table at the appropriate time. When to do this depends on the scope of applicability of the table. If your ActionTable is exported from an editable object of modifier plugin, then you typically want it only to be active when editing the object or modifier. This is done by activating it in your plugin's Animatable::BeginEditParams() method, and deactivating it in Animatable::EndEditParams().

For FFD, this looks like this:

ffdActionCB = new FFDActionCB<FFDMod >(this);
ip->GetActionManager()->ActivateActionTable(ffdActionCB, kFFDActions);

The first parameter is the ID of the table to activate, and the second is an instance of the ActionCallback class that is responsible for executing actions. In EndEditParams(), we deactivate the table:

ip->GetActionManager()->DeactivateActionTable(ffdActionCB, kFFDActions);
delete ffdActionCB;

For other types of plugins the table can be activated at different times. For example, you could write a GUP plug-in that activates the table when the plug-in is loaded to provide actions that are always available.