Node Event System

The Node Event System available in 3ds Max 2009 and higher lets you define a scripted function that is triggered in response to any of various node-related scene events. The events map one-to-one with the event methods of class INodeEventCallback in the 3ds Max SDK. For detailed information on each, read the methods comments for class INodeEventCallback in maxsdk\include\ISceneEventManager.h

The Node Event System is similar to the General Event Callback Mechanism. It operates as a layer over the General Event Callback system, but also receives some node messages which the older system cannot handle, like geometry, topology and mapping changes. This system will also catch events which happen as a result of undo/redo, while the older system sometimes would not.

Thus, if you wish to catch nodes-related events and changes to nodes, the Node Event System is your best option.

Note: The Node Event System is inappropriate for catching any events except those specifically related to scene nodes. It is mainly intended to be used with a UI (such as a scripted plug-in). The system is timer based, so it requires the Windows message loop to be running, which means its use in a script without a UI can be problematic.

Contructor:

<callbackItem>NodeEventCallback [mouseUp:<boolean>] [delay:<integer>] \
[enabled:<boolean>] [polling:<boolean>] \
[callbackBegin:<fn>] [all:<fn>] \
[added:<fn>] [deleted:<fn>] \
[linkChanged:<fn>] [layerChanged:<fn>]\
[groupChanged:<fn>] [hierarchyOtherEvent:<fn>] \
[modelStructured:<fn>] [geometryChanged:<fn>] \
[topologyChanged:<fn>] [mappingChanged:<fn>] \
[extentionChannelChanged:<fn>] [modelOtherEvent:<fn>] \
[materialStructured:<fn>] [materialOtherEvent:<fn>] \
[controllerStructured:<fn>] [controllerOtherEvent:<fn>] \
[nameChanged:<fn>] \
[wireColorChanged:<fn>] [renderPropertiesChanged:<fn>] \
[displayPropertiesChanged:<fn>] [userPropertiesChanged:<fn>] \
[propertiesOtherEvent:<fn>] \
[subobjectSelectionChanged:<fn>] [selectionChanged:<fn>] \
[hideChanged:<fn>] [freezeChanged:<fn>] \
[displayOtherEvent:<fn>] [callbackEnd:<fn>]      

The example script below prints a message to the MAXScript Listener when any message is received by registering a function to the optional keyword argument all:

The script also displays a message box when the linkChanged: or layerChanged: event occurs, that is, whenever one node is linked or unlinked from another node, or moved to a different layer.

EXAMPLE SCRIPT:

   --Define a callback function to handle linking and layer changes:
   fn CallbackFn1 ev nd = (
   messageBox ("Event Detected: Event "+(ev as string)+ \
   ", Nodes " + (nd as string))
   )
   --Define a callback function to handle ALL messages:
   fn CallbackFn2 ev nd = (
   format "Event Detected: Event%, Nodes %\n" ev nd )
   --Register callbacks for link changes, layer changes using the
   --first function and for all changes using the second function.
   --Act only when mouse is up and there have been no other events
   --within one second (1000 milliseconds):
   callbackItem = NodeEventCallback mouseUp:true delay:1000 linkChanged:CallbackFn1 layerChanged:CallbackFn1 all:CallbackFn2

Destructor:

When the NodeEventCallback() constructor is called, an instance of the class is created and returned as the result of the call.

To release this instance and thus remove the callback, the value of the variable holding the handler to the instance has to be set to undefined and the garbage collector must be called to reclaim the memory and effectively destroy the callback.

FOR EXAMPLE:

   --Continuing the previous example where the variable
   --callbackItem was used to store the callback instance,
   callbackItem = undefined
   gc light:true

Handling optional keyword parameters:

mouseup: 

If the optional keyword argument mouseUp: is set to true, the callback will only be triggered when the mouse button is released.

delay: 

When the optional keyword argument delay: is supplied (as time in milliseconds), the callback is only triggered if the given time passes with no events, that is, only after the system is idle for the specified time.

polling: 

When the optional keyword argument polling: is set to true, the pending events for the NodeEventCallbackValue can be fired by calling triggerNodeEventCallback(). Available in 3ds Max 2019.3 Update and higher. The polling argument cannot be used if either mouseUp or delay are specified.

Callback Function optional keyword parameters:

One or more of the other optional keyword arguments can be supplied with pre-defined MAXScript Functions to be executed if a callback of the specified type is found in the message received by the Node Event System.

Important:

The callback functions always take two parameters: the first is the event name that triggered the function call, the second is a list of node AnimHandles.

The GetAnimByHandle() method must be used to get the node itself.

If the specified function does not expect two arguments, an error will be thrown. For example, if the function ObjFn is passed but it does not provide any arguments, the error will look like

   >> MAXScript NodeEventCallback Exception: -- Argument count error: ObjFn wanted 0, got 2 <<

From the text of the error one could incorrectly assume that the function was correct and the callback was wrong, but this is not the case.

The callback always passes the two arguments, but if the function is unable to accept them, the error message simply reflects that fact as it has no knowledge what called the function and why it passed two parameters to it.

Callback Batch Handling:

callbackBegin:   
callbackEnd: 

When a callback is triggered, messages for all queued events will be sent in one batch.

The callbackBegin: message indicates the start of a batch, while callbackEnd: indicates the end of a batch.

Registering functions using these two optional keyword arguments lets you get notification of the beginning and the end of a single message batch.

all: 

The supplied function will be called whenever ANY of the node-related messages listed below has been received. Note that this function is called in addition to any event-specific functions that have been registered. The all function is called first.

Node Creation and Removal Callbacks:

added: 

The supplied function will be called whenever a node has been added to the scene.

deleted: 

The supplied function will be called whenever a node has been deleted from the scene.

Node Hierarchy, Layer and Group Callbacks:

linkChanged: 

The supplied function will be called whenever the node is linked to or unlinked from another node.

layerChanged: 

The supplied function will be called whenever the node has moved to another layer.

groupChanged: 

The supplied function will be called whenever the node has been added or removed from a group.

hierarchyOtherEvent: 

The supplied function will be called whenever the node's hierarchy has been changed in some other way.

Node Model, Geometry, Topology and Mapping Callbacks:

modelStructured: 

The supplied function will be called whenever the model's structure has been modified.

geometryChanged: 

The supplied function will be called whenever the geometry of the node has changed, for example a vertex has been moved.

topologyChanged: 

The supplied function will be called whenever the topology of the node's geometry has changed, for example an edge has been added or removed.

mappingChanged: 

The supplied function will be called whenever the mapping of the node's geometry has changed, for example UV coordinates have been assigned or modified.

extentionChannelChanged: 

The supplied function will be called whenever the a non-mapping channel of the node has been changed, for example Vertex Alpha, Soft-Selection and so on.

modelOtherEvent: 

The supplied function will be called whenever the node's model has been changed in some other way.

Node Material Callbacks:

materialStructured: 

The supplied function will be called whenever a material has been added or removed from the node.

materialOtherEvent: 

The supplied function will be called whenever the node's material has been changed.

Node Controller Callbacks:

controllerStructured: 

The supplied function will be called whenever a controller has been assigned or removed to a SubAnim track of the node.

controllerOtherEvent: 

The supplied function will be called whenever a node's controller has been changed in another way.

Node Name and Properties Callbacks:

nameChanged: 

The supplied function will be called whenever the name of the node has been changed.

wireColorChanged: 

The supplied function will be called whenever the wireframe color of the node has been changed.

renderPropertiesChanged: 

The supplied function will be called whenever the Render Properties of the node has been changed, including Renderable, Visible To Camera, Visible To Reflections and so on.

displayPropertiesChanged: 

The supplied function will be called whenever a display property of the node has been changed, for example "Display As Box", "Show Vertex Ticks", "Backface Culling" and so on.

userPropertiesChanged: 

The supplied function will be called whenever the User Properties buffer of the node has been changed.

propertiesOtherEvent: 

The supplied function will be called whenever any other properties of a node have been changed.

Node Selection Callbacks:

subobjectSelectionChanged: 

The supplied function will be called whenever the sub-object selection of a node has changed.

selectionChanged: 

The supplied function will be called whenever the selected state of the node has changed, for example a node has been selected or deselected.

Node Display Callbacks:

hideChanged: 

The supplied function will be called whenever the Hidden flag of a node has changed.

freezeChanged: 

The supplied function will be called whenever the Frozen flag of a node has changed.

displayOtherEvent: 

The supplied function will be called whenever other display flags of a node have changed.

triggerNodeEventCallback <NodeEventCallbackValue>

Triggers the pending events for the specified NodeEventCallbackValue. Available in 3ds Max 2019.3 Update and higher. This function works for NodeEventCallbackValues with polling specified as true.

<array> getNodeEventCallbacks 

Gets an array of all registered NodeEventCallback values in the current scene. Available in 3ds Max 2019.3 Update and higher.

showNodeEventCallbacks [asArray:<boolean>] [to:<stream>] [

Returns information about all registered NodeEventCallback values in the current scene, including the enabled, polling, mouseUp, and delay settings, the eventIds registered and the functions associated with those events, and the script file name that created the NodeEventCallback. Available in 3ds Max 2019.3 Update and higher.

If the optional asArray keyword parameter is specified as true, this information is returned as an array of strings, one for each NodeEventCallback. Otherwise it is a single string.

If the optional to keyword parameter is specified, the output is written to the specified stream.

Example

poll_event_count = 0

fn runExample = (
-- reset the scene
    resetMaxFile #noprompt --reset the scene
    gc() -- run gc again to clean out previous runs

    --Define a callback function to handle ALL messages:
    fn MyCallback ev nd = (format "Event Detected: Event%, Nodes %\n" ev nd )

    fn MyPollCallback ev nd =     (
        format "Event Detected: Event%, Nodes %\n" ev nd
        poll_event_count += 1 )

    -- Create callback item
    callbackItem = NodeEventCallback added:MyCallback 
    pollCallBackItem = NodeEventCallback added:MyPollCallback    

    -- add something, which will trigger the first callback, but not the second:
    t = teapot()

    -- this will still be 0, because polling is on:
    format "poll events: % \n" poll_event_count

    -- trigger polling callback
    triggerNodeEventCallback pollCallBackItem

    -- node event system is timer based, and requires
    -- message pump system.  This will give the system
    -- time to "catch up", since we do not have a UI
    windows.processpostedmessages();sleep .1
    windows.processpostedmessages()

    -- did it trigger?
    format "poll events: % \n" poll_event_count

    showNodeEvents = showNodeEventCallbacks asArray:true
    format "showNodeEventCallbacks : % \n" showNodeEvents

    getNodeEvents = getNodeEventCallbacks()
    format "Number of event callbacks: %\n" getNodeEvents.count
    for i = 1 to getNodeEvents.count do format "Callback % : % \n" i getNodeEvents[i]

    -- delete the callback items, and garbage collect:
    callbackItem = pollCallbackItem = undefined
    gc()

)

runExample()

--> Output:
runExample()
poll events: 0 
Event Detected: Event#added, Nodes #(6772)
Event Detected: Event#added, Nodes #(6772)
poll events: 1 
showNodeEventCallbacks : #(#(NodeEventCallback<0x000001A535A18210>, #added, "MyPollCallback", \
 "D:\My Documents\3ds Max\scripts\NodeEventCallbacksEx.ms"), #(NodeEventCallback<0x000001A535A229F4>,\
 #added, "MyCallback", "D:\My Documents\3ds Max\scripts\NodeEventCallbacksEx.ms")) 
Number of event callbacks: 2
Callback 1 : NodeEventCallback<0x000001A535A18210> 
Callback 2 : NodeEventCallback<0x000001A535A229F4> 
482872L
OK