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.
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.
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
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
If a runtime error occurs in the body of a change handler while it is being executed, an error message is displayed and that handler is permanently disabled.
If all of the objects being tracked by a change handler are deleted, the change handler is also deleted.
The do <expr>
change handler code is executed in a special context and not in the context of the code that created it. This means that the <expr>
code cannot contain references to local variables in outer code nestings surrounding the when
because those variables are on an execution stack that does not exist at the time the when
handler is called. The compiler detects any illegal references to outer locals and generates a compiler message. An important exception to this is utility and rollout panel locals such as, local functions, rollout variables, and nested rollouts. You can refer to them in change handler code inside rollout code as they are associated directly with their rollout or utility object.
Change handlers are called only for user initiated events, and not for changes resulting from a change in controller values. For example, a change handler on the transform attribute of a node would be called when the user moves the node. If the position of the node is animated, and you play back the animation, the transform attribute change handler is not called.
If you assign a change handler to an attribute on multiple objects, the change handler is called once per object if that object’s attribute changes.
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.
Change handlers are only applied to object already present in the scene. Change handlers are not automatically applied to newly created objects.
If you have multiple change handlers defined, the order in which the change handlers are called is somewhat arbitrary.
Due to the way that 3ds Max internally processes notification signals, the $ form of accessing selected objects is not recommended in a select change handler. To access the selected objects, you must use the selection objectset. This is because $ relies on information that has not yet been set in the selection processing whereas selection uses a different method of accessing selections and the information it uses has been set up.
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.