Follow these steps to make a controller lockable. Also, note that the samples in maxsdk\\samples\\controllers
are lockable.
Include the header file ILockedTracks.h
.
Derive your controller plug-in publicly from ILockedTrackImp
directly or from a class that derives from ILockedTrackImp
such as StdLockableControl
or LockableControl
.
StdLockableControl
or LockableControl
because they already contain default functions needed to properly lock your controller. For example they contain all of the logic needed to determine whether or not an item is really locked, and handles the default mechanism for the unlocking and locking of parent and child sub-animatables.Animatable::GetInterface()
, Control::IsReplaceable()
, and Control::CanApplyEaseMultCurves()
.Make sure the Animatable::GetInterface()
function is defined because it is where the locked track interface resides. At a minimum you need to implement it as follows:
void* GetInterface(ULONG id)
{
switch (id) {
case I_LOCKED:
return (ILockedTrackImp*) this;
default:
return Control::GetInterface(id);
}
}
If your controller is a leaf controller, you need to disable the appending of ease and mult curves to your controller. To do this, you need to modify the Control::CanApplyEaseMultCurves()
function to return false when the controller is locked. If it is not a leaf controller, there is no need to worry about this function because you cannot apply eases or mult controllers to non-leaf controllers.
BOOL CanApplyEaseMultCurves() {
return !GetLocked();
}
When a controller is locked it cannot be replaced, so you need to modify the Control::IsReplaceable()
function as follows:
BOOL IsReplaceable() {
return !GetLocked();
}
Make sure that the ReferenceMaker::Load()
and ReferenceMaker::Save()
functions are implemented and that you save and load the ILockedTrackImp::mLocked
member variable as shown in the following example:
#define LOCK_CHUNK 0x2535
IOResult MasterPointControlImp::Save(ISave *isave)
{
Control::Save(isave); // save ORT
ULONG nb;
int on = (mLocked==true) ? 1 :0;
isave->BeginChunk(LOCK_CHUNK);
isave->Write(&on,sizeof(on),&nb);
isave->EndChunk();
return IO_OK;
}
IOResult MasterPointControlImp::Load(ILoad *iload)
{
ULONG nb;
IOResult res;
res = Control::Load(iload); // Load ORT
if (res!=IO_OK) return res;
while (IO_OK==(res=iload->OpenChunk()))
{
ULONG curID = iload->CurChunkID();
if (curID==LOCK_CHUNK)
{
int on;
res=iload->Read(&on,sizeof(on),&nb);
mLocked = on ? true : false;
}
iload->CloseChunk();
if (res!=IO_OK) return res;
}
return IO_OK;
}
In your implementation of ReferenceTarget::Clone()
, make sure to clone the inherited value of ILockedTrackImp::mLocked
.
In your implementation of Control::Copy()
, make sure to copy the value of mLocked
.
Note that only the functions Save()
, Load()
, Clone()
, and Copy
require explicit use of the ILockedTrackImp::mLocked
variable. Other functions which need to query the locked status of a controller must use the ILockedTrackImp::GetLocked()
function because that takes into consideration any active overrides or container states to determine if the controller is locked.
Inside the controller's implementation of Animatable::AssignController()
, you need to check both to see if the controller is locked, in which case you do not assign the controller, and the controller that is getting assigned over is also not locked. You must also modify Animatable::CanAssignController()
, but that function cannot be used reliably to determine if a controller might get assigned.
Some functions need to be locked: meaning that they return immediately if ILockedTrackImp::GetLocked()
returns true
.
If a controller is locked it must lock the functions Animatable::BeginEditParams()
and Animatable::EditTrackParams()
. You might also need to modify Animatable::EndEditParams()
accordingly. If you do normally allow the editing of track parameters, you must return TRACKPARAMS_NONE
from the Animatable::TrackParamsType()
function when the track is locked.
In addition to the Animatable
functions mentioned above, other Animatable
functions that must be locked are those that modify the keys or other information in your controller, such as Animatable::CopyKeysFromTime()
, Animatable::DeleteKeyAtTime()
, Animatable::DeleteKeys()
, Animatable::DeleteTime()
, Animatable::ReverseTime()
, Animatable::ScaleTime()
, Animatable::InsertTime()
, Animatable::MapKeys()
, Animatable::AddNewKey()
, Animatable::MoveKeys()
, Animatable::ScaleKeyValues()
, Animatable::SetSelKeyCoordsExpr()
, Animatable::CommitSetKeyBuffer()
, Animatable::RevertSetKeyBuffer()
, and others.
You must also lock schematic view functions that change links, such as Animtable::SvCanConcludeLink()
, and Animatable::SVLinkChild()
.
You usually do not have to lock functions that set selection (Animatable::SetKeyByIndex
, and others) or flag keys (Animatable::FlagKey()
) because in most circumstances the selection or flag state of a key does not change how that controller computes its value. In general, only lock operations will have an effect when Control::GetValue()
is called.
The control functions that need to be locked include Control::SetValue()
, Control::CreateLockKey()
, and any function that sets a sub-controller (such as Control::SetPositionController()
, or Control::SetRollController()
).
Control functions that must never get locked include Control::GetValue()
and Control::ChangeParents()
.
For most controllers, the only IK function that needs to get locked is Control::PasteIKParams()
. The other functions are needed to be left unlocked or IK might not work.
If the controller inherits from IKeyControl
, then some of its functions also need to be locked. They include IKeyControl::SetKey()
and IKeyControl::AppendKey()
. Note that IKeyControl::GetKey()
returns a pointer to the key which in theory the client might modify, but cannot be locked because it is the only way a client can examine the contents of a key.
For PRS controllers, you will need to lock the setting of inheritance flags in Control::SetInheritanceFlags()
Do not lock Control::ChangeParents()
because when a parent changes you will want the keys to modify, if needed. Also, you might need to unlock sub controllers, using ILockedTracksMan::PushUberUnLockOverride()
and ILockedTracksMan::PopUberUnLockOverride()
when changing parents before calling Control::SetValue()
on them.
If you need to unlock something temporarily, the best way is to use the ILockedTracksMan::PushUberUnLockOverride()
function to set up an override that disables all locks, and then the ILockedTracksMan::PopUberUnLockOverride()
function to revert the override unlock state. This is safer than individual unlocking the controller because if a controller is in an imported container, it cannot get unlocked through the other SDK functions.
We currently do not lock exposed SDK or MAXScript published functions that might set internal values, which are normally locked. Usually, this is not a problem because we prevent display of the default UI.