PDA

View Full Version : Python API question


invadererik
03-17-2009, 09:20 PM
Hi I'm trying to create my first Node using Python API.

Im trying to create a compound attribute, basically an array of floats.
I've searched the examples and the internet, but haven't found
a good example that covers this well.

im initializing my node with attributes like this:

multiMultiX.aInValues = nAttr.create("inValues", "ivs", OpenMaya.MFnNumericData.kFloat, 0.0)nAttr.setKeyable(1)
nAttr.setStorable(1)
nAttr.setReadable(1)
nAttr.setWritable(1)
nAttr.setDefault(0.0)
multiMultiX.aInList = compoundAttr.create ("inList", "il" ) compoundAttr.setArray(1);
compoundAttr.addChild(multiMultiX.aInValues);
compoundAttr.setReadable(1);
compoundAttr.setUsesArrayDataBuilder(1);
And this works to a certain degree, when I select my node I can
see in the attribute editor the aInList attribute with a button to
"add new item" but when I click it I get this:

// Error: Found no attribute match for "multiMultiX1.inValues" //

If anybody can point me to any example of a Maya Python API plug-in
that deals with compound attributes, that would be great !

Keilun
03-17-2009, 09:44 PM
Can you post your full initialize method?

invadererik
03-17-2009, 09:57 PM
Thank you for helping !

(copy paste below , I think all the tabbing is gone, sorry)


def nodeInitializer():
nAttr = OpenMaya.MFnNumericAttribute ()
compoundAttr = OpenMaya.MFnCompoundAttribute ()
try:
multiMultiX.aInMultiplier = nAttr.create( "multiplier", "mul", OpenMaya.MFnNumericData.kFloat, 1.0)
nAttr.setKeyable(1)
nAttr.setStorable(1)
nAttr.setReadable(1)
nAttr.setWritable(1)
nAttr.setDefault(1.0)
multiMultiX.aInValues = nAttr.create("inValues", "ivs", OpenMaya.MFnNumericData.kFloat, 0.0)
nAttr.setKeyable(1)
#nAttr.setArray (1)
#nAttr.setUsesArrayDataBuilder(1);
nAttr.setStorable(1)
nAttr.setReadable(1)
nAttr.setWritable(1)
nAttr.setDefault(0.0)
multiMultiX.aOutValues = nAttr.create("outValues", "ovs", OpenMaya.MFnNumericData.kFloat )
nAttr.setKeyable(0)
#nAttr.setArray (1)
#nAttr.setUsesArrayDataBuilder(1);
nAttr.setStorable(0)
nAttr.setHidden(0)
nAttr.setWritable(0)
nAttr.setReadable(1)
multiMultiX.aInList = compoundAttr.create ("inList", "il" )
compoundAttr.setArray(1);
compoundAttr.addChild(multiMultiX.aInValues);
compoundAttr.setReadable(1);
compoundAttr.setUsesArrayDataBuilder(1);
multiMultiX.aOutList = compoundAttr.create ("outList", "ol" )
compoundAttr.setArray(1);
compoundAttr.addChild(multiMultiX.aOutValues);
compoundAttr.setReadable(1);
compoundAttr.setUsesArrayDataBuilder(1);
except:
sys.stderr.write("Failed to create attributes\n")
raise
try:
multiMultiX.addAttribute(multiMultiX.aInMultiplier)
multiMultiX.addAttribute(multiMultiX.aInList)
multiMultiX.addAttribute(multiMultiX.aOutList)
multiMultiX.addAttribute(multiMultiX.aInValues)
multiMultiX.addAttribute(multiMultiX.aOutValues)
except:
sys.stderr.write("Failed to add attributes\n")
raise
try:
multiMultiX.attributeAffects (multiMultiX.aInMultiplier, multiMultiX.aInList)
multiMultiX.attributeAffects (multiMultiX.aInMultiplier, multiMultiX.aOutList)
multiMultiX.attributeAffects (multiMultiX.aInList, multiMultiX.aOutList)
except:
sys.stderr.write("Failed in setting attributeAffects\n")
raise

Keilun
03-17-2009, 10:12 PM
For child attributes of a compound attribute, you do not need to call addAttribute. You only need to call addAttribute for the compound attribute. Give that a try. Let me know if that resolves your issue.

Specifically, comment out these lines:


multiMultiX.addAttribute(multiMultiX.aInValues)
multiMultiX.addAttribute(multiMultiX.aOutValues)

invadererik
03-17-2009, 10:20 PM
Same error ! = ( , what I did see before is that if I added the floats before the lists (compund attributes) it would say "attribute already added" , but it doesnt complain if I add them afterwards.

In case you were wondering
Im just trying to create something like the
plusMinusAverage Node

Any links to any Python API that does something similar ?
Thank you for the help, I really appreciate it.

Keilun
03-17-2009, 10:28 PM
The Maya devkit should have a number of nodes converted to Python. I'm not sure which of them involve compound attributes. You could probably do a search for MFnCompoundAttributes in *.py files in the /devkit/ folder.

Same error ! = ( , what I did see before is that if I added the floats before the lists (compund attributes) it would say "attribute already added" , but it doesnt complain if I add them afterwards.

Yeah, addAttribute would likely return a MS::kFailure at that point which is probably silently failing in your method? I'm not sure. But the addChild will already add it to the attribute list, once you addAttribute the compound attr.

Other things to check:

- Try changing the names of your attributes to something more unique (especially the short-forms). It could possibly be conflicting with default attribute names.

- Run the MEL commands and post the output:

listAttr yourNodeName
listAttr -sn yourNodeName

This will output the list of long and short names. This would help check if there are any name conflicts.

invadererik
03-17-2009, 11:37 PM
Actually maya devkit was the first place I looked, in the python folder there is not 1 single script that used a compound attribute. Thats why im asking for any example.

I tried changing my variable names, I added my name "erik" at the beginning of all variables,
that did not help, same error with longer name.

Maybe Im supposed to define a function somewhere of what happens when the button is clicked? Also wouldnt it start out with 1 float already?

RyanT
03-18-2009, 03:33 AM
invadererik I am about to leave my office but I can give you an example script to look at

Download my rt44MatrixToTRS utility node here:

http://www.rtrowbridge.com/blog/downloads/

I would need to see your entire script to debug it properly. You can e-mail it to me if you want or post it here. As others have said you do not want to add attributes on their own if you are creating a compound attribute. You only add them to the compound attribute then you add them to the node. Like so:

cAttr = OpenMaya.MFnCompoundAttribute()

# Vector X
rtMatrixUtilNode.in00 = nAttr.create("in00", "i00", OpenMaya.MFnNumericData.kFloat, 0.0)
nAttr.setWritable(True)
nAttr.setStorable(True)
nAttr.setReadable(True)
nAttr.setKeyable(True)

rtMatrixUtilNode.matrixIn = cAttr.create( "matrixIn", "mi" )
cAttr.addChild( rtMatrixUtilNode.in00 )

Make sure your node also has both the compound attribute and the child attributes of that compound node added to the MPxNode as attrbitues like this:

in00 = OpenMaya.MObject()

You can tear apart my utility node to see this. If you post your script I will show you how to do that with what your doing.

RyanT
03-18-2009, 05:30 AM
Try this, hope it helps. You can find more Maya Python examples on my blog.

"""
Save this file somewhere on your computer with the .py extention
In maya select the menu Window/Settings and Preferences/Plug-in Manager
Click the browse button and find this script

Now in the script editor in a python tab run these lines of script:

import maya.cmds as cmds

cmdAttrNode = cmds.createNode('compoundExampleNode')
sphere = cmds.polySphere()
cmds.connectAttr( (sphere[1] + '.radius'), (cmdAttrNode + '.input'), force=True)
cmds.getAttr( cmdAttrNode + '.output')
"""

import sys
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx

kcompUtilNodeTypeName = "compoundExampleNode"
kcompUtilNodeClassify = "utility/general"
kcompUtilNodeId = OpenMaya.MTypeId(0x87325)

# define a new matrixUtilNode class derived from the MPxNode class
class rtcompUtilNode(OpenMayaMPx.MPxNode):

# class variables
compIn = OpenMaya.MObject()
compOut = OpenMaya.MObject()
input = OpenMaya.MObject()
output = OpenMaya.MObject()


def __init__(self):
OpenMayaMPx.MPxNode.__init__(self)

# arguments ( self, MPlug, MDataBlock)
def compute(self, plug, dataBlock):

# if these attributes are requested, recompute their values
if plug == rtcompUtilNode.compOut or plug.parent() == rtcompUtilNode.compOut:

# get MDataHandle's to attributes
#
try:
input_dataHandle = dataBlock.inputValue( rtcompUtilNode.input )
except:
sys.stderr.write( "Failed to get MDataHandle inputValue input" )
raise
try:
output_dataHandle = dataBlock.outputValue( rtcompUtilNode.output )
except:
sys.stderr.write( "Failed to get MDataHandle outputValue output" )
raise

# get values from dataHandle
#
input_value = input_dataHandle.asFloat()

# set the output
output_dataHandle.setFloat(input_value)

# set the plug clean so maya knows it can update
dataBlock.setClean(plug)

else:
return OpenMaya.kUnknownParameter

return OpenMaya.MStatus.kSuccess

def nodeCreator():

return OpenMayaMPx.asMPxPtr( rtcompUtilNode() )

# create and initialize the attributes to the node
def nodeInitializer():

nAttr = OpenMaya.MFnNumericAttribute()
cAttr = OpenMaya.MFnCompoundAttribute()

# create input attributes
#
rtcompUtilNode.input = nAttr.create("input", "i", OpenMaya.MFnNumericData.kFloat, 0.0)
nAttr.setWritable(True)
nAttr.setStorable(True)
nAttr.setReadable(True)
nAttr.setKeyable(True)

rtcompUtilNode.output = nAttr.create("output", "o", OpenMaya.MFnNumericData.kFloat, 0.0)
nAttr.setWritable(False)
nAttr.setStorable(False)
nAttr.setReadable(True)


# create compound attribute
#
rtcompUtilNode.compIn = cAttr.create( "compIn", "ci" )
cAttr.addChild( rtcompUtilNode.input )


rtcompUtilNode.compOut = cAttr.create( "compOut", "co" )
cAttr.addChild( rtcompUtilNode.output )

# add attribues
#
rtcompUtilNode.addAttribute( rtcompUtilNode.compIn )
rtcompUtilNode.addAttribute( rtcompUtilNode.compOut )

# Setup which attributes affect each other
rtcompUtilNode.attributeAffects ( rtcompUtilNode.compIn, rtcompUtilNode.compOut )



# initialize the script plug-in
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject, "Autodesk", "1.0", "Any")
try:
mplugin.registerNode( kcompUtilNodeTypeName, kcompUtilNodeId, nodeCreator, nodeInitializer, OpenMayaMPx.MPxNode.kDependNode, kcompUtilNodeClassify)
except:
sys.stderr.write( "Failed to register node: %s" % kcompUtilNodeTypeName )
raise


# uninitialize the script plug-in
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.deregisterNode( kcompUtilNodeId )
except:
sys.stderr.write( "Failed to deregister node: %s" % kcompUtilNodeTypeName )
raise

RyanT
03-18-2009, 06:37 AM
Here is a detailed description of how the node works.

http://www.rtrowbridge.com/blog/2009/03/17/python-api-compound-attribute/

invadererik
03-19-2009, 08:01 AM
Hey Ryan,

Wow, Im blown away by the response, a whole tutorial ! Yikes. Thank you very much.

However, one slight problem even though yes admittedly it solves the basic issue I was having ...
( basicaly if I comment out the properties for the compound object,
I get exactly what you have, which is pretty much the same as having a bunch
of floats, only difference is you can connect all of them in one connection I guess)
it doesn't really address the goal of what I was trying to do! = (

Really I dont want to sound mean, I am really appreciating your help and Keiluns ! Oh and I loved Drakes Fortune awesome game, awesome job !

Anyway back to the problem at hand... I guess the problem is not directly with compound attributes themeselves, but compund attributes with these properties:

compoundAttr.setArray(1);
compoundAttr.setUsesArrayDataBuilder(1);

... basically as I explained before I'm trying to create something that resembles the way the plusMinusAverage utility Node works. Basically you get a button that lets you append attributes of x type into your compund attr.

So hopefully the tutorial will help other people getting into the Python API ! But sadly didnt help me = ( ...

Oh by the way, I think its awesome you're converting micheal comets
pose deformer code to Python ! I've rigged some stuff with it before and it kicks !
( altough I think youd lose a lot of the speed in python, and you really need it for PSD's ! )

Anyway, would love to see a solution to the oum... ... what should I call it... compundAttr self expanding array problem ?

Hint for solving the problem could possibly be found in C++ API example: multiCurveNode .... seems to work in C++ why not in python = (

Oh by the way how did you keep the tabbing (formating) for your python code on the post?

Keilun
03-19-2009, 06:34 PM
I was looking over your code again and figured we should start stripping it down. One thing came to mind is that your input attribute shouldn't need to be using an array data builder. If it's really an input, then there really isn't any reason your node should be building that attribute at all (unless I'm mistaken?).

If you can copy the source code for the whole node, we'd be in a better position to help solve your issue. Specifically your compute method, but other setup details may be pertinent as well.

RyanT
03-19-2009, 06:47 PM
Sorry invadererik I missed that you wanted an array attribute. Here is an example. I plan on doing a tutorial on this one as well. I am glad you like Uncharted. As for the pose reader. Yes the PSD might be questionable as far as getting it to work fast with Python. I did get the pose reader node to work though at speed and that node by itself is very helpful.


"""
# load this file as a plugin then execute this code in a python script editor tab
import maya.cmds as cmds
sphere = cmds.polySphere()
pNode = cmds.createNode( 'pyNode' )
cmds.move(1.2, 3.4, 4.5, sphere[0], os=True, wd=True )

cmds.connectAttr( (sphere[0] + '.translateX'), (pNode + '.inputs[0]'), f=True )
cmds.connectAttr( (sphere[0] + '.translateY'), (pNode + '.inputs[1]'), f=True )
cmds.connectAttr( (sphere[0] + '.translateZ'), (pNode + '.inputs[2]'), f=True )
cmds.getAttr( (pNode + '.output[0]') )
cmds.getAttr( (pNode + '.output[1]') )
cmds.getAttr( (pNode + '.output[2]') )
"""


import sys
import maya.OpenMaya as om
import maya.OpenMayaMPx as omMPx

kPluginNodeTypeName = "pyNode"

pyNodeId = om.MTypeId(0x87000)

class pyNode( omMPx.MPxNode ):

inputs = om.MObject()
output = om.MObject()

def __init__( self ):
omMPx.MPxNode.__init__( self )

def compute( self, plug, dataBlock ):

if (plug == pyNode.output) and plug.isElement():

# Version 1.0
# This is ineffective and buggy because it goes through all the output plugs even if they
# dont exist yet
"""
# Get the input handle
dataHandle = dataBlock.inputArrayValue( pyNode.inputs )

# Get output handle
outputHandle = dataBlock.outputArrayValue( pyNode.output )

numElements = dataHandle.elementCount()

# Iterate through input array and set the output array
for i in range(numElements):

dataHandle.jumpToElement(i)
handle = dataHandle.inputValue()
result = handle.asFloat()

outputHandle.jumpToElement(i)
outdatahandle = outputHandle.outputValue()
outdatahandle.setFloat( result )

dataBlock.setClean( plug )
"""


# Version 2.0
# This only checks on the plug that has been changed
# Get the input handle
dataHandle = dataBlock.inputArrayValue(pyNode.inputs)

# Get output handle
outputHandle = dataBlock.outputArrayValue(pyNode.output)

# Get the element index
index = plug.logicalIndex()

# Position the arrays at the correct element.
dataHandle.jumpToElement(index)
outputHandle.jumpToElement(index)

# Copy the input element value to the output element.
outputHandle.outputValue().setFloat(dataHandle.inputValue().asFloat())



# Version 3.0
"""
# Get the input handle
dataHandle = dataBlock.inputArrayValue(pyNode.inputs)
numElements = dataHandle.elementCount()

# Get output handle and its array data builder
outputHandle = dataBlock.outputArrayValue(pyNode.output)
outputBuilder = outputHandle.builder()

# Do some really expensive setup code.

# Iterate through input array and set the output array
for i in range(numElements):

dataHandle.jumpToElement(i)
handle = dataHandle.inputValue()
result = handle.asFloat()

try:
outputHandle.jumpToElement(i)
outdatahandle = outputHandle.outputValue()
except:
outdatahandle = outputBuilder.addElement(i)

outdatahandle.setFloat(result)

outputHandle.set(builder)
"""

else:
return om.kUnknownParameter



def compute(self, plug, dataBlock):
if (plug == pyNode.output) and plug.isElement():
# Get the input handle
dataHandle = dataBlock.inputArrayValue(pyNode.inputs)

# Get output handle
outputHandle = dataBlock.outputArrayValue(pyNode.output)

# Get the element index
index = plug.logicalIndex()

# Position the arrays at the correct element.
dataHandle.jumpToElement(index)
outputHandle.jumpToElement(index)

# Copy the input element value to the output element.
outputHandle.outputValue().setFloat(dataHandle.inputValue().asFloat())
else:
return om.kUnknownParameter


def nodeCreator():

return omMPx.asMPxPtr( pyNode() )

def nodeInitializer():

nAttr = om.MFnNumericAttribute()
pyNode.inputs = nAttr.create ( "inputs", "in", om.MFnNumericData.kFloat, 0.0 )
nAttr.setArray(1)
nAttr.setStorable(1)
pyNode.addAttribute ( pyNode.inputs )

nAttr = om.MFnNumericAttribute()
pyNode.output = nAttr.create( "output", "out", om.MFnNumericData.kFloat, 0.0 )
nAttr.setArray(1)
nAttr.setStorable(1)
nAttr.setWritable(1)
pyNode.addAttribute( pyNode.output )

pyNode.attributeAffects( pyNode.inputs, pyNode.output )

def initializePlugin(mobject):

mplugin = omMPx.MFnPlugin(mobject)

try:
mplugin.registerNode( kPluginNodeTypeName, pyNodeId, nodeCreator, nodeInitializer )

except:
sys.stderr.write( "Failed to register node: %s" % kPluginNodeTypeName )
raise

def uninitializePlugin(mobject):

mplugin = omMPx.MFnPlugin(mobject)

try:
mplugin.deregisterNode( pyNodeId )

except:
sys.stderr.write( "Failed to deregister node: %s" % PluginNodeTypeName )
raise

invadererik
04-24-2009, 07:00 AM
Hey Ryan,

Looking good, the code post is a bit confusing because it has like 4 compute functions, not exactly sure which one you deemed to be the best. I ended up using the last one, to pretty much do what I wanted but adding connections in code only. There is still the small issue of the useful for (non coders/scripters) button that adds an empty float.

Anyway thank you for all the help !

CGTalk Moderation
04-24-2009, 07:00 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.