Running a Maya script node on import

Well, I have my selector working very nicely, with the selector code running from a script node whenever you open the file. Works great - if you OPEN the file. So if I IMPORT or REFERENCE the file instead, it does NOT work. Nothing gets executed.

I kind of ran across this while working around the namespace thing, because the code grabs information from an attribute on the script node itself. Namespaces get added automatically when you import. There also doesn’t seem to be any easy way to get a reference to the name of the script node you’re running from, so I just wrote a dialog that grabs a list of the namespaces and lets you choose.

Except the dialog only shows up if you open the file, instead of importing it. Sort of useless, since opening a file doesn’t add a namespace.

Is this normal behavior? If not, how do I fix it? If so, uh, how do I fix it? :slight_smile:

As always, if there’s a better approach altogether, please do tell.

Edit: I guess I should mention that I’m on Maya 2018 and using Python.

Thanks!

The only way I know to run code during an import or reference is either a scriptJob with the PostSceneRead event type.
Or using MSceneMessage system from the OpenMaya api.

I don’t have any experience using the scriptJob myself, but I’ve used the MSceneMessage system to handle all kinds of code in the past.

You can also force a script node to execute explicitly with cmds.scriptNode(nodename, executeBefore=True) or cmds.scriptNode(nodename, executeAfter=True) depending on which one you want.

Something like this will fire all the scriptNodes in a given namespace:

def fire_scriptnodes_in_namespace(ns = ''):
        scripts = cmds.ls(ns + ":*", type='script', r=True)
        for script in scripts:
            if cmds.getAttr(script + '.before'):
                cmds.scriptNode(script, eb=True)
        
            if cmds.getAttr(script + '.after'):
                cmds.scriptNode(script, ea=True)

You know, I had never thought of putting scriptNodes in a namespace before.

Your scriptnode scripts can find themselves and their own attributes when run, regardless of namespaces, if you do something like this inside the script nodes

# ID 345
scripts = cmds.ls( type="script", r=1) or []
for node in scripts:
    text = cmds.getAttr(node + ".before") or cmds.getAttr(node + ".after") or ""
     if '# ID 345' in text:
        do_something_with_my_attributes(node)
1 Like

I appreciate all of the recommendations. I think what I’m going to try is the MSceneMessage.addCallback() to get the node to run on import, and then Theodox’s trick with the ID comment to see if I can grab the namespace (although I have a feeling it might break if you have multiple references to the same file). I had another idea, which is really sort of a kludge, and that’s just have the script node show a dialog where the user can hand-pick the namespace.

I’ll report back how it goes and what I end up doing to make it work. For anyone else reading this, here’s the docs on MSceneMessage. Edit: Fixed the link.

Ok, I got it to work. Here’s what I did.

First off, to deal with the namespace issue, I just pop up the dialog asking which namespace to use. Not the prettiest way, but it does work, and it will work no matter how many times you import the same file (which is something we will most likely do). I’m sure there are other ways to do that, but this one is straightforward and our artist knows how to respond to it.

Edit: Here’s the code that I used to ask for the namespace. Not really very interesting, but it might help someone.

def getNamespace(self):
		print("Getting the namespace.")
		cmds.namespace(set=":")
		namespaces = cmds.namespaceInfo(lon=True)
		namespaces.append("(none)")
		ns = cmds.confirmDialog(
			title='Namespace selection', 
			message='The selector needs to know then namespace of the character: Choose the namespace:',
			button=namespaces)
		if ns == "(none)":
			ns = ""
		return ns

Second, to deal with running the script node on import or reference, I used MSceneMessage.addCallback(). Here’s the code I used:

	# register for restoring from a reference.
	# store the reference to it so we can unregister it later if necessary. never throw away your receipts! er, references.
	if Selector.referenceStartup == 0:
		referenceStartup = OpenMaya.MSceneMessage.addCallback(37, "selectorMain")
		print("Registering for loading references.")
			
	# register for restoring from an import.
	# store the reference to it so we can unregister it later if necessary. 
	if Selector.importStartup == 0:
		importStartup = OpenMaya.MSceneMessage.addCallback(4, "selectorMain")
		print("Registering for importing.")

The integers are the message IDs. I found that the constants given in the documentation don’t work. Use the numbers instead.

Then, in the selectorMain() that I referenced:

def selectorMain():
    try:
        ns = Selector.getNamespace()
        selID = cmds.getAttr(ns + ':SelectorNode.selectorID')
        mulID = cmds.getAttr(ns + ':SelectorNode.multiID')
        Selector.register(selID, mulID, ns)
    except RuntimeError, err:
        print("The selector identifiers were not set. You will need to set them manually. Error: " + err)

And of the Selector.register() code is nothing special.

So I hope that helps someone else that’s having the same issue. You won’t hurt my feelings if you have any corrections to make, so please do point out any problems with this technique.

Is this running a script on particular character?

You can simplify the whole thing if thats the case – attach your scriptnode to the a custom attrib on the root of the character using a message connection, and have the scriptnode use the ID trick to trace upwards to the character it cares about. Then have your UI look at all the character roots in the scene and invoke the one the user cares about.

Or, maybe better, you could hotkey it to use the current selection, trace up to the root, find the associated script node, and run it that way – no dialog needed.

The script is supposed to work on any character and any selector. As a matter of fact, I’m thinking seriously of releasing the whole script for others to use. It still needs to be polished up a bit, though.

The ID trick is very cool, except (and correct me if I’m wrong) it breaks if you have the same file imported or referenced multiple times. (The animations are for a game, and some of them are interpersonal interactions like kill moves, so you need two characters in the scene.) Wouldn’t you need to go to the script node for each instance and change it to a unique ID? I think my animator would be at my desk with torches and pitchforks. : )

I like the current selection idea, and I actually use exactly that for another script, but this one needs to run automatically at load time. But otherwise a great idea.

The ID trick will break if you have two copies of the same scriptnode in the file, yes – it’s easy to make the id’s unique when creating them but there’s no way to stop people importing the same file over and over. You can however create a global dictionary and check to see if the same id has already fired in a given session and skip it.

If you’re explicitly firing the nodes the other trick is to collect all the nodes before any file import (using the preopen message) then collect the new ones by seeing what has been added to the list after the import and then loop over those.

Poor maya, we torture it so.

Poor maya, we torture it so.

This is true. So, so true. : )

The ID trick sounds like it will come in handy for a much bigger project I’m going to be doing that will need a global library to keep track of everything anyway. I’ll keep that in mind.