Lesson 3: The Reference System
Plug-ins can use the 3ds Max reference system to be notified when another entity changes in the scene. A plug-in creates a reference to a second plug-in in the reference system schema, so if the second plug-in (the reference target) is changed, deleted or updated, the first plug-in (the reference maker) is automatically notified. A plug-in can be both a reference target and a reference maker at the same time. An example of references is a scene object following a second scene object. If the first object has a reference to the second one, it is notified whenever the coordinates of the second object changes, so it can update its own coordinates. Another example is a camera that is looking at a geometric object and its perspective needs to be updated whenever the parameters of the geometric object, such as position, orientation, size, material, etc. changes or when the object is deleted. We will also see in Lesson 6: Parameter Blocks that the plug-ins have a reference to their parameters (stored in a separate objects of the class IParamBlock2
) and update themselves whenever one of their parameters change. You can refer to the topic Reference System in the Programmers Guide to read detailed information about this.
In a 3ds Max plug-in, any pointer to an object that is set, accessed, modified or deleted using the specific guidelines of the reference system is called a reference. It is always recommended to maintain a 3ds Max reference rather than a general pointer when keeping track of an object. A general pointer only refers to a memory address and is not capable of determining what 3ds Max or other plug-ins do to the object in that address. The target object can even be deleted, leaving your plug-in with a hanging pointer. When you are using a reference, your plug-in is informed of any changes to its target object. In order to use the references system and register the pointers as references, your plug-in should extend from the class ReferenceMaker
. Most of the plug-in types are already extended directly or indirectly from ReferenceMaker
(although there are exceptions such as Utility
plug-ins).
Being a ReferenceMaker
, your plug-in will need to implement four virtual functions from that class. These functions are:
int ReferenceMaker::NumRefs();
3ds Max calls this function to know how many references we are holding in our plug-in class.
void ReferenceMaker::SetReference(int i, ReferenceTarget* pTarget);
3ds Max calls this function to give us a reference. We should store the given pointer in the reference whose index is given, and 3ds Max will notify us of any changes to the target node. Note: Only 3ds Max calls this function. Never call it to set or update one of your references. Use
ReferenceMaker::ReplaceReference()
instead.RefTargetHandle ReferenceMaker::GetReference(int i);
3ds Max calls this function to get the ith reference managed by our plug-in instance.
RefResult ReferenceMaker::NotifyRefChanged ( const Interval& changeInt, RefTargetHandle hTarget, PartID& partID, RefMessage message, BOOL propagate);
3ds Max calls this function to let us know that one of our references has been changed. The complementary information is provided in the list of arguments:
- Interval
changeInt
: this parameter is never used - RefTargetHandle
hTarget
: Our reference that has been changed or updated - PartID&
partID
: Additional information depending on the message - RefMessage
message
: Tells what exactly has happened.
- Interval
Please note that you should never directly assign the address of an object to your reference. Neither should you call ReferenceMaker::SetReference()
for this purpose. You will need to call reference handling methods such as ReferenceMaker::DeleteReference()
, or ReferenceMaker::ReplaceReference()
to modify or update your references.
Adding some functionality to your utility plug-in
The plug-in project in Lesson 1: Sample utility plug-in is a valid plug-in but does not perform any specific task other than prompting a string. To demonstrate how you can have your plug-in interact with 3ds Max, lets modify that utility plug-in to show the name of the selected scene object in the UI panel. We will need to add some new functions and edit some existing ones for this. We also add the data member INode* mpMyNode
to make a reference to the node in the scene graph that represents the currently selected scene object.
Creating a UI
Before starting to edit the source code, we need to make sure that we have a placeholder for the name of the selected node in the plug-in UI panel.
If you are using the plug-in wizard, add a static text widget (QLabel) to your dialog within the .ui file to show the name of the selected scene object. To do this, right-click and select Open for the plugin_form.ui in Visual Studio. This will open the form in Qt Designer. Drag in a new Label widget, set its text to "Uninitialized" and re-name it "NodeName_Label". You can delete the two place-holder labels ("Generated by Autodesk 3ds Max" and "TODO: Place panel") created by the plug-in wizard.
The project Lesson3 implements a utility plug-in class named SampleRef that is also a reference maker, and displays the name of the selected node in the viewport. The following code snippets are copied from that project.
Using the reference system in your utility plug-in
After creating the UI panel we need to know when a new scene object is selected by the user. Our data member INode* mpMyNode
is a reference to a node in the scene graph, and not to a scene object. Therefore it will be our responsibility to update this reference when the selected object in the viewport changes. The function UtilityObj::SelectionSetChanged()
will help us here. 3ds Max calls this function whenever the user selects a new object or deselects the old one. We will write a function to update the caption of the static text we added to the dialog. We name this function SetText()
here with the following implementation:
void SampleRef::SetText(const MSTR& s)
{
// MSTR is the main string class in 3ds Max SDK.
// ui is the instance of our Rollup UI
ui.NodeName_Label->setText(s);
}
We will need to call this function to update the text in the rollout page when the plug-in starts, a new node is selected, a selected node is changed or the node is de-selected. 3ds Max calls different functions in our plug-in when each of these situations happen. These function are:
UtilityObj::BeginEditParams()
is called when the utility plug-in starts and the rollout page appears. We introduced this function in lesson 1. Note that we only update our reference (and do not directly update the text) by calling theReplaceReference()
here. This will consequently cause 3ds Max to call ourSetReference()
, where we update the text.UtilityObj::SelectionSetChanged()
is called when the user selects another object in the viewport or when he deselects a previously selected object. The data memberINode* mpMyNode
in our plug-in is a reference to a node in the scene graph, and not directly to a scene object. When a new scene object is selected the currently referenced node stays unchanged and our reference will not be notified. We will use this function to update our reference manually in this case. Again, we only update our reference and the text is updated as a result, similar toBeginEditParams()
above.ReferenceMaker::NotifyRefChanged()
is called when there is a change in one of the reference targets of our plug-in. In this example, when the scene object that our reference node is representing changes its position or its name or gets deleted, the corresponding node (our reference) will be changed or deleted automatically. 3ds Max callsNotifyRefChanged()
to notify us of such an event. We change the text in the rollout page in that function to reflect the change.
The following code snippets show the implementation of these functions. The Interface::GetSelNode()
function used in the implementation of SampleRef::SelectionSetChanged()
returns a pointer to the INode
object corresponding to the currently selected object in the viewport.
void SampleRef::SelectionSetChanged(Interface *ip,IUtil *iu)
{
if (ip->GetSelNodeCount() > 0)
ReplaceReference(0, ip->GetSelNode(0));
else
ReplaceReference(0, NULL);
}
void SampleRef::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;
}
}
and:
RefResult SampleRef::NotifyRefChanged(const Interval& changeInt, RefTargetHandle hTarget,
PartID& partID, RefMessage message, BOOL propagate)
{
// Should be true, but just in case
if (mpMyNode)
{
MSTR s(mpMyNode->GetName());
s += _T(" is changed.");
SetText(s);
}
return REF_SUCCEED;
}
You can compile the Lesson3 project and then start 3ds Max to see your plug-in (named SampleRef) come to life. You can click on Utilities > More to find it in 3ds Max.
References and the Track View
The animatable references of a 3ds Max plug-in (i.e. references extended from the class Animatable
) are displayed in the Track View. We will see in Lesson 4: Animation Controllers that such references can also be viewed and possibly edited in the Curve Editor if they are controllers (i.e. extended from the class Control
).