Making Controllers Lockable
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 fromILockedTrackImp
such asStdLockableControl
orLockableControl
.- When possible prefer
StdLockableControl
orLockableControl
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. - Make sure to keep the same logic if you override the default implementations of
Animatable::GetInterface()
,Control::IsReplaceable()
, andControl::CanApplyEaseMultCurves()
.
- When possible prefer
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()
andReferenceMaker::Save()
functions are implemented and that you save and load theILockedTrackImp::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 ofILockedTrackImp::mLocked
.In your implementation of
Control::Copy()
, make sure to copy the value ofmLocked
.Note that only the functions
Save()
,Load()
,Clone()
, andCopy
require explicit use of theILockedTrackImp::mLocked
variable. Other functions which need to query the locked status of a controller must use theILockedTrackImp::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 modifyAnimatable::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()
returnstrue
.If a controller is locked it must lock the functions
Animatable::BeginEditParams()
andAnimatable::EditTrackParams()
. You might also need to modifyAnimatable::EndEditParams()
accordingly. If you do normally allow the editing of track parameters, you must returnTRACKPARAMS_NONE
from theAnimatable::TrackParamsType()
function when the track is locked.In addition to the
Animatable
functions mentioned above, otherAnimatable
functions that must be locked are those that modify the keys or other information in your controller, such asAnimatable::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()
, andAnimatable::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 whenControl::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 asControl::SetPositionController()
, orControl::SetRollController()
).Control functions that must never get locked include
Control::GetValue()
andControl::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 includeIKeyControl::SetKey()
andIKeyControl::AppendKey()
. Note thatIKeyControl::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 lockControl::ChangeParents()
because when a parent changes you will want the keys to modify, if needed. Also, you might need to unlock sub controllers, usingILockedTracksMan::PushUberUnLockOverride()
andILockedTracksMan::PopUberUnLockOverride()
when changing parents before callingControl::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 theILockedTracksMan::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.