Scripted Animation Controller Plug-ins

Controllers are plug-ins that handle the animation tasks in 3ds Max. They are typically assigned to object in Track View or on the Motion panel, though they can also be assigned programmatically (see the examples later in this section).

Scripted animation controllers in MAXScript support parameter blocks and rollouts.

Scripted animation plug-in controllers are defined for the following superclasses:

Event Handlers

Scripted animation controller plug-ins require at least a getValue event handler. All other event handlers are optional. Handlers associated with the hold/restore system are not normally implemented. See the discussion in Hold and Restore System

Note: If an unhandled runtime error is thrown while evaluating any of these handlers, the plug-in instance is disabled.

on create do

Called with an instance of the controller is created in a scene. This handler is used to set state variables for the controller (see Local Variables below). For more information about the on create handler, see the Event Handlers section in Scripted Plug-in Clauses_TOPIC_740.

on getValue do

Returns a value, the type depending upon the controller type.

on setValue absVal relVal commit do

Called when the controller is set to a value, either via UI, MAXScript, or other max code. This handler is optional.

If the setValue was made in relative mode (this can be tested with method == #relative), then relVal contains the delta from the current value while absVal is an absolute value pre-computed for your convenience. If the setValue was made in absolute mode (method == #absolute), then relVal is the same as absVal.

The commit parameter specifies whether the controller should persist or simply cache the setting of the value. If false, the commitValue and restoreValue handlers are required. However, as far as is known, no current plug-ins ever pass this value as false, and so these handlers are not required. See Hold and Restore System below for more information.

on mouseCycleStarted do

Called when the user is beginning to modify the controller with the mouse in a viewport. For example, when moving a node in a viewport, this is called on each transform-related controller.

This handler is optional, and primarily supports the hold/restore system.

on mouseCycleCompleted do

Called when the user is finishing modifying the controller with the mouse in a viewport. For example, when moving a node in a viewport, this is called on each transform-related controller.

Note: in some cases mouseCycleCompleted can be called without an associated mouseCycleStarted. An example of this is when moving a controller's keys in TrackView.

This handler is optional, and primarily supports the hold/restore system.

on holdBegin do

Called when the controller's value is beginning to be modified.

This handler is optional, and primarily supports the hold/restore system.

on holdEnd do

Called when the controller's value has finished being modified.

This handler is optional, and primarily supports the hold/restore system.

on holdRestore do

Called when a controller is reset to an earlier value by the undo system, such as when a user performs an undo of a controller set value.

This handler is optional, and primarily supports the hold/restore system.

on holdRedo do

Called when a controller is set to a later value by the undo system. This is called when a user performs a redo of a controller set value.

This handler is optional, and primarily supports the hold/restore system.

on commitValue do

Called when the commit parameter for the setValue handler is false, which means the controller isto persist the setting of the value from the cached value.

This handler is optional, and primarily supports the hold/restore system.

on restoreValue do

Called when the commit parameter for the setValue handler is false, which means the controller is to not use the cached value.

This handler is optional, and primarily supports the hold/restore system.

Hold and Restore System

The commit parameter for setValue and the commitValue/restoreValue handlers is part of an "inner" hold and restore mechanism. When the controller's SetValue() method is called, if the commit parameter is true, then the controller should save the value of the controller at the current time into its cache and also 'commit' the value. For example, this stores a key in the case of a keyframe controller. If the set value was not committed then RestoreValue() may be called to restore the previous value. This restores the current cache value to the value that was set before SetValue() was last called.

Note: The commit parameter to setValue, as far as is known, is never passed as false by any current plug-in, and may be removed in future iterations of controller scripted plug-in types. However, the controller framework does provide this inner hold and restore mechanism, and may be used by third-party plug-ins.

The example script for FloatController below includes detection and information display if this mechanism is used.

Local Variables

The following local variables are available to scripted animation plug-in controllers.

isLeaf - read/write - bool value.

Indicates whether the controller is a leaf controller. If a controller is a leaf controller, then it must not by definition have any sub-controllers or references. The controller should return TRUE if it has no sub-controllers. For example, a PRS controller is not a leaf controller (because it has sub-controllers for Position, Rotation and Scale), but a simple keyframed float controller is a leaf controller.

Ease/Multiplier curves and Out-Of-Range Types can only be applied to Leaf controllers. Various filtering in TreeView depends on whether the controller is a Leaf controller or not. This variable defaults to false. This variable is typically set in the 'on create' handler.

isKeyable - read/write - bool value

Indicates whether UI elements (a spinner or slider) linked to the parameter the controller is applied to is enabled. If this variable is false, the UI element is disabled. This variable defaults to false if a setValue handler is not defined, true if defined. If you want to set this variable, it would typically be set in the 'on create' handler.

method - read-only - name value

For use within thesetValue handler. Specifies whether the call occurred with method type #relative or #absolute.

parentTransform - read-only - Matrix3 value

This variable is for transform-related scripted plug-in controllers, and is 'undefined' for other scripted plug-in controllers. For use within the on getValue handlers. This variable contains the (modified) parent node transform when evaluating the on getValue handler for transform-related scripted plug-in controllers (positionController, rotationController, scaleController, transformController) when method == #relative. A copy of the parent's transform matrix is passed to the node's transform controller, and the transform controller and its sub-controllers can operate on and modify the Matrix3 value. This value represents the current transform value as it passes through the controller pipeline.

For example, in a PRS controller, the Position controller typically translates the Matrix3 value by its Point3 value, and then the modified Matrix3 value is passed to the Rotation controller, which typically rotates the Matrix3 value, and then the modified Matrix3 value is passed to the Scale controller. This value is read-only - the C++ implementation of the scripted plug-in operates on this Matrix3 value based on the value returned from the getValue handler.

If method == #absolute in the on getValue handler of a transform-related controller, the controller is being directly evaluated (for example, via .value in MaxScript), and the parentTransform will be an identity matrix.

usesParentTransform - read/write - bool value

This variable controls the caching of the current value of the scripted plug-in controllers. Normally, the controller caches its validity interval and current value of the controller. If the time value of a getValue call occurs is within the validity interval, the cached current value is returned without evaluating the getValue handler. If the return value of getValue handler call is dependent on the parent's transform value, this will cause incorrect results if the parent node is transformed, because no notification is sent from the parent node to all the child node transform-related controllers, invalidating those controllers. When this variable is true, the cached current value will never be used, and the getValue handler call is always called. This will cause more evaluations to occur, so it should only be set to true for transform-related scripted plugin controllers whose result are dependent on the parent's transform. This variable defaults to false. This variable is typically set in the create handler.

FloatController

The float controller plug-in type controls floating point values. The example below adds an offset value to a float, and the test case controls the x position of an object. Note that this sample illustrates implementing a param block and rollout UI, which is visible on the Motion panel when the controlled object is selected. It also illustrates how to use the hold/restore/redo system by implementing on commitValue, restoreValue, and the holdBegin/holdEnd/holdRestore/holdRedo handlers. As noted in the Hold and Restore System above, these handlers are not believed to be required, since no current shipped plug-in uses this mechanism. However, there may be 3rd-party plug-ins that do.

plugin FloatController FloatController_example
name:"FloatController Example"
classID:#(0x47db14fe, 0x4e9b5f90)
(
    local first = true -- used for debug tracing of 'commit' handling
    local useCachedValue = false -- set when have SetValue with commit == false
    local cachedValue = undefined -- set when have SetValue with commit == false
    local inMouseCycle = false -- true when within mouseCycleStarted/mouseCycleCompleted
    local inMouseCycleControllerAssigned = false -- true when controller assigned within mouseCycleStarted/mouseCycleCompleted
    local inMouseCycleSetValue = false -- true when setValue w/o holdRestore occurred within mouseCycleStarted/mouseCycleCompleted

    parameters pblock rollout:params
    (
        value type:#float animatable:true ui:value
        valueController type:#maxobject
    )
    rollout params "FloatController Test Parameters"
    (
        Spinner value "Value:" 
    )
    on getValue do 
    (
        format "getValue - useCachedValue: %; value: %; cachedValue: %\n" useCachedValue value cachedValue
        if useCachedValue then cachedValue else value
    )
    on setValue val relVal commit do 
    (
        format "setValue - val: %; commit: %; useCachedValue: %; value: %; cachedValue: %\n" val commit useCachedValue value cachedValue
        if not commit and first do
        (
            print (CaptureCallStack())
            first = false
            messagebox "setValue called with commit = false - see listener for stack trace"
        )
        if commit then
        (
            useCachedValue = false
            cachedValue = undefined
            if inMouseCycle do
                inMouseCycleSetValue = true
            value = val
        )
        else
        (
            useCachedValue = true
            cachedValue = val
        )
        SetControllerValue valueController val commit #absolute
    )
    on commitValue do
    (
        format "commitValue - useCachedValue: %; value: %; cachedValue: %\n" useCachedValue value cachedValue
        if first do
        (
            print (CaptureCallStack())
            first = false
            messagebox "commitValue called - see listener for stack trace"
        )
        useCachedValue = false
        value = cachedValue
        cachedValue = undefined
        CommitControllerValue valueController
    )
    on restoreValue do
    (
        format "restoreValue - useCachedValue: %; value: %; cachedValue: %\n" useCachedValue value cachedValue
        if first do
        (
            print (CaptureCallStack())
            first = false
            messagebox "restoreValue called - see listener for stack trace"
        )
        useCachedValue = false
        cachedValue = undefined
        RestoreControllerValue valueController
    )
    on mouseCycleStarted do 
    (
        format "mouseCycleStarted - holding: %; value.controller: %\n" (theHold.Holding()) value.controller
        inMouseCycle = true
        inMouseCycleControllerAssigned = false
    )
    on mouseCycleCompleted do 
    (
        format "mouseCycleCompleted - holding: %; value.controller: %\n" (theHold.Holding()) value.controller
        inMouseCycle = false
        inMouseCycleControllerAssigned = false
    )
    on holdBegin do 
    (
        format "holdBegin\n" 
        if inMouseCycle and animateMode and value.controller == undefined and (currenttime != 0) do
        (
            inMouseCycleControllerAssigned = true
            with undo off value.controller = NewDefaultFloatController()
        )
    )
    on holdEnd do 
    (
        format "holdEnd - inMouseCycleControllerAssigned: %; inMouseCycleSetValue: %\n" inMouseCycleControllerAssigned inMouseCycleSetValue
        if inMouseCycleControllerAssigned and not inMouseCycleSetValue do
            with undo off value.controller = undefined
    )
    on holdRestore do 
    (
        format "holdRestore\n" 
        if inMouseCycle do
            inMouseCycleSetValue = false
    )
    on holdRedo do format "holdRedo\n" 

    on create do
    (
        valueController = NewDefaultFloatController()
        isLeaf=true
    )
)
/*  Test case:
t = teapot isselected:true
t.radius.controller = FloatController_test()
t.pos.controller.x_position.controller = FloatController_example()
*/

This is a more typical version of the same float controller that does not implement the Hold/Restore system. It also shows how you can make a procedural controller - in this case, modifying the controlled float value with the current time as well as an offset. Note the use of usePBValidity:false in the plug-in definition.

plugin FloatController FloatController_simplified_example
name:"Simplified FloatController Example"
usePBValidity:false -- false because time dependent
classID:#(0xc1dbcd6, 0x70ecb08a)
(    
    parameters pblock rollout:params
    (
        offset_value type:#float animatable:true ui:offset_value
  valueController type:#maxobject subAnim:true
    )
    rollout params "FloatController Test Parameters"
    (
        Spinner offset_value "Offset Value:" range:[0, 1e9, 40]
    )
    on getValue do 
    (
        valueController.value +offset_value + currentTime as integer / framerate
    )
    on setValue val relVal commit do 
    (
        val -= ( offset_value+ currentTime as integer / framerate)
        SetControllerValue valueController val commit #absolute
    )    
    on create do
    (
        valueController = NewDefaultFloatController()
        isLeaf=true
    )
)
/*  Test case:
t = teapot isselected:true
t.radius.controller = c = FloatController_simplified_example()
c.isKeyable = true
c.isleaf = false
*/

Point3Controller

A Point3Controller plug-in type controls a Point3 value. Note that this does not control a position value, a PositionController (see below) should be used for that purpose. This controller can be used for things such as coordinate offsets. In the example below, the controller is applied to a material’s diffusemap coordinates.

plugin Point3Controller Point3Controller_example
name:"Point3Controller Example"
classID:#(0x47db14ff, 0x4e9b5f9f)
(
    parameters pblock 
    (
        offset_value type:#point3 animatable:true 
        valueController type:#maxobject subAnim:true
    )
    on getValue do 
    (
        valueController.value + offset_value
    )
    on setValue val relVal commit do 
    (
        val -= offset_value
        SetControllerValue valueController val commit #absolute
    )

    on create do
    (
        valueController = NewDefaultPoint3Controller()
        isLeaf=true
    )
)
/*  Test case:
t = teapot isselected:true
t.material = standard diffusemap:(cellular())
t.material.diffusemap.coords.offset.controller = Point3Controller_example()
c = t.material.diffusemap.coords.offset.controller
meditmaterials[1] = t.material.diffusemap
max mtledit
t.pos.controller = Point3Controller_example()  -- will give: -- Runtime error: Incompatible controller type for property: position
*/

Point4Controller

This plug-in controller type controls Point4 values, such as 4D color space values in shaders.

This example adds an offset value to a Point4 value.

plugin Point4Controller Point4Controller_example
name:"Point4Controller Example"
classID:#(0x47db14ff, 0x4e9b5f9f)
(
    parameters pblock 
    (
        offset_value type:#point4 animatable:true 
        valueController type:#maxobject subAnim:true
    )
    on getValue do 
    (
        valueController.value + offset_value
    )
    on setValue val relVal commit do 
    (
        val -= offset_value
        SetControllerValue valueController val commit #absolute
    )

    on create do
    (
        valueController = NewDefaultPoint4Controller()
        isLeaf=true
    )
)
/*  Test case:
c = Point4Controller_example()
at time 100 with animate on c.offset_value =[100,200,300,400]
slidertime = 50f
c.offset_value 
c.value += [10,20,30,40]
c.offset_value 
c.value 
c.valueController.value
*/

ColorController

The ColorController plugin type controls the animation of a color value. In this example, the controller adds an offset to a color value, and the controller is applied to a material's diffuse color.

plugin ColorController ColorController_example
name:"ColorController Example"
classID:#(0x47db14ba, 0x4e9b5f9f)
(
    parameters pblock 
    (
        offset_value type:#color animatable:true 
        valueController type:#maxobject subAnim:true
    )
    on getValue do 
    (
        valueController.value + offset_value
    )
    on setValue val relVal commit do 
    (
        val -= offset_value
        SetControllerValue valueController val commit #absolute
    )
    on create do
    (
        valueController = NewDefaultColorController()
        isLeaf=true
    )
)
/*  Test case:
t = teapot isselected:true
t.material = standard ()
t.material.diffuse.controller = ColorController_example()
c = t.material.diffuse.controller
meditmaterials[1] = t.material
max mtledit
*/

PositionController

PositionController plug-in types control animatable positions. In the example below, the controller adds an offset to the controlled Point3 value, and is applied to a teapot object's position.

plugin PositionController PositionController_example
name:"PositionController Example"
classID:#(0x47db14fa, 0x4e9b5f9f)
(
    parameters pblock 
    (
        offset_value type:#point3 animatable:true 
        valueController type:#maxobject subAnim:true
    )
    on getValue do 
    (
        res = valueController.value + offset_value
        res
    )
    on setValue val relVal commit do 
    (
        val -= offset_value
        format "committing val: %\n" val
        SetControllerValue valueController val commit #absolute
    )

    on create do
    (
        valueController = NewDefaultPositionController()
        isLeaf=true
    )
)

/*  Test case:
t = teapot isselected:true
t.pos.controller = PositionController_example()
c = t.pos.controller 

*/

RotationController

Rotation controller plug-in types control a rotation value for a scene object. In the example controller below, the rotation value is stored as the rotation part of a Matrix3 value. The controller adds an offset to the controlled value, and is applied to an object's rotation.

plugin RotationController RotationController_example
name:"RotationController Example"
classID:#(0x47db14ac, 0x4e9b5f9f)
(
    parameters pblock 
    (
        offset_value type:#matrix3 animatable:true 
        valueController type:#maxobject subAnim:true
    )
    on getValue do 
    (
        res = valueController.value + offset_value.rotationpart
        res
    )
    on setValue val relVal commit do 
    (
        val -= offset_value.rotationpart
        SetControllerValue valueController (val as matrix3) commit #absolute
    )

    on create do
    (
        valueController = NewDefaultRotationController()
        isLeaf=true
    )
)

/*  Test case:
t = teapot isselected:true 
t.rotation.controller = RotationController_example()
c = t.rotation.controller 
*/

ScaleController

A ScaleController type plug-in controls a scale value as a Point3. In the example below, the controller adds an offset to the controlled value.

plugin ScaleController ScaleController_example
name:"ScaleController Example"
classID:#(0x7fa4db14, 0x4e5f9b9f)
(
    parameters pblock 
    (
        offset_value type:#point3 animatable:true 
        valueController type:#maxobject subAnim:true
    )
    on getValue do 
    (
        res = valueController.value + offset_value
        res
    )
    on setValue val relVal commit do 
    (
        val -= offset_value
        SetControllerValue valueController val commit #absolute
    )

    on create do
    (
        valueController = NewDefaultScaleController()
        isLeaf=true
    )
)

/*  Test case:
t = teapot isselected:true
t.scale.controller = ScaleController_example()
c = t.scale.controller 
c.value = [.9,.9,.9]

*/

TransformController

The TransformController plug-in controls a transform value (a Matrix3). In this example, it adds an offset to the controlled value.

plugin TransformController TransformController_example
name:"TransformController Example"
classID:#(0x47db1cad, 0x4e9b5f9c)
(
    parameters pblock 
    (
        offset_value type:#matrix3
        valueController type:#maxobject subAnim:true
    )
    on getValue do 
    (
        res = valueController.value * offset_value
        res
    )
    on setValue val relVal commit do 
    (
        val = val * inverse offset_value
        SetControllerValue valueController val commit #absolute
    )
    on create do
    (
        valueController = NewDefaultMatrix3Controller()
        isLeaf=true
    )
)

/*  Test case:
t = teapot isselected:true
t.transform.controller = TransformController_example()
c = t.transform.controller 

at time 100 c.value
c.value = matrix3 1


*/