The AL_USDMaya event system attempts to provide a more robust event system for Maya that works around some of the short comings of the MMessage/scriptJob approach. This system is employed within AL_USDMaya to expose programming hooks that can be used to execute your own code during the internal processes of AL_USDMaya (e.g. before/after a variant switch)
More...
The AL_USDMaya event system attempts to provide a more robust event system for Maya that works around some of the short comings of the MMessage/scriptJob approach. This system is employed within AL_USDMaya to expose programming hooks that can be used to execute your own code during the internal processes of AL_USDMaya (e.g. before/after a variant switch)
Events Motivation
Why not use scriptJob / MMessage?
Maya already has its own event management system, which are exposed via the MMessage (and derived classes) in the API, and scriptJob within MEL/python. These systems work, but they have a number of drawbacks when deployed in a medium to large sized studio with multiple shows in flight. As an example of some of the problems that can arise, consider this scenario:
global proc onFileNew_createMagicNode()
{
$node = `createNode "transform"`;
rename $node "essential_shot_settings";
}
global int $scriptJob1 = `scriptJob -e "NewSceneOpened" "onFileNew_createMagicNode"`;
global proc onFileNew_createDefaultSetOfNodes()
{
sets -n "doNotExport" `ls -type transform`;
}
global int $scriptJob2 = `scriptJob -e "NewSceneOpened" "onFileNew_createDefaultSetOfNodes"`;
Now in this case, since we are registering those script jobs in a specific order, when a 'file new' occurs, our custom shot node will be created, and it will be added to the set of nodes to ignore at export time. All well and good!
If however we registered scriptJob2 first, we'd end up with the set being created first, and then we'd create our shot settings node (which would not be part of the set). Now who knows which is the right way around in this context (it is after all an illustrative example!), but the important take home message here, is that there can be behavioural changes when scriptJobs and MMessages are registered in differing orders.
This is often a problem in most studios, since it's likely that those two scriptJobs (or MMessage events) are actually located in different plug-ins, and therefore small bugs can be introduced if the events are accidentally registered in an incorrect order.
In cases where small bugs are introduced, it is often extremely hard to track down what has caused the offending bug, since the maya event system doesn't really give you an adequate way to track down which events triggered which callbacks, and more importantly any ideas in how to track down the code that contained the events.
Some Terminology
- Event : An event is a point in code that can trigger multiple callbacks
- Callback : This is a small bit of code that the user can bind to a specific event, to be executed when it is triggered
- Node Event : an event that is bound to a specific maya node
- Global Event : an event that is not bound to any particular node
Core Event API
Implementing the System Backend
The event system itself is defined in such as a way as to allow you to provide a custom backend when initialising the event system. The primary reason for this is to allow a repurposing of the event system into different DCC contexts (e.g. Maya, Houdini, etc). Within the usdmaya plugin, this backend binding is provided in the class MayaEventSystemBinding defined within AL/usdmaya/Global.cpp. The main purpose of this binding is to connect the event system to the underlying logging and scripting capability of the DCC app.
#include "AL/event/EventHandler.h"
constexpr uint32_t kUserSpecifiedEventType = 0;
constexpr uint32_t kSchemaEventType = 1;
constexpr uint32_t kUSDMayaEventType = 2;
constexpr uint32_t kMayaEventType = 3;
static const char* const eventTypeStrings[] =
{
"custom",
"schema",
"maya",
"usdmaya"
};
class MayaEventSystemBinding
: public AL::maya::EventSystemBinding
{
public:
MayaEventSystemBinding()
: EventSystemBinding(eventTypeStrings, sizeof(eventTypeStrings) / sizeof(const char*)) {}
bool executePython(const char* const code) override
{
return MGlobal::executePythonCommand(code, false, true);
}
bool executeMEL(const char* const code) override
{
return MGlobal::executeCommand(code, false, true);
}
void writeLog(AL::maya::EventSystemBinding::Type severity, const char* const text) override
{
switch(severity)
{
case kInfo: MGlobal::displayInfo(text); break;
case kWarning: MGlobal::displayWarning(text); break;
case kError: MGlobal::displayError(text); break;
}
}
};
static MayaEventSystemBinding g_eventSystem;
void initEventSystem()
{
AL::event::EventScheduler::initScheduler(&g_eventSystem);
}
Global Events in C++
The following code sample provides a simple example of how the C++ api works in practice.
#include "AL/event/EventHandler.h"
AL::event::EventId g_mySimpleEvent = 0;
class SimpleEventExample
: public MPxCommand
{
public:
static void* creator() { return new SimpleEventExample; }
MStatus doIt(const MArgList& args)
{
AL::event::EventScheduler::getScheduler().triggerEvent(g_mySimpleEvent);
return MS::kSuccess;
}
};
AL::maya::event::CallbackId g_myCallbackId = 0;
void myCallbackFunction(void* userData)
{
MGlobal::displayInfo("I am a callback!\n");
}
MStatus initializePlugin(MObject obj)
{
AL::event::EventScheduler::initScheduler(&g_eventSystem);
auto& scheduler = AL::event::EventScheduler::getScheduler();
g_mySimpleEvent = scheduler.registerEvent("OnSomethingHappened", kUserSpecifiedEventType);
if(g_mySimpleEvent == 0)
{
std::cout << "event failed to register (name is in use!)" << std::endl;
}
g_myCallbackId = scheduler.registerCallback(
g_mySimpleEvent,
"myToolName_myCallbackFunction",
myCallbackFunction,
10000,
nullptr);
if(g_myCallbackId == 0)
{
std::cout << "callback failed to register (tag is in use, or event id is invalid)" << std::endl;
}
MFnPlugin fn(obj);
fn.registerCommand("simpleEventExample", SimpleEventExample::creator);
return MS::kSuccess;
}
MStatus uninitializePlugin(MObject obj)
{
AL::event::EventScheduler::getScheduler().unregisterCallback(g_myCallbackId);
AL::event::EventScheduler::getScheduler().unregisterEvent(g_mySimpleEvent);
MFnPlugin fn(obj);
fn.unregisterCommand("simpleEventExample");
maya::EventScheduler::freeScheduler();
return MS::kSuccess;
}
It should be noted that once this plugin have been loaded, there are a number of MEL commands exposed that allow you to interact with that event in MEL/python script. Firstly we can get a list of the global events registered:
print `AL_usdmaya_ListEvents`;
We can also trigger the event from MEL/python if we wish:
AL_usdmaya_TriggerEvent "OnSomethingHappened";
Via the MEL command AL_usdmaya_Callback it is possible to assign a callback from a MEL or python script.
string $melCodeToExecute = "print \"mel callback!\n\"";
global int $callbackId[] = `AL_usdmaya_Callback -me "OnSomethingHappened" "MyMelScript_operation" 10001 $melCodeToExecute`;
You will notice that the callback id's are returned as an array. This is simply because the C++ Callback ID's are 64bit, however sadly MEL does not support 64bit integer values, so the callbacks are returned as a pair of 32bit integers. These pair of callback values can be used to query some information about the callback using the command AL_usdmaya_CallbackQuery
print ("tag: " + `AL_usdmaya_CallbackQuery -tag $callbackId[0] $callbackId[1]` + "\n");
print ("eventId: " + `AL_usdmaya_CallbackQuery -e $callbackId[0] $callbackId[1]` + "\n");
print ("type: " + `AL_usdmaya_CallbackQuery -ty $callbackId[0] $callbackId[1]` + "\n");
print ("weight: " + `AL_usdmaya_CallbackQuery -w $callbackId[0] $callbackId[1]` + "\n");
print ("code: " + `AL_usdmaya_CallbackQuery -c $callbackId[0] $callbackId[1]` + "\n");
If you wish to see which callbacks are registered against a specific event, you can use the AL_usdmaya_ListCallbacks command, e.g.
proc printCallbackInfo(string $eventName)
{
int $callbackIds[] = `AL_usdmaya_ListCallbacks $eventName`;
print ("EventBreakdown: " + $eventName + "\n");
for(int $i = 0; $i < size($callbackIds); $i += 2)
{
print ("callback " + ($i / 2 + 1) + " : [" + $callbackIds[$i] + ", " + $callbackIds[$i + 1] + "]\n");
print (" tag: " + `AL_usdmaya_CallbackQuery -tag $callbackIds[$i] $callbackIds[$i + 1]` + "\n");
print (" eventId: " + `AL_usdmaya_CallbackQuery -e $callbackIds[$i] $callbackIds[$i + 1]` + "\n");
print (" type: " + `AL_usdmaya_CallbackQuery -ty $callbackIds[$i] $callbackIds[$i + 1]` + "\n");
print (" weight: " + `AL_usdmaya_CallbackQuery -w $callbackIds[$i] $callbackIds[$i + 1]` + "\n");
print (" code: \n----------------------------------------------------------------\n" +
`AL_usdmaya_CallbackQuery -c $callbackIds[$i] $callbackIds[$i + 1]` +
"\n----------------------------------------------------------------\n");
}
}
printCallbackInfo("OnSomethingHappened");
If you wish to delete a callback, then you can do it in one of two ways:
AL_usdmaya_Callback -d $callbackId[0] $callbackId[1];
AL_usdmaya_DeleteCallbacks $callbackId[0];
It is also possible to define entirely new events in your own MEL or python scripts, e.g.
AL_usdmaya_Event "AnEventDefinedInMEL";
AL_usdmaya_TriggerEvent "AnEventDefinedInMEL";
AL_usdmaya_Event -d "AnEventDefinedInMEL";
Node Events in C++
To make use of the maya node events, your node should derive from the AL::usdmaya::nodes::MayaNodeEvents class. A simple example of setting a node up with the events system would look like so:
#include "AL/event/EventHandler.h"
class MyMayaNode
: public MPxNode,
public AL::maya::NodeEvents
{
public:
MyMayaNode()
{
registerEvent("PreThingHappened", kUserSpecifiedEventType);
registerEvent("PostThingHappened", kUserSpecifiedEventType);
}
~MyMayaNode()
{
unregisterEvent("PreThingHappened");
unregisterEvent("PostThingHappened");
}
void thingHappened()
{
triggerEvent("PreThingHappened");
doTheThing();
triggerEvent("PostThingHappened");
}
};
That's basically the only setup you need to perform in order to make a custom plugin node compatible with the events system. We can now use AL_usdmaya_ListEvents to get a list of the events that the node supports
$node = `createNode "MyMayaNode"`;
print `AL_usdmaya_ListEvents $node`;
Via the MEL command AL_usdmaya_Callback it is possible to assign a callback from a MEL or python script to that node.
string $melCodeToExecute = "print \"mel callback!\n\"";
global int $callbackId[] = `AL_usdmaya_Callback -mne $node "PreThingHappened" "MyMelScript_operation" 10001 $melCodeToExecute`;
We can also trigger the event from MEL/python if we wish:
AL_usdmaya_TriggerEvent "PreThingHappened" $node;
As with the Global Events, these pair of callback values can be used to query some information about the callback using the command AL_usdmaya_CallbackQuery
print ("tag: " + `AL_usdmaya_CallbackQuery -tag $callbackId[0] $callbackId[1]` + "\n");
print ("eventId: " + `AL_usdmaya_CallbackQuery -e $callbackId[0] $callbackId[1]` + "\n");
print ("type: " + `AL_usdmaya_CallbackQuery -ty $callbackId[0] $callbackId[1]` + "\n");
print ("weight: " + `AL_usdmaya_CallbackQuery -w $callbackId[0] $callbackId[1]` + "\n");
print ("code: " + `AL_usdmaya_CallbackQuery -c $callbackId[0] $callbackId[1]` + "\n");
If you wish to see which callbacks are registered against a specific event, you can use the AL_usdmaya_ListCallbacks command, e.g.
proc printNodeCallbackInfo(string $eventName, string $node)
{
int $callbackIds[] = `AL_usdmaya_ListCallbacks $eventName $node`;
print ("EventBreakdown for node: " + $node + " and event: " + $eventName + "\n");
for(int $i = 0; $i < size($callbackIds); $i += 2)
{
print ("callback " + ($i / 2 + 1) + " : [" + $callbackIds[$i] + ", " + $callbackIds[$i + 1] + "]\n");
print (" tag: " + `AL_usdmaya_CallbackQuery -tag $callbackIds[$i] $callbackIds[$i + 1]` + "\n");
print (" eventId: " + `AL_usdmaya_CallbackQuery -e $callbackIds[$i] $callbackIds[$i + 1]` + "\n");
print (" type: " + `AL_usdmaya_CallbackQuery -ty $callbackIds[$i] $callbackIds[$i + 1]` + "\n");
print (" weight: " + `AL_usdmaya_CallbackQuery -w $callbackIds[$i] $callbackIds[$i + 1]` + "\n");
print (" code: \n----------------------------------------------------------------\n" +
`AL_usdmaya_CallbackQuery -c $callbackIds[$i] $callbackIds[$i + 1]` +
"\n----------------------------------------------------------------\n");
}
}
printNodeCallbackInfo("PreThingHappened", $node);
If you wish to delete a callback, then you can do it in one of two ways:
AL_usdmaya_Callback -d $callbackId[0] $callbackId[1];
AL_usdmaya_DeleteCallbacks $callbackId[0];
It is also possible to define entirely new events on the node in your own MEL or python scripts, e.g.
AL_usdmaya_Event "AnEventDefinedInMEL" $node;
AL_usdmaya_TriggerEvent "AnEventDefinedInMEL" $node;
AL_usdmaya_Event -d "AnEventDefinedInMEL" $node;
Parent / Child events in C++
As an optional part of the events system, it is possible to provide additional information to an event that describes a parent/child relationship between callbacks and events. Child events themselves are not triggered automatically (that task is something that needs to be done manually), and child events can only be parented to a callback.
#include "AL/event/EventHandler.h"
AL::event::EventId g_mySimpleEvent1 = 0;
AL::event::EventId g_mySimpleEvent2 = 0;
class ParentEventExample
: public MPxCommand
{
public:
static void* creator() { return new ParentEventExample; }
MStatus doIt(const MArgList& args)
{
AL::event::EventScheduler::getScheduler().triggerEvent(g_mySimpleEvent1);
return MS::kSuccess;
}
};
AL::maya::event::CallbackId g_myCallbackId1 = 0;
AL::maya::event::CallbackId g_myCallbackId2 = 0;
void myCallbackFunction1(void* userData)
{
MGlobal::displayInfo("I am a callback that will trigger an event!\n");
AL::event::EventScheduler::getScheduler().triggerEvent(g_mySimpleEvent2);
}
void myCallbackFunction2(void* userData)
{
MGlobal::displayInfo("I am the child callback!\n");
}
MStatus initializePlugin(MObject obj)
{
AL::event::EventScheduler::initScheduler(&g_eventSystem);
auto& scheduler = AL::event::EventScheduler::getScheduler();
g_mySimpleEvent1 = scheduler.registerEvent("OnParentThingHappened", kUserSpecifiedEventType);
if(g_mySimpleEvent1 == 0)
{
std::cout << "event failed to register (name is in use!)" << std::endl;
}
g_myCallbackId1 = scheduler.registerCallback(
g_mySimpleEvent1,
"myToolName_myCallbackFunction1",
myCallbackFunction1,
10000,
nullptr);
if(g_myCallbackId1 == 0)
{
std::cout << "callback failed to register (tag is in use, or event id is invalid)" << std::endl;
}
g_mySimpleEvent2 = scheduler.registerEvent("OnChildThingHappened", kUserSpecifiedEventType, g_myCallbackId1);
if(g_mySimpleEvent2 == 0)
{
std::cout << "event failed to register (name is in use!)" << std::endl;
}
g_myCallbackId2 = scheduler.registerCallback(
g_mySimpleEvent2,
"myToolName_myCallbackFunction2",
myCallbackFunction2,
10000,
nullptr);
if(g_myCallbackId2 == 0)
{
std::cout << "callback failed to register (tag is in use, or event id is invalid)" << std::endl;
}
MFnPlugin fn(obj);
fn.registerCommand("parentEventExample", ParentEventExample::creator);
return MS::kSuccess;
}
MStatus uninitializePlugin(MObject obj)
{
AL::event::EventScheduler::getScheduler().unregisterCallback(g_myCallbackId2);
AL::event::EventScheduler::getScheduler().unregisterCallback(g_myCallbackId1);
AL::event::EventScheduler::getScheduler().unregisterEvent(g_mySimpleEvent2);
AL::event::EventScheduler::getScheduler().unregisterEvent(g_mySimpleEvent1);
MFnPlugin fn(obj);
fn.unregisterCommand("parentEventExample");
return MS::kSuccess;
}
Parent / Child events in MEL
To set up a parent/child relationship between callbacks in MEL or python, use the -p flag when creating the child event. A simple example follows:
string $mainEventName = "ParentEvent";
string $childEventName = "ChildEvent";
AL_usdmaya_Event $mainEventName;
string $parentCommand = "AL_usdmaya_TriggerEvent " + $childEventName + ";";
int $parentCB[] = `AL_usdmaya_Callback -me $mainEventName "parentEvent" 10000 $parentCommand`;
AL_usdmaya_Event -p $parentCB[0] $parentCB[1] $childEventName;
string $childCommand = "sphere;";
int $childCB[] = `AL_usdmaya_Callback -me $childEventName "childEvent" 10000 $childCommand`;
AL_usdmaya_TriggerEvent $mainEventName;
If you wish to determine the parent callback of a given event, you can query that information via the AL_usdmaya_EventQuery command with the -p / -parent flag:
int $parentCallback[] = `AL_usdmaya_EventQuery -p $childEventName`;
if(size($parentCallback))
{
print ("parent callback of " + $childEventName + " has ID [" + $parentCallback[0] + ", " + $parentCallback[1] + "]\n");
}
else
{
print ($childEventName + " does not have a parent callback\");
}
similarly, given a callback you can determine the child event ID's via the AL_usdmaya_CallbackQuery command and the -ce / -childEvents flag:
int $childEventIds[] = `AL_usdmaya_CallbackQuery -ce $mainEventName $parentCallback[0] $parentCallback[1]`;
for($event in $childEventIds)
{
print ("eventID " + $event + " is a child of callback " + $parentCallback[0] + " " + $parentCallback[1] + "\n");
}
If you wish to convert the event ID back into the text name (and possibly get the associated node) you the AL_usdmaya_EventLookup command
int $childEventIds[] = `AL_usdmaya_CallbackQuery -ce $mainEventName $parentCallback[0] $parentCallback[1]`;
for($event in $childEventIds)
{
print ("eventID " + $event + " is a child of callback " + $parentCallback[0] + " " + $parentCallback[1] + "\n");
print (" - eventName " + `AL_usdmaya_EventLookup -name $event` + "\n");
print (" - owningNode " + `AL_usdmaya_EventLookup -node $event` + "\n");
}