Making Controllers Lockable
 
 
 

The following are instructions on how to make a controller lockable. Also note that the samples in maxsdk\samples\controllers are lockable.

  1. Include the header file ILockedTracks.h
  2. Derive your controller plug-in publicly from ILockedTrackImp directly or from a class that derives from ILockedTrackImp such as StdLockableControl or LockableControl.
    • When possible prefer StdLockableControl or LockableControl since they already contain default implementations of 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 though that if you override the default implementations of Animatable::GetInterface(), Control::IsReplaceable(), and Control::CanApplyEaseMultCurves() that you keep the same logic.
  3. Make sure the Animatable::GetInterface() function is defined since it's 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);  
      }
    }
    
  4. If your controller is a leaf controller, you need to disable of appending of ease and mult curves to your controller. To do this you'll need to modify the Control::CanApplyEaseMultCurves() to return false when the controller is locked. If it's not a leaf controller, there is no need to worry about this function since you can't apply eases or mult controllers to non-leaf controllers.
    BOOL CanApplyEaseMultCurves() {
      return !GetLocked(); 
    }
    
  5. When a controller is locked it cannot be replaced, so you will need to modify the Control::IsReplaceable() function. A sample implementation is:
      BOOL IsReplaceable() {
        return !GetLocked();
      }
    
  6. Make sure that the ReferenceMaker::Load() and ReferenceMaker::Save() functions are implemented and that you save and load the ILockedTrackImp::mLocked member variable. Here's a sample implementation,
    #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;	
    }
    
  7. In your implementation of ReferenceMaker::Clone() make sure to clone the inherited value of ILockedTrackImp::mLocked.
  8. In your implementation of Control::Copy() make sure to copy the value of mLocked.
  9. 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 should use ILockedTrackImp::GetLocked() function since that will also take into consideration any active overrides or container states to determine if the controller is locked.
  10. Inside the controller's implementation of Animatable::AssignController() you need to check both to see if the controller is locked, in which case you don't assign the controller, and the controller that's getting assigned over isn't locked also. You should also modify Animatable::CanAssignController() but that function cannot be used reliably to determine if a controller may get assigned.
  11. Some functions need to be locked: meaning that they return immediately if ILockedTrackImp::GetLocked() returns true.
  12. If a controller is locked it should lock the functions Animatable::BeginEditParams() and Animatable::EditTrackParams(). You may also need to modify Animatable::EndEditParams() accordingly. If you do normally allow the editing of track parameters you should return TRACKPARAMS_NONE from the Animatable::TrackParamsType() function when the track is locked.
  13. In addition to the Animatable functions mentioned above, other Animatable functions that should be locked the control is 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(), etc.
  14. You should also lock schematic view functions that change links, such as Animtable::SvCanConcludeLink(), and Animatable::SVLinkChild().
  15. You usually don't have to lock functions that set selection (Animatable::SetKeyByIndex, etc..) or flag keys (Animatable::FlagKey()) because in most circumstances the selection or flag state of a key doesn't change how that controller computes its value. In general only lock operations that will have an effect when Control::GetValue() is called.
  16. 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()).
  17. Control functions that should never get locked include Control::GetValue() and Control::ChangeParents().
  18. 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 may not work.
  19. If the controller inherits from IKeyControl, then some of its functions also need to get locked. They include IKeyControl::SetKey() and IKeyControl::AppendKey(). Note that IKeyControl::GetKey() returns a pointer to the key which in theory the client may modify, but can't be locked because it is the only way a client can examine the contents of a key.
  20. For PRS controllers, you will need to lock the setting of inheritance flags in Control::SetInheritanceFlags() Don't lock Control::ChangeParents() becaue when a parent changes you will want the keys to modify if needed. Also you may need to unlock sub controllers, via the ILockedTracksMan::PushUberUnLockOverride() and ILockedTracksMan::PopUberUnLockOverride() when changing parents before calling Control::SetValue() on them.
  21. If you need to unlock something temporarily the best way to do it so the 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 since if a controller is in an imported container, it can't get unlocked via the other SDK functions.
  22. We currently don't lock exposed SDK or MAXScript published functions that may set internal values that are normally locked. Usually this isn't a problem since we prevent display of the default UI.