Nuke - How to modify a node from inside a function? - python

In order to get well acquainted with Python for Nuke I am creating a small game that takes place in the Node Graph, but I hit a snag while trying to move my "character" around using a function. The character is a Dot and the function is trying to read its position in X and Y to determine in which direction it can move, then provide the player with those options and finally move the character in the direction chosen. The function must receive the character as an input but that's where I am having trouble, this is the simplified version of that part of the code:
global currentPosX
global currentPosY
currentPosX = 0
currentPosY = 0
def moveArea(self, node):
charT = node
print = currentPosX
currentPosX = charT['xpos'].value()
currentPosY = charT['ypos'].value()
char = nuke.nodes.Dot(hide_input=1, xpos=490, ypos=70)
moveArea(char)
I have tried many things and this code you see here is where I just couldn't think of any other options, I believe the problem is in how I input the 'char' node into the function but I could not find any resource that made it clear. Any help would be appreciated!

I've created a simplified function with some useful nuke commands thrown in that might be useful to you. For example, a function outside of a class doesn't need a self argument. This code only creates a characterDot if one doesn't already exist, so you'll be able to execute it multiple times and see the dot move further.
def moveArea(node, moveX=0, moveY=0):
# query current position of input node
currentPosX = node['xpos'].value()
currentPosY = node['ypos'].value()
# calculate new position based on arguments
newPosX = currentPosX + moveX
newPosY = currentPosY + moveY
# actually move the node
print "moving %s from (%s,%s) to (%s,%s)" % (node.name(), currentPosX, currentPosY, newPosX, newPosY)
node['xpos'].setValue(newPosX)
node['ypos'].setValue(newPosY)
# create the node for the very first time with a unique name (if it doesn't exist)
if not nuke.exists('characterDot'):
characterDot = nuke.nodes.Dot(hide_input=1, xpos=490, ypos=70, name='characterDot')
# find the node based on the name whenever you want
char = nuke.toNode('characterDot')
# run the move function on the character
moveArea(char, 100, 20)
Some syntax errors aside, your original code wasn't hugely problematic - though you never actually set new values to the node (using setValue) and only queried the nodes positions. Passing in the entire object is acceptable practice as far as I'm concerned! Though as part of working with nuke will likely involve lots of selecting, creating and deselecting nodes - so I've added a bit of code that will find the dot again based on its unique name.
My advice would be to create a class for the character dot, which has move functions that find it then move it.
Let me know if that helps, or if you can provide a slightly more complex demonstration of the issue you're having!

Related

Pymel: How do I close For loop after deleting null groups?

As I continue to study For Loops: I've run into some annoying errors. The problem is the script does exactly what I want it to. It deletes the null groups under the demo joints: but unlike other loops I've made for renaming which can be closed with a transform flag in the cmds.ls command: cmds.listRelatives doesn't allow a transform flag to close out the loop. You run the script by simply clicking Build Examples then hitting Delete Waste Groups
I've tried every flag according to the Maya documentation: but nothing seems to be closing the loop. I dont know if I need another variable, or a combination of some flags: or if I am using the wrong type of wording: but ideally what I would like this script to do is simply close out the loop so I dont get the error Error: No object matches name: curve
'''
import DS_wasteGroup_cleanerDemo
reload (DS_wasteGroup_cleanerDemo)
DS_wasteGroup_cleanerDemo.gui()
'''
import re
import maya.cmds as cmds
import maya.mel as mel
if cmds.window("renameWin", exists =True):
cmds.deleteUI("renameWin", window = True)
myWindow = cmds.window("renameWin",t='DS_wasteGroup_cleanerDemo',w=200, h=500, toolbox=True)
column = cmds.columnLayout(adj=True)
def gui():
cmds.button( label="Build Examples", c = buildExamples)
cmds.separator( w=200, h=3)
cmds.button( label="Delete Waste Groups", c = deleteWasteGrp)
cmds.separator( w=200, h=9)
cmds.setParent('..')
cmds.showWindow(myWindow)
def buildExamples(*args):
cmds.group(n='exampleGroup1',world=True,empty=True)
cmds.joint(n='demoJoint1')
cmds.group(n='curve1',world=True,empty=True)
cmds.parent('curve1','demoJoint1')
cmds.joint(n='demoJoint2')
cmds.parent('demoJoint2','exampleGroup1')
cmds.group(n='curve2',world=True,empty=True)
cmds.parent('curve2','demoJoint2')
cmds.joint(n='demoJoint3')
cmds.parent('demoJoint3','exampleGroup1')
cmds.group(n='curve3',world=True,empty=True)
cmds.parent('curve3','demoJoint3')
cmds.joint(n='demoJoint4')
cmds.parent('demoJoint4','exampleGroup1')
cmds.group(n='curve4',world=True,empty=True)
cmds.parent('curve4','demoJoint4')
cmds.joint(n='demoJoint5')
cmds.parent('demoJoint5','exampleGroup1')
cmds.group(n='curve5',world=True,empty=True)
cmds.parent('curve5','demoJoint5')
def deleteWasteGrp(*args):
grpList = cmds.listRelatives('demoJoint*',p=True,f=True)
for name in grpList:
print(grpList)
cmds.delete('curve*')
My apologies if I'm posting simple questions. I do write Python scripts to automate the most tedious tasks in rigging: but my knowledge is only intermediate. I want to learn more python so my scripts arent so clunky and brute forced: as well as the fact that I need them to be more adaptable to various types of characters: so any resources that dumb all this down would also be appreciated. Thank you for your help.
The error is correct, because the very first time the for loop executes, all "curve" obects are deleted, then in the next iteration, the same command does not find any curve objects because they are already deleted. If you place the delete command outside the for loop, the error should disappear.
Honestly I would take a whole different approach as you're hard-coding everything which could easily lead to disaster. When I mean hard-code, I mean you're trying to parent, let's say, "demoJoint2" to an object. This is bad because why are you assuming that "demoJoint2" even exists? If you create an object with a specific name that already exists, Maya will auto-rename the new object, and now you're referencing the wrong one right off the bat! Instead when you create your objects, capture their names in a variable then work with that, otherwise you'll be constantly shooting yourself in the foot.
Here's the same script with an approach I would try instead:
import maya.cmds as cmds
def gui():
if cmds.window("renameWin", exists=True):
cmds.deleteUI("renameWin", window=True)
myWindow = cmds.window("renameWin", t="DS_wasteGroup_cleanerDemo", w=200, h=500, toolbox=True)
column = cmds.columnLayout(adj=True)
cmds.button(label="Build Examples", c=buildExamples)
cmds.separator(w=200, h=3)
cmds.button(label="Delete Waste Groups", c=deleteWasteGrp)
cmds.separator(w=200, h=9)
cmds.setParent("..")
cmds.showWindow(myWindow)
def buildExamples(*args):
root = cmds.group(n="exampleGroup1", world=True, empty=True)
for i in range(5): # Loop to the amount of joints you want to create.
jnt = cmds.createNode("joint", name="demoJoint" + str(i + 1)) # Use `i` to help name the object.
jnt = cmds.parent(jnt, root)[0] # Parenting changes its long name, so recapture the joint in a variable.
crv = cmds.group(n="curve" + str(i + 1), world=True, empty=True) # Create empty group.
cmds.parent(crv, jnt) # Parent curve to joint.
def deleteWasteGrp(*args):
jnts = cmds.ls("demoJoint*", long=True, type="joint") # Get all `demoJoints`.
children = cmds.listRelatives(jnts, f=True, children=True, type="transform") or [] # Get all of their children, and only get transform types.
curves = [obj for obj in children if obj.split("|")[-1].startswith("curve")] # Don't assume we got the right objects. Run a final loop to collect any object that starts with `curve`. Need to use split as we're looping through long names but need to check its short name.
if curves: # `cmds.delete` will error if this list is empty, so don't assume.
cmds.delete(curves) # Delete all curves at once.
gui()
Now I can hit the build button as much as I want with no issues, and delete all the curves when pressing the delete button.
A few more notes:
Notice in buildExamples I'm using a loop to create all the objects instead of reusing redundant code that does the same thing. You could even have a spinbox in your gui that defines how many joints it creates now, where as before it wasn't possible because the count was hard-coded.
cmds.listRelatives does have a way to filter objects by transforms by setting parameter type="transform". In fact you'll see many commands have this same parameter (again start checking docs).
cmds.listRelatives('demoJoint*',p=True,f=True) was grabbing the joint's parent, not its children. The docs clearly state this.
Running cmds.delete('curve*') is going to delete ALL objects with names that start with curve, and since you're running this in a loop it's trying to do this multiple times.
maya.cmds is not pymel. There's a whole separate module called pymel.
If you're unsure with any parts of the code try adding in a print statement to see what it's doing.
I feel like you're going about this whole process a bit wrong, and I would love to elaborate if you're interested, but for now here is a fix for your loop situation:
def deleteWasteGrp(*args):
curveList = cmds.ls('curve*',transforms=True)
try:
cmds.delete(curveList)
print('Deleted the following objects: {}'.format(curveList))
except Exception as e:
cmds.warning('Cleanup failed: {}'.format(e))
The cmds.delete method accepts a list parameter, which in your case is the easiest way to get the job done. Keep in mind that when you delete a parent object, you also delete its children, so depending on your circumstances deleting objects can be order-specific.
Throwing any "likely to fail" calls in a try/except clause is generally a good idea, as it lets you handle the error gracefully. Be careful, however, to not suppress it and just move on -- you at the very least need to alert the user adequately.
Lastly, your buildExamples method will most likely fail if you run it more than once. Because you are addressing objects by string literals (hard coded names) instead of keeping track of their actual names (and full path). You will likely see this error eventually:
# Error: ValueError: file <maya console> line ??: More than one object matches name: demoJoint1 #
Edit: Some elaborations as requested
The commands cmds.group and cmds.joint return a string value indicating the actual name of the object created (in create mode). It's usually a good idea of storing this value in case Maya decides to name your object slightly differently than what you are expecting, usually when there is a naming clash. Eg:
print cmds.group(name='test', world=True, empty=True)
# Returns: test
print cmds.group(name='test', world=True, empty=True)
# Returns: test1
Example of how to capture object names as you create them. I've concatenated your five identical(ish) calls to create joints and curves in this loop:
import maya.cmds as cmds
topGroupName = 'exampleGroup'
actualTopGroupName = None
# Create top level group
actualTopGroupName = cmds.group(n=topGroupName, world=True, empty=True)
# Loop through 5 times and do the following:
for i in range(5):
# PS: hash character in name indicates "next available number"
cmds.select(clear=True)
jnt = cmds.joint(n='demoJoint#')
crv = cmds.group(n='curve#',world=True,empty=True)
cmds.parent(crv, jnt)
cmds.parent(jnt, actualTopGroupName)
Example of how to narrow down which objects to search for with cmds.ls:
topGroupName = 'exampleGroup'
print cmds.ls('|{}*|*|curve*'.format(topGroupName))
# Returns: [u'curve1', u'curve2', u'curve3', u'curve4', u'curve5']
# The string .format() expression is just a dynamic way of writing this:
# |exampleGroup*|*|curve*
Vertical pipes (|) indicate levels in a hierarchy, similar to how slashes (/) work in URLs. And asterisks/wildcards (*) indicate "any character, or none".
Hope this helps you along your way a little bit.

Linking two fields in 2 different nodes created proceduraly

I am creating a nuke node to easily manipulate my different lights in a node. I want to code it so that it doesn't matter how many lights are in any given file, and the code can figure it out and display the necessary tools to modify them. To achieve this, I first identify how many lights are in the file and store that value, which then gets fed to a 'for' cycle to create as many nodes and tools as lights are on the original file. 'tempNumber' is the number assigned to the original Lights on the file, and it helps create and manage all the tools, labeled to match the light they are modifying. It becomes complicated because the names of the nodes generated look like this:
globals()['expoParent{}'.format(tempNumber)]
Up to now everything has been working fine but I am stuck trying to link two different Exposure nodes to reflect the same values as the user modifies them. I created this code for that purpose on a separate script:
par = nuke.nodes.EXPTool(mode = 'Stops', name = 'Parent')
chi = nuke.nodes.EXPTool(mode = 'Stops', name = 'Child')
par.knob('knobChanged').setValue('chi["red"].setValue(par["red"].value())\nchi["green"].setValue(par["green"].value())\nchi["blue"].setValue(par["blue"].value())')
This previous bit of code works perfectly, it creates 2 Exposure nodes and it links the Child to the Parent so that any change made to the Parent is then automatically passed along to the Child immediately. Now the problem comes when I try to replace the variables par and chi with globals()['expoParent{}'.format(tempNumber)] and globals()['expoChild{}'.format(tempNumber)] . At this point the code stops working and the link is not successful. I have read plenty of people saying that variables should not be created through a for loop but I don't see any other way of doing it.
Why do it with a knobChanged function at all when you can just link the node fields with expressions?
Here's an example that links two grade nodes (Grade1 and Grade2) with Grade1 as the parent. You'll need to have these in your script before running the code. Though you could also create them procedurally.
parent = nuke.toNode('Grade1')
child = nuke.toNode('Grade2')
child['multiply'].setExpression('%s.multiply' % parent.name())
This is a lot cleaner, and would be represented visually in the node tree too.

how to add location constraints for an object in blender?

I want to make it so that my program will stop running and print object is out of bounds if an object goes say into the negative z part of the plane in blender.
the objects name is Cube.031. I will sudo code what I want to do I just am not sure about sure how to do the syntax for it.
if(Cube.031.zLocation < 0)
print(object is out of bounds)
end
If you know some programming, learning python won't take long.
For the blender specific info, almost everything is accessed through the bpy module, the API reference is online.
You can refer to an object by name in bpy.data.objects[]. There are also other lists available, like bpy.context.selected_objects[] and bpy.context.visible_objects[].
An objects location is an array of three values (x,y,z), you can either access the z location as location.z or location[2].
import bpy
obj = bpy.data.objects['Cube.031']
if obj.location.z < 0:
print('object is out of bounds')
If you wanted to go through all selected objects
for obj in bpy.context.selected_objects:
if obj.location.z < 0:
print('object {} is out of bounds'.format(obj.name))
Note that v2.80 is due for release soon and has some changes to the API, if you are just starting with blender you may want to start with 2.80. You will also find blender.stackexchange a better place to ask for blender specific help.

3dsmax python add float script to sub animations

I have the next setup: I have a sphere which has a morpher modifier. This morpher modifier has a certain amount of channels filled with morph targets aka sub animations. Now I want to add a controller to each of these subanimations, more specifically a controller with a float script. I have the code snippet that should work but when I go to the curve editor, the morph channels/ sub animations did not change controller, nor is the value of their controller changed.
import MaxPlus
target = MaxPlus.INode.GetINodeByName('Sphere001')
#Retrieve the morpher modifier
mod = target.GetModifier(0)
#ID of a float script controller
id = MaxPlus.Class_ID(1233584870,1911625032)
#Create float controller
float_co = MaxPlus.Factory.CreateFloatController(id)
#Retrieve the first morph channel / sub animation
sub = mod.GetSubAnim(1)
#Controller is assigned to the sub animation
sub.AssignController(float_co,1)
#Basic test which assigns 20 to the sub animation
float_co.ParameterBlock.Script.Value = '20'
When I add a wrong value to the script, for example:
float_co.ParameterBlock.Script.Value = '=20'
I receive an error and the usual window when you manually add a controller to an object or node. However the strange thing is that at the top of the window: the name of the object to which it is connected, does not show. See figure for clarification:
Can someone tell me what I'm doing wrong? Thank you!
I solved it by using the ugly way:
import MaxPlus
test = MaxPlus.FPValue()
success = MaxPlus.Core.EvalMAXScript(string_with_command,test)
This is used twice: first to create the float script controller and a second time to add the script to the controller. Be careful if anyone wants to try this, the script for the controller needs to be a string. Do not use
x as string
with the expressing you want to use as script for the float script controller since maxscript will evaluate x on the timeframe you are currently in 3ds max and will convert this value to a string. This value will be used as script for all time frames which clearly is not what you want. A small hack I used was:
script_value_example = '"amax #(0, ($sphere.position.x - cube.position.y))"'
This is still a string for python and maxscript will see the " " and will interpret it as a string. The other way around doesn't work, maxscript does not interpret ' ' as string.
Maybe this will help someone in the future. Also if someone knows the proper way to do it using the code in the question, please leave a reply, I'm interested to know.

Python Global Variable Not Working

I have an extremely long program for a Canasta game that I am writing, in it there is a variable that ends the turn of the player if it equals a certain value. However, the turn repeats itself.
I have read many different articles and posts on forums like this one, but none of these seem to work. I even have a debug system to print the value of the variable before the if statement, and the variable is the right value, but the if statement doesn't see the same value.
I can't give the entire code, as it is extremely long, but I can give the sections that use this variable. I am using Python 2.7.2 and cannot make files separate from the main file to import.
The first line of the program is:
endTurn=1
The following function is called when conditions to win the game are met, but I have not been able to test that this works because of the bug.
def winGame(player):
global endTurn
gameWinner=player
gameWinner["score"]+=100
endTurn=0
The following function is called when a move is made. The variable decide is a raw_input() variable.
def move(player,decide):
global endTurn
theMove=decide.lower()
#if player says to end turn
if theMove=="end":
#until player discards something
discarded=0
while not discarded:
displayHand(player)
#ask player for discard
discard=int(raw_input(" Enter the list number of the card you wish to discard:"))-1
#if discard ID is in player's hand length
if not discard<0 and not discard>len(player["hand"])-1:
#add card to discard pile and remove from hand
discardPile.append(player["hand"][discard])
del(player["hand"][discard])
discarded=1
debug("Before changing, endTurn is %s"%str(endTurn))
endTurn = 0
debug("After changing, endTurn is %s"%str(endTurn))
if theMove=="new book":
newBook(player)
if theMove=="add to book":
addBook(player)
Here is where the turn should be ended. turn(thePlayer) repeats the function. The debug statement shows the correct value, 0, but the if still reads a 1. The function this is in also has the global endTurn at the top.
debug("If ending turn, endTurn of %s should be 0."%str(endTurn))
if endTurn==1:
turn(thePlayer)
Any help is greatly appreciated!
EDIT: The code is available at http://labs.codecademy.com/CV9z#:workspace. I ask that anyone viewing the code does not modify it, so that other people can see the true code.
I fixed the bug. I removed the endTurn variable and instead made the function that ends the turn just do the turn of the next player. It also simplified my code a bit.
I am wondering though, is using this method of running a function inside of itself over and over and over again (without leaving) rather messy or maybe slowing down my program?
EDIT: I now realize that this answer does not exactly help with anyone else who is stuck with global variables...
The previous answer I posted was just wrong, as it was based on a misinterpretation of the namespace docs as I had read them (Thanks to TheifMaster for pointing out my error), so this is a SEVERELY EDITED ANSWER:
The problem is in the while loop that I was able to read when the link to the entire code was posted after the erroneous answer I gave earlier.
while gameWinner==0:
endTurn=1
turn(player1)
if not gameWinner==0:
endTurn=1
turn(player2)
The game can never get to player two in this loop until gameWinner!=0.
I added the entire code to my sandbox and changed it to:
while gameWinner==0:
endTurn=1
turn(player1)
if gameWinner==0: #if player1 did not win yet
endTurn=1
turn(player2)
However, the game is still buggy... No one can win! gameWinner is not changed globally by the winGame() function until you add it to the global statement as I have shown here.
def winGame(player):
global endTurn, gameWinner
gameWinner=player
gameWinner["score"]+=100
debug("gameWinner == " + str(gameWinner))
endTurn=0

Categories

Resources