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 leasta 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.
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.
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.
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.
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.
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 throught he 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 <controller>.value in MaxScript), and the parentTransform will be an identity matrix.
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 */