Announcements

Between mid-October and November, the content on AREA will be relocated to the Autodesk Community M&E Hub and the Autodesk Community Gallery. Learn more HERE.

Node added callback

Node added callback

Anonymous
Not applicable
3,208 Views
12 Replies
Message 1 of 13

Node added callback

Anonymous
Not applicable

Hi,

 

is there a way to register a callback whenever a node is added to the DG and after it is connected? There exists the MDGMessage::addNodeAddedCallback, but when it is called it is still not connected to the DG.

So there is no way to get the root shape of the added node in the callback (e.g. node "polyExtrudeFace" is added and the root shape might be the node "pCubeShape", which I want to determine in the callback function).

 

Thanks in advance.

3,209 Views
12 Replies
Replies (12)
Message 2 of 13

Anonymous
Not applicable

None an idea ? What I want is a callback, which enables me for example to get all nodes which are created when a lattice modifier is used.

0 Likes
Message 3 of 13

Anonymous
Not applicable

MPxNode class has four methods legalConnecton, legalDisconnection, connectionMade, connectionBroken. They get called at appropriate times. Have you considered them?

0 Likes
Message 4 of 13

Anonymous
Not applicable

Hi ajith.mk91,

thank you for your advice. Unfortunately I donnot see how this would be helpful, as

 What I need is a callback for standard maya nodes. I need a callback when a node is added and already inserted into the dependency graph. The MDGMessage::addNodeAddedCallback function is called when the node is not inserted into the dependency graph yet, so you have no chance to know into which object or shape the modifier is inserted. Is there a way to know into which object a node is inserted? One way would be to register the callback, but it is called too early.

Example: I create a object cube. Then adding an extrude node. The node is inserted into the cube Dependency Graph. Is there a way to know in which object (in the example the cube) the node (in the example the extrude) is inserted into?

0 Likes
Message 5 of 13

Anonymous
Not applicable

OK. I don't have access to a system as I type this. So pls bear with me 😉

 

You want a callback after the connection is made. In Maya node creation and connection between two nodes are different events. So what you need is MDGMessage::addConnectionCallback. MMessag::MPlugFunction is the type of function you register with addConnectionCallback. Now this function has srcPlug and destPlug as parameters among others. Once you get the hold of plugs, you can get the node to which the plug belongs. Check MPlug docs on how to do this.

 

So you get source node from srcPlug and destination node from destPlug. All this is after the connection was made. As I said, I am not sure if this does what you want. I will check my solution when I get back to office in a few hours. 

0 Likes
Message 6 of 13

Anonymous
Not applicable

I checked this. Use node() method of MPlug class. It returns MObject. Initialize appropriate MFn function set on this MObject. 

 

I am worried about the last part. addNodeAddedCallback has a nodetype argument to filter out unwanted nodes. addConnectionCallback does not have that. So you potentially have to check what type of node is that against most of the common MFnTypes to do something useful with it.

Message 7 of 13

cheng_xi_li
Autodesk Support
Autodesk Support

Hi,

 

As ajith suggested, using addConnectionCallback is correct way to do that.

 

Another common trick to do is use QTimer::singleShot. Here is a sample for Python I've written before, C++ should be the same.

 

import maya.cmds as cmds
import maya.api.OpenMaya as om2
import PySide2.QtCore as qc

selectionCallback = None

def CreateNodeCallbackDelayed(objHandle, customData):
global selectionCallback
#remove my callback as I am debugging
om2.MMessage.removeCallback(selectionCallback)
print "list in a singleshot"
listConnectionsCallback(objHandle)

def listConnectionsCallback(objHandle):
if objHandle.isValid():
object = objHandle.object()
nodename = om2.MFnDependencyNode(object).name()
print 'CreateNodeCallback : name=', nodename
print cmds.listConnections(nodename)

def CreateNodeCallback(*args):
if len(args) == 2:
object = args[0]
objHandle = om2.MObjectHandle(object)
print "list directly"
listConnectionsCallback(objHandle)
qc.QTimer.singleShot(0, lambda: CreateNodeCallbackDelayed(objHandle, args[1]))

def removeCallback(id):
try:
om2.MMessage.removeCallback(id)
except:
sys.stderr.write('Failed to remove callback\n')

selectionCallback = om2.MDGMessage.addNodeAddedCallback( CreateNodeCallback, 'polyExtrudeFace', 'polyExtrudeFacet')

"""
Result:
polySphere;
// Result: pSphere1 polySphere1 //
polyExtrudeFacet;
list directly
CreateNodeCallback : name= polyExtrudeFace1
None
// Result: polyExtrudeFace1 //
list in a singleshot
CreateNodeCallback : name= polyExtrudeFace1
[u'pSphere1', u'polySphere1', u'pSphere1']
"""

 

It will only work in GUI mode.  QTimer::singleShot will put your callback into Maya idle queue which is shared with Qt GUI's idle queue. When the polyExtrudeFacet command is running, the idle queue will put into on hold states. After the command is finished, Maya will execute the command queue again and you'll have all default data most of the time.

 

Yours,

Li

 

 

0 Likes
Message 8 of 13

Anonymous
Not applicable

Yes, that is a possibility. Thank you. It is a little bit cumbersome, but it can do the job.

0 Likes
Message 9 of 13

Anonymous
Not applicable

Thank you. I have opted for a similar approach but with the class MTimerMessage. The problem with this is, that it is not ensured, that you will get all nodes. For example, I want to record all nodes which are created when a lattice modifier is used.

 

You can of course wait e.g. 0.3 seconds and collect all nodes which are created in this timeframe. But then, if there is some issue with performance maya could take longer than 0.3 seconds and you would not record all nodes. I think there is no nice solution to this, because you do not know when the last node or connection is made, so you can stop the recording. Of course you can program special cases, like the lattice operation has grouppart as the last node, which is created and so on, but this will produce ugly code.

0 Likes
Message 10 of 13

Anonymous
Not applicable

You seem to have moved on but just out of curiosity, is it possible to combine addNodeAddedCallback, addConnectionCallback and classes involving timing that both of you used.

 

For example, addConnectionCallback is added inside addNodeAddedCallback(so you can use filter node mechanism) with the reference of the variable in which callback id is stored in your plugin via user data of addConnectionCallback. You somehow need to communicate from your plugin to addConnectionCallback that you are done with recording nodes(to take care of timing issue).If done remove addConnectionCallback from inside addConnectionCallback. I am not sure if you can manipulate callback registration and de-registration from inside your callbacks. Also not sure how you can communicate from your plugin to callback, but hey how ugly can it get.

 

I don't know what I just typed. May be this is a crazy idea. Or this is what happens when you think after a couple of beers. 😄

Message 11 of 13

Anonymous
Not applicable

You can do that, but this results in complicated code. For example you must be always sure, that the callbacks are called in correct order and so on. Then you must be sure that the callbacks are removed correctly otherwise you can create loops and so on. It is not optimal. I think the best method is to register new nodes with addConnection Callback and compare it to an internal variable which holds the nodes which are already created. I discovered the addNode Callback is practically useless, because the name of the node is not correctly defined in the callback and as another issue you cannot correctly resolve custom attributes. No idea, why this issues happen.

0 Likes
Message 12 of 13

Anonymous
Not applicable

There is really something strange happening in the timer callback of the connection add callback. For example you cannot query the node type with cmds.objectType(). There just happens an error silently. Does someone know if there are any restrictions for function calls in the timer callback?

0 Likes
Message 13 of 13

Anonymous
Not applicable

Quite an old post, but I came across the same problem and that solution pretty much worked!

I had to switch the event type from `MDGMessage.addConnectionCallback` to `MEventMessage.addEventCallback` with the idle event, reason being that you need to know when to manually remove the callback, and with the connection callback it wasn't clear in my case.
E.g. I was trying to attach a callback to whenever a constraint is made and play with the related transforms, but in the addConnectionCallback I wouldn't have a good reasoning to tell Maya when all the related objects were done connecting to the newly made constraint. This is why the idle event worked better, since Maya just waits until all its previous operations were done before executing that new callback.

 

It's a bit messy, but here's a trickled down version of what I wrote which is basically a class that can get new node types and attach callbacks for when they're made, and do whatever needs to be done with these new nodes after they've been properly connected to the DG graph:

 

import pymel.core as pm
import maya.api.OpenMaya as om2


class NewNodeTypeCallbacksManager():
    _callbacks_list = []
    _idle_timed_callback = None
    _new_node = None

    def createNewNodeCallbacks(self, node_type):
        new_node_type_callback = om2.MDGMessage.addNodeAddedCallback(self._newNodeMadeCallback, node_type)
        self._callbacks_list.append(new_node_type_callback)

    def removeNewNodeCallbacks(self):
        if self._callbacks_list:
            for callbacks in self._callbacks_list:
                om2.MMessage.removeCallback(callbacks)
            self._callbacks_list = []

    def _newNodeMadeCallback(self, *args):
        obj_handle = om2.MObjectHandle(args[0])
        if obj_handle.isValid():
            self._new_node = pm.ls(om2.MFnDependencyNode(obj_handle.object()).name())
            self._idle_timed_callback = om2.MEventMessage.addEventCallback('idle', self._idleTimedCallback, clientData=None)

    def _idleTimedCallback(self, *args):
        om2.MEventMessage.removeCallback(self._idle_timed_callback)
        self._idle_timed_callback = None
        self.doSomethingAtIdleEvent(self._new_node[0])

    @staticmethod
    def doSomethingAtIdleEvent(new_node):
        # Do whatever you need to do with the new node,
        # after Maya has processed it into the DG graph
        # and all the node's info is valid.
        pass

 

Hope that helps if anyone ever run into this in the future! 😀