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:
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
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.
Returns a value, the type depending upon the controller type.
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.
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.
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.
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.
Called when the controller's value is beginning to be modified.
This handler is optional, and primarily supports the hold/restore system.
Called when the controller's value has finished being modified.
This handler is optional, and primarily supports the hold/restore system.
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.
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.
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.
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.
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.
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.
The following local variables are available to scripted animation plug-in controllers.
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.
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.
For use within thesetValue handler. Specifies whether the call occurred with method type #relative or #absolute.
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
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.
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
*/
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
*/
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
*/
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 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
*/
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
*/
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]
*/
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
*/