[Python] Running A ScriptJob In A Script Node

Hello,

I know that scriptJobs are not saved with the Maya scene but they can be run when the scene is open using a script node but I’m not sure how to go about setting it up using Python, I found this post but it only deals with creating a script node to execute a single command whereas I want an entire script to be evaluated.

I’ve created a very basic test script which changes the colour of a box to either green or blue when an attribute is changed:


import maya.cmds as cmds

def matColorChange():
    if cmds.getAttr("myBox.matColor") == 0:
        cmds.setAttr("myMaterial.color", 0, 1, 0)
    else:
        cmds.setAttr("myMaterial.color", 0, 0, 1)
        
cmds.scriptJob(ac=["myBox.matColor", matColorChange])

And I’ve attached the scene file.

Thanks for any help!
-Harry

Do you want to create a Maya virus? Because that’s how you get a Maya virus.

Seriously though, you can include multiple python commands on a single line by separating them with semicolons (;). looping can be done using list comprehensions if you need to get dirty: python - Single Line Nested For Loops - Stack Overflow if statements can be single line too: python - Putting a simple if-then-else statement on one line - Stack Overflow

you really just want to do and import and run of some external function though “import blahblah as bb;bb.do_blah()”

What I really want to do is FK-IK snapping and parent switching, I just created this as a small test to try out ideas. I’ve written the Python scripts to do both of those things which are executed whenever a relevant attribute is changed, the problem is I don’t want to have to load the scripts at the start of every Maya session. I thought using a script node would be a good way of running these scripts automatically whenever I open Maya and if I share the file I’d prefer not to have to distribute the Python scripts, not because I wouldn’t want people to see them, just because it’s more hassle.

Thanks,
-Harry

Yeah, you can use scriptNodes and scriptJobs to do what you want. I might consider this if I were working with someone, for instance an ad agency that would not have my Maya environment and I wanted to be able to just “email” them Maya files. It seems like this is a poor choice for an internal project where you control all the moving pieces. For example, if you want to fix a bug in your IK/FK code, you can do this easily if it is stored in a regular script file. If you need to fix a bug in code that has been deployed into the Maya files themselves, then you will need to batch process all your Maya files each and every time you want to make such a change. I might consider a hybrid approach that consists of code that is designed to be embedded as needed to deal with external agencies.

I think what you want to do is to create two scriptnodes (at least). One of them will get executed on load, and it will set up scriptJobs that call code in a second scriptNode. This second scriptNode will have the scriptType “Execute on demand” and will be explicitly called using the testing syntax: cmds.scriptNode( nodeName, executeBefore=True ). Since it is going to be difficult to pass parameters to a scriptNode, I think you might want to use global variables to contain any required info.

I suspect this approach will also execute slower than a regular Python file since it can’t be converted to a .pyc and will have to be evaluated each time it is run.

EDIT: Also, using executed code (including expressions) to implement rigging features is a big no-no if this code needs to be executed every frame during playback. We know this from experience. You will end up creating rigs that perform poorly, and animators will love you just a little bit less. I would try to implement such features as this using shader nodes and such if possible. You can do many of the same things you would do in code (such as conditional if statements), but they will execute much, much faster.

Thanks for the reply.

I didn’t really understand what you meant in the second paragraph, you mention one script node to set up multiple scriptJobs but I still don’t know what the code would be for a Python script node which has more than one line.

You mention shader nodes, I’m familiar with the commonly used ones but I can’t think how they could be used for what I want, were you referring to any in particular?

I’m not sure if you’re familiar with the Stewart rig from AnimationMentor, but it uses MEL scriptJobs in a script node to do the FK-IK snapping - I’d prefer to use Python but I guess I could convert the scripts to MEL.

Thanks again,
-Harry

The speed part should be a one time hit on load: the presence of a PYC file doesn’t matter too much on modern machines, it just means you’ll pay the script-to-compilation-to-memory cost – some fraction of a second – instead of just the compiledfile-to-memory cost – a smaller fraction – on startup.

However btribble is right that callbacks and scriptjobs are tricky beasts to use in rigging: they live in maya’s GUI layer, not down in the scene graph, so they are (a) slower than node base solutions and (b) prone to slowdowns based on the UI state of your windows. They can be useful aids to tools but you don’t want to rely on scriptJobs during every frame of an animation! You may want to explicitly disable them when animations are playing back or the time slider is scrubbing.

As for ‘shader nodes’ - many riggers use networks of math nodes from the Hypershade window to handle the math in their rigs rather than relying on expressions. 10 years ago this was critical - evaluating a big mel expression every frame was really slow, and nodes made things much more workable. On modern machines you can often get by with expressions but nodes are still faster; expressions are more flexible and powerful but nodes are speedier.

[QUOTE=hazmondo;25438]
I didn’t really understand what you meant in the second paragraph, you mention one script node to set up multiple scriptJobs but I still don’t know what the code would be for a Python script node which has more than one line.
[/QUOTE]

I believe you can actually just use multiline script code when setting up the scriptJob/scriptNode. To make it easy, you can assign it to a variable.

the_script_str = """
def rigging_ik_stuff():
    some_value = cmds.mayastuff("I can use double quotes inside triple-double quotes")
    if some_value:
        do_some_other_stuff()
"""

Then when you set up the scriptNode or scriptJob, you can just set script=the_script_str.

Another couple of caveats of using scriptnodes to alter rigging behaviour is that they are not executed when a scene is referenced (as rigs usually are in production in maya) and also the user can choose not to have them execute when opening a scene (via the option dialog for open scene). So you can end up with cases where rig features that depend on that script not working and the user not understanding why.

I was able to get the script node working by putting my def and scriptJob between triple quotes and assigning it as a variable and it works when the scene is opened, but as mentioned it doesn’t work when it is referenced.

So far I’ve used nodes to create all the rigging features and I have an enumerated attribute to control the FK IK snapping, conceptually I’m not sure how I would go about creating a node network that would allow me to control the limbs in FK or IK but then snap when the attribute is changed.

I don’t believe that I can use nodes because I’m using gimbal controls on, for example, the wrist so I can’t just match local space to local space, I really need match world space to world space.

I know that in any animation package there’s several ways of doing the same thing, but it doesn’t seem that there is any documentation on the internet of how to do FK IK snapping other than with scriptJobs, can I ask how you guys would do it?

Thanks again,
-Harry

I’d do it from within a custom GUI for the rig if you have one. Alternatively, the most lightweight way to do it is probably to just give them a shelf button. A few free rigs go the simple shelf button route so it’s easy for people to download and install themselves, but it’s not the nicest. Another option which works well (pretty elegant and modular) is to trigger it via a custom context sensitive marking menu i.e. when you right click over a control which supports IK/FK snapping you get a customised marking menu with the option to snap in there (and once you have that set up there’s a lot more useful things you can put in there too).

[QUOTE=Warheart;25444]I’d do it from within a custom GUI for the rig if you have one. Alternatively, the most lightweight way to do it is probably to just give them a shelf button. A few free rigs go the simple shelf button route so it’s easy for people to download and install themselves, but it’s not the nicest. Another option which works well (pretty elegant and modular) is to trigger it via a custom context sensitive marking menu i.e. when you right click over a control which supports IK/FK snapping you get a customised marking menu with the option to snap in there (and once you have that set up there’s a lot more useful things you can put in there too).[/QUOTE]

Yup. Pretty much this. We do something similar at work. It’s a lot safer to handle and there aren’t any issues with referencing.

Iterations on deployments are much easier as well.

One thing you could do if you did want to distribute scripts via scriptNodes is to stash the body of the script inside a fileInfo and then use the scriptNode to extract the script from there to disk and/or to shelf buttons. (It’s bad manners to do this without a confirm dialog, of course). This lets you have any degree of complexity you need, lets the users see your code (again, good manners) and allows you to distribute via a single file.

There’s a thread on here somewhere with code for stashing arbitrary data into fileInfo.

However, as others have pointed out, you probably don’t want the rig to fail without the script. You never know why somebody won’t or can’t use your script, so you should treat any script support as extra value rather than base functionality

Sorry for the late reply, I’ve been ill for the past few days and haven’t had a chance to do try to test anything out but I will!

Thanks again,
-Harry

I’ve recently returned to the idea of using scriptNodes to run scriptJobs, I have a test scene which works when loaded and when referenced once, but when you try to reference it a second time doesn’t work properly.

The test script is really two parts; the first part creates geometry and attributes and the second part creates the scriptNode and the scriptJobs.

The final scene is a cube and a circle (which is parented to the cube) which has an attribute on it which when changed prints a specified local translation axis value of the cube.

Here’s the script:


import maya.cmds as cmds

theCube = cmds.polyCube()
theCircle = cmds.circle()

cmds.parent(theCircle[0], theCube[0])

cmds.addAttr(theCircle[0], ln="printTranslate", nn="Print Translate", at="enum", en="X:Y:Z", h=False, k=True)
cmds.addAttr(theCircle[0], ln="theCube", nn="The Cube", at="message")
cmds.connectAttr(theCube[0] + ".message", theCircle[0] + ".theCube")

# theString will be used in the scriptNode
theString = '''import maya.cmds as cmds

# Get all transform nodes in the scene
theObjectList = cmds.ls(tr=True)
# Create an empty list
theCircleList = []
# For every object in theObjectList, if the object contains a printTranslate attribute, append it to theCircleList
for theObject in theObjectList:
	if cmds.attributeQuery("printTranslate", n=theObject, ex=True):
		theCircleList.append(theObject)

# Get all the current scriptJobs in the scene
theJobList = cmds.scriptJob(lj=True)		

# For every object in theCircleList, if no scriptJobs exist containing it's name, then create a scriptJob with the
# printTranslate definition
for theCircle in theCircleList:
	# Get the parent cube through the message attribute
	theCubeMsg = cmds.listConnections(theCircle + ".theCube")[0]
	
	# Create local variable with a zero value
	exists = 0
	# For every job in theJob list, if theJob contains theCircle's name then add 1 to the exists variable
	for theJob in theJobList:
		if theJob.find(theCircle) != -1:
			exists += 1
	
	# If exists is zero create a definition which prints one of theCubeMsg's translation axis values based on
	# the printTranslate attribute value
	if exists == 0:
		def printTranslate():
			theAttr = cmds.getAttr(theCircle + ".printTranslate")
			theStringAttr = cmds.getAttr(theCircle + ".printTranslate", asString=True)
		
			theObjectTranslate = cmds.getAttr(theCubeMsg + ".translate")[0][theAttr]
			# Format the string which will be printed
			print(theCubeMsg + " translate " + theStringAttr + ": " + str(theObjectTranslate))
		
		cmds.scriptJob(ac=[theCircle + ".printTranslate", printTranslate])'''

cmds.scriptNode(st=2, bs=theString, n="test", stp="python")

The problem which happens when referencing a second time is this:
Lets say we use the namespace string “test1”, when you change the circle’s printTranslate value to, say, “X” we get “test1_:pCube1 translate X: 0” as expected. When we reference the file again using the namespace string “test2” and change the test2:nurbsCircle1.printTranslate it prints “test1_:pCube1 translate X: 0”, it always test1_:pCube1 using the current printTranslate attribute on test1_:nurbsCircle1.

I’ve tried a bunch of stuff but I just can’t seem to get both to work when referenced! Any help would be much appreciated.

Thanks,
-Harry

Hmmm…

I can’t think of anything offhand. I don’t have time to play with this right now. You almost want a python script to be able to query the context in which the code is running (IE ask for a scriptNode node name).

And/Or…

If you’ll excuse the Minecraft Redstone line of thinking, ScriptNodes should have an attribute and a mode called scriptTrigger (or whatever) that causes the contained script to execute when the input becomes true.

None of this helps you right now I know…

Hi,

This is an old thread, but still useful and easy to find.

Instead of using Global variables, my Startup script node modifies the script string by inserting a “this” variable that gets the node with the changing attribute by its uuid.

From the “this” variable I can find other attributes or nodes connected by message attributes.

Hope this is helpful.

1 Like