twitter youtube facebook linkedin email
Connect with:

Design Animation Stories

Creating Plug-ins that Work, Part 2

Drew
17 December, 2015

In the first part of this story series, we created a simple Hello World Utility plug-in, derived from UtilityObj. In this installment, we are going to modify our utility to do something a little more useful: display the name of any selected scene object. Along the way we will learn a bit about 3ds Max’s reference system.

The Reference System

Plug-ins can use the 3ds Max reference system to be notified when another entity changes in the scene. If the referenced object (the reference target) changes, is deleted or updated, your plug-in (the reference maker) is automatically notified. An example of references in use is a scene object “following” a second object. Whenever the target object’s position changes, the first object is notified and can update its own coordinates accordingly. Another example is a camera with a target. We will see in a later story that plug-ins also have a reference to their parameters (stored in an iParmBlock2 object), and update themselves whenever their parameters change.

You should always use 3ds Max references rather than a general pointer when keeping track of an object. In order to use the reference system, your plug-in must extend from the class ReferenceMaker. Most plug-in classes already inherit directly or indirectly from Referencemaker, although UtilityObj is one exception, so we will need to explicitly extend in our code.

 

Creating the UI

Before we start writing code, let’s set up our plug-in’s UI. In the Resource View in Visual Studio, expand the HelloMax.rc file, open the Dialog folder, and open the IDD_PANEL dialog. This is the default dialog created by the plug-in wizard. Delete the label and spinner widgets, and add a static text, setting the text to “uninitialized” and the ID to IDC_NODENAME.

image017

Note: increase the size of the static text a bit, so the scene object name is not truncated.

 

Now let’s work on the code. The first thing we need to do is inherit from ReferenceMaker:

class HelloMax : public UtilityObj , ReferenceMaker

 

We will implement these four ReferenceMaker virtual functions:

  • NumRefs() – returns the number of references we have.
  • GetReference() – gives 3ds Max access to our references.
  • SetReference() – allows 3ds Max to set one of our references. The plug-in does not call this to set a reference, it should use ReplaceReference() or DeleteReference().
  • NotifyRefChanged() – this is called by 3ds Max whenever our target has changed.

Now we need to implement some of the ReferenceMaker functions. Add these prototypes to the public section of the class declaration:

       // Reference Maker:

       // Return how many references we support
       int NumRefs();

       // Max calls this function to see what references we hold
       ReferenceTarget* GetReference(int i);

       // Max calls this function to set a reference on us
       void SetReference(int i, ReferenceTarget* pTarget);

       // Max calls this function to let us know of a changed state on our references.
       RefResult NotifyRefChanged(const Interval &changeInt, RefTargetHandle hTarget, PartID& partID, RefMessage message, BOOL propagate);

 

We will implement these later.

We also need to be notified of selection changes in the scene, so we will implement the UtilityObj::SelectionSeteChanged function, a callback that 3ds Max calls whenever a new node is selected in the scene. Add this prototype to the class declaration:


       // This callback is executed whenever
       // a new node is selected in the scene.
       void SelectionSetChanged(Interface *ip,IUtil *iu);

 

Finally, we need to keep track of the currently selected scene node, so we’ll use an INode* data member. We also need a function to set the text in our UI.

Add these prototypes to the private section of your class declaration:

       // Our reference pointer.
       INode* mpMyNode;

       // A small function to set the text on our
       // dialog to the currently referenced nodes name.
       // s is the input string to be displayed.
       void SetText(const MSTR& s);

 

Now it’s time to implement these functions. First, let’s implement the ReferenceMaker functions:

First, NumRefs(), which simply returns 1 since we are maintaining a single reference to the currently selected Node:

int HelloMax::NumRefs()
{
       return 1;
}

GetReference() returns our reference:

// Max calls this function to see what references we hold
ReferenceTarget* HelloMax::GetReference(int i)
{
       return mpMyNode;    //we have only one reference in this plug-in
}

SetReference() is a little more involved. 3ds Max calls this function to set our reference, and we need to respond by setting our data member mpMyNode to the target. First we cast the ReferenceTarget pointer to an INode. If this cast is null, we know nothing is currently selected. We can also get the selected node’s name, and update our UI text.

// Max calls this function to set a reference on us
// NOTE: we should never call it directly. Use ReplaceReference()
//       when you need to change your reference.
void HelloMax::SetReference(int i, ReferenceTarget* pTarget)
{
       switch (i)
       {
       case 0:                          //we have only one reference in this plug-in
             {
                    INode* tmp = dynamic_cast<INode*>(pTarget);
                    if (tmp != NULL) {
                           mpMyNode = tmp;
                           MSTR s(tmp->GetName());
                           s += _T(" is being observed now.");
                           SetText(s);
                    }
                    else {
                           SetText(_T("No node is currently being observed."));
                    }
             }
             break;
       }
}

Finally, let’s implement NotifyRefChanged(). Here we simply update the UI to indicate that the selected node has changed. Note that we need to return a RefResult to 3ds Max. In this case it’s REF_SUCCEEDED, but in some situations you can return REF_STOP to prevent the message from being propagated to dependents.

// Max calls this function to let us know of a changed state on our references.
RefResult HelloMax::NotifyRefChanged(const Interval &changeInt,          // this parameter is never used
                                                            RefTargetHandle hTarget,     // Our reference who is notifying us.
                                                            PartID& partID,                           // Can be additional information, depending on the message
                                                            RefMessage message,                   // Whats happened? This is the important info!
                                                            BOOL propagate)
{
       // Should be true, but just in case
       if (mpMyNode) {
             MSTR s(mpMyNode->GetName());
             s += _T(" is changed.");
             SetText(s);
       }
       return REF_SUCCEED;
}

 

Finally, let’s implement our convenience function to set the text on our plug-in’s rollout, using the Windows API SetDlgItemText():

// 3ds Max will call this function whenever a new scene object is selected
void HelloMax::SetText(const MSTR&s){
       // MSTR is the main string class in 3ds Max SDK.
       // hPanel is a data member pointing to the rollout page and
       // IDC_NODENAME is the name of the static text field in that rollout page.
       SetDlgItemText(hPanel, IDC_NODENAME, s.data());
}

Compile and run 3ds Max to see the results:

image019

Stay tuned for the next installment in this story series, where we will learn about Controllers and create a different kind of plug-in, an Animation Controller.

Drew

Drew is a Learning Content Developer at Autodesk.

0 Comments

'