Change Handlers and When Constructs

Change Handlers are used to detect when certain types of user events are performed on objects in a scene, allowing you to write scripts that respond to user operations like object moves, vertex edits, object deletions, name changes, and so on. Change Handlers are applied to individual MAXWrapper objects and specify an attribute to monitor for that object and an expression to evaluate when that attribute changes. Example attributes that can be monitored are geometry, name, transform, and parameters. Change Handlers are not stored in a 3ds Max scene.

The ChangeHandler : Value class instances represent change handler setups. ChangeHandler values are created using the when construct. Each time a when construct is executed, it creates and returns a new ChangeHandler instance. You must store this ChangeHandler value in a variable or array so that you can dismiss the handler when it is appropriate to do so.

The when construct defines a change handler function for a certain type of event on one or more objects. The system then automatically calls this function whenever the event occurs.

Note: Change events are only generated when the user changes the object. Events are not generated if the change is triggered by an animation.

The syntax of the when construct has two forms as follows:

when <attribute> <objects> change[s] [ id:<name> ] \ 
[handleAt: #redrawViews|#timeChange] \ 
[ <object_parameter> ] do <expr>
when <objects> deleted [ id:<name> ] \ 
[handleAt:#redrawViews|#timeChange] \
[ <object_parameter> ] do <expr> 

In the first form, the <attribute> can be one of:

 topology geometry name[s] transform select parameters subAnimStructure controller children

These specify the attribute of the given object(s) to be tracked for change (see below for more details).

The second form tracks object deletion by the user or other scripts. This is typically used if you have tables or other structures containing MAXScript object values and you want to remove deleted objects from them as the user modifies the scene.

NOTES:

The #selectedNodesPreDelete general callback is the far superior method to use to catch node deletion by the user. It is called before anything happens to the nodes and will report the deletion of any node. In contrast, the when construct operates only on nodes explicitly registered with the callback.

mypot1 = teapot() --create a teapot
fn whendeleted = --define a callback function
(
local deletedobjects = callbacks.notificationParam()
format "Callback: %\n" deletedobjects
-- This will printevery object that was deleted.
-- By this point, the object isstillattached to it's parents
-- and is still in groups etc...
-- Nothing has been done to the nodeyet.
)
when mypot1 deleted id:#foo obj do --define a when construct
(
format "When Construct:%\n" obj
)
--unregisterany preDeletecallbacks
callbacks.removeScripts #selectedNodesPreDelete
--register the function as general callback
callbacks.addscript #selectedNodesPreDelete "whendeleted()"

The when construct is called before the actual deletion of the object.

In both forms, the <objects> argument can be one of the following:

If more than one object is specified, the handler is called each time the given attribute of any of the objects is signaled as changed by the 3ds Max core.

The optional id: parameter specifies an ID for one or more handlers as a name literal. The ID name can later be used to delete the handler and if the same name is given to several handlers, they can be deleted as a group.

The optional handleAt: parameter signals that the change handler expression must not be executed immediately upon notification, but delayed until either the 3ds Max viewports are redrawn ( #redrawViews ) or current 3ds Max animation time is changed ( #timeChange ). Delaying the processing of the change handler expression is highly recommended, as described in the Caution section below. An example usage of the handleAt: parameter is:when select $ changes id:#foo handleAt:#redrawViews do ...

The optional <object_parameter> lets you specify the name of a parameter variable that will be accessible in the do <expr> and will hold the actual object that has changed. You typically specify this parameter name if the handler will respond to changes in many objects, so you can determine which one has changed. The <expr> can be a single expression or block expression.

EXAMPLE

b1 = box()
b2 = box()
b3 = box()
when transform $box001 changes do
$box002.pos = $box001.pos + delta
max select all
when names selection change obj do update_name_table obj
when #($box001, $box002, $box003) deleted obj do
(
messageBox "Warning!"
deleteItem obj_table (findItem obj_table obj)
)

The change attributes are interpreted as follows:

topology

Signaled when the topology of an object changes in the Modify panel such as, using a mesh smooth, optimize, or vertex delete.

geometry

Signaled when the geometry of an object changes such as, by moving a vertex or using an animated modifier.

name or names

Signaled when the name of an object is changed if this occurs because a user edits the name in one of the 3ds Max command panels. The handler is called repeatedly with each character that is changed.

transform

Signaled when the transform of an object is changed such as, by a move, rotate, or scale.

select

Signaled when a scene node moves into or out of the current selection set. You should interrogate the <node>.isSelected property to determine the new state.

parameters

Signaled when any parameters are changed in the object. This is something of a catch all because the core signals this event in many situations.

subAnimStructure

Signaled when the dynamic subAnim structure changes such as, when a new vertex becomes animated in an editable mesh, or when a new controller is added to a list controller. Also called when subAnims are reassigned, for example, when a material is changed in an object.

controller

Signaled when a new controller is assigned to one of the object's tracks.

children

Signaled when an object has immediate children added or removed.

Disabling and Enabling Change Handlers

NEW in 3ds Max 2024: You can specify whether a change handler is enabled with its .enabled property.

For example:

b = box()
ch = when parameters b change do format "b.width=%" b.width
b.width += 10 -- called
ch.enabled = false
b.width += 10 -- not called

Deleting Change Handlers

You can use the following two methods for deleting change handlers:

deleteChangeHandler <change_handler>

deletes specified change handler . change_handler is the value returned by a when construct.

deleteAllChangeHandlers [ id:<name> ]

If optional id: parameter is not specified, deletes all change handlers. If optional id: parameter is specified, deletes all change handlers with the specified id.

FOR EXAMPLE:

deleteAllChangeHandlers id:#foo

For efficiency reasons, you do not want irrelevant change handlers running in the background, so it is essential that you delete those that you no longer need.

Special Considerations

FOR EXAMPLE, IF YOU SAY:

when select $ changes obj do update_modifier_list obj

The function update_modifier_list is called whenever any object is selected or deselected. This is true even if you do an Edit / Select All. In this case, update_modifier_list will be called once for each object currently selected (as the objects are deselected), and then once for all objects in the scene (as the objects are selected). If update_modifier_list is at all processor-intensive, a significant system slowdown can occur.

CAUTION !

The change handler system is based on 3ds Max's internal notification system which essentially drives all animation and interactivity within 3ds Max.

There are cases when the core sends many signals for the same change, so setting up change handlers on many objects that do vast amounts of computing can significantly slow down the system.

Strictly speaking, change handlers are supposed to be used to just set "dirty flags" in a lightweight, order-independent way, and then use Redraw Views or Time Change callbacks to actually cause the triggered processing.

You must attempt to use change handlers judiciously, and not, for example, as a simple way to get scripted controllers.

If you do find that a desired setup is being flooded with unnecessary signals, you must use the handleAt: to delay the actual processing of the event handler script until a redrawViews or timeChange event occurs.