Rivet-like node attempt

226 views
Skip to first unread message

Enrico Losavio

unread,
Jul 29, 2014, 7:21:38 PM7/29/14
to python_in...@googlegroups.com
Hi everyone!

I'm trying to write a sort of River node, but I ran into some difficulties (obviously :) ) and any help would be greatly appreciated!

Basically i want a DG node that can
  • accept geometry as an input
  • retrieve mesh information
  • make up his own calculations based on the mesh information and a few custom attributes
  • have an array of MPoints (or k3Double i guess) as output to be piped into each locator
  • have the output updated every time an attribute or the mesh itself changes
As far as I know, the MPxDeformerNode is the only way to access the geometry data. But I don't want to deform the mesh, just query information.
And yet I don't fully understand how the push-pull mechanism works in this case. In a custom DG node i would use the "if plug == attribute" to force the evaluation, but in this case...?
I've already jot down the basic structure for the node, but it doesn't work as it should.
In Maya I loaded the plugin, applied the deformer to a simple cube, and fed each value of the array into the Translate attribute of specifically created locators (that thus went in the correct position, one on each vertex)
If I tweak the mesh, the locators remain in the same position, don't follow the vertices. If I change the value of the positionOffset attribute, nothing happens.
But I noticed that if i tweak the mesh and then change the value of the attribute, the position of all the locators suddenly updates as though the mesh is read properly, but the positionOffset value remains the previous one.

Here's a simplified version of the code I wrote...:

class AttachToMesh(OMMPx.MPxDeformerNode):


    mObj_positionOffset
= OM.MObject()
    mObj_outPosition
= OM.MObject()


   
def __init__(self):
       
OMMPx.MPxDeformerNode.__init__(self)


   
def deform(self, dataBlock, geoIterator, matrix, geometryIndex):
       
       
# RETRIEVE GEOMETRY DATA
        input
= OMMPx.cvar.MPxDeformerNode_input
        dataHandleInputArray
= dataBlock.outputArrayValue(input)
        dataHandleInputArray
.jumpToElement(geometryIndex)
        dataHandleInputElement
= dataHandleInputArray.outputValue()

        inputGeom
= OMMPx.cvar.MPxDeformerNode_inputGeom
        dataHandleInputGeom
= dataHandleInputElement.child(inputGeom)
        inMesh
= dataHandleInputGeom.asMesh()


       
# POSITION OFFSET
        dataHandlePositionOffset
= dataBlock.inputValue(AttachToMesh.mObj_positionOffset)
        positionOffsetValue
= dataHandlePositionOffset.asFloat()

       
# OUT POSITION
        dataHandleArrayOutPosition
= dataBlock.outputArrayValue(AttachToMesh.mObj_outPosition)
        dataBuilderOutPosition
= dataHandleArrayOutPosition.builder()

        vertexPosition
= OM.MPoint()
        mFnMesh
= OM.MFnMesh(inMesh)
       
        normals
= OM.MFloatVectorArray()
        mFnMesh
.getVertexNormals(False, normals)


       
while not geoIterator.isDone():


            vertexPosition
.x = geoIterator.position().x + ( normals[geoIterator.index()].x * positionOffsetValue )
            vertexPosition
.y = geoIterator.position().y + ( normals[geoIterator.index()].y * positionOffsetValue )
            vertexPosition
.z = geoIterator.position().z + ( normals[geoIterator.index()].z * positionOffsetValue )
           
            dataHandleOutPosition
= dataBuilderOutPosition.addElement(geoIterator.index())
            dataHandleOutPosition
.set3Double(vertexPosition.x, vertexPosition.y, vertexPosition.z)

            geoIterator
.next()
       
        dataHandleArrayOutPosition
.set(dataBuilderOutPosition)
       
       
#here I'm not sure which setClean function I'm supposed to use... in this case I chose dataBlock.setClean
       
#dataHandleArrayOutPosition.setAllClean()
       
        dataBlock
.setClean(AttachToMesh.mObj_outPosition)


def nodeInitializer():    
    mFnAttr
= OM.MFnNumericAttribute()


   
AttachToMesh.mObj_positionOffset = mFnAttr.create("positionOffset", "posOff", OM.MFnNumericData.kFloat, 0.0)
    mFnAttr
.setKeyable(1)

   
AttachToMesh.mObj_outPosition = mFnAttr.create("outPosition", "outPos", OM.MFnNumericData.k3Double, 0.0)
    mFnAttr
.setKeyable(0)
    mFnAttr
.setWritable(0)
    mFnAttr
.setArray(1)
    mFnAttr
.setUsesArrayDataBuilder(1)

   
AttachToMesh.addAttribute(AttachToMesh.mObj_outPosition)

   
AttachToMesh.attributeAffects(AttachToMesh.mObj_positionOffset, AttachToMesh.mObj_outPosition)



Thank you in advance for any reply!!


Leonardo Bruni

unread,
Jul 30, 2014, 7:42:46 AM7/30/14
to python_in...@googlegroups.com
Hi Enrico, 
actually you can access to all geometry data also from a MPxNode, maybe is also more easy.
In my opinion if you don't need build a deformer, is better use a node.
A rivet node, is also quite simple to do, you need just few geometry datas so, my suggestion it's go with a node =)
For access to all mesh functions you need get an handle to your MObject, in my case aInMesh:

[CODE]
# inputMesh
aInMesh = OpenMaya.MObject()
[/CODE]

For example this is my compute node:
[CODE]
# Node compute
    def compute(self, plug, data):
        thisNode = uberRivetNode.thisMObject(self)

        inputMeshPlug = OpenMaya.MPlug(thisNode, uberRivetNode.aInMesh)

        if not inputMeshPlug.isConnected():
            return OpenMaya.kNotImplemented

        ...
        ...

        # Get handle to inputMesh
        inputMesh = data.inputValue(uberRivetNode.aInMesh).asMesh()
[/CODE]

[CODE]
# Get handle to inputMesh
inputMesh = data.inputValue(uberRivetNode.aInMesh).asMesh()
[/CODE]

And after you can get mesh functions with:

[CODE]
fnMesh = OpenMaya.MFnMesh(inputMesh)
[/CODE]

If you have any questions, just ask

Cheers

Leonardo









--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/1e1a201e-452c-4f97-bb73-4abc120f5430%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Message has been deleted

Enrico Losavio

unread,
Jul 30, 2014, 1:42:59 PM7/30/14
to python_in...@googlegroups.com
Hey Leonardo.
Thanks a lot for the answer, it's been very useful.

So revised my code, but it's still not working. I mean... Maya handles the node smoothly, I can connect a mesh into the inMesh attribute, tweak the value of the positionOffset attribute, and even connect the values of the outPosition array to the translate channel of the locators. But still the locators all stay in the center of the scene (0,0,0) and if I modify the positionOffset or tweak the mesh, nothing happens: they stay there.
I also have an error (one for each connected locator, it doesn't show up if the locators are not connected..) :
" Error: RuntimeError: file S:\Maya_2015_DI\build\Release\runTime\Python\Lib\site-packages\maya\OpenMaya.py line 8174: (kInvalidParameter): Object is incompatible with this method "
 
Most likely my error is how I write the data into the array, but I don't know which is the right way to do it!
Here's a snippet of my code:

class AttachToMesh(OMMPx.MPxNode):

    a_inMesh
= OM.MObject()
    a_positionOffset
= OM.MObject()
    a_outPosition
= OM.MObject()

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

   
def compute(self,plug,dataBlock):

        thisNode
= AttachToMesh.thisMObject(self)
        inputMeshPlug
= OM.MPlug(thisNode, AttachToMesh.a_inMesh)

       
if not inputMeshPlug.isConnected():
           
return OM.kNotImplemented

       
if plug == AttachToMesh.a_outPosition:

           
# GET MESH DATA
            inMesh
= dataBlock.inputValue(AttachToMesh.a_inMesh).asMesh()
           
           
# GET POSITION OFFSET VALUE
            dataHandlePositionOffset
= dataBlock.inputValue(AttachToMesh.a_positionOffset)

            positionOffsetValue
= dataHandlePositionOffset.asFloat()

           
# OUT POSITION

            dataHandleArrayOutPosition
= dataBlock.outputArrayValue(AttachToMesh.a_outPosition)
            dataBuilderOutPosition
= dataHandleArrayOutPosition.builder()
           
            geoIterator
= OM.MItGeometry(inMesh)            
            mFnMesh
= OM.MFnMesh(inMesh)
            vertexPosition
= OM.MPoint()

            normals
= OM.MFloatVectorArray()
            mFnMesh
.getVertexNormals(False, normals)

           
while not geoIterator.isDone():

                vertexPosition
.x = geoIterator.position().x + ( normals[geoIterator.index()].x * positionOffsetValue )
                vertexPosition
.y = geoIterator.position().y + ( normals[geoIterator.index()].y * positionOffsetValue )
                vertexPosition
.z = geoIterator.position().z + ( normals[geoIterator.index()].z * positionOffsetValue )
               
                dataHandleOutPosition
= dataBuilderOutPosition.addElement(geoIterator.index())
                dataHandleOutPosition
.set3Double(vertexPosition.x, vertexPosition.y, vertexPosition.z)

                geoIterator
.next()
           
            dataHandleArrayOutPosition
.set(dataBuilderOutPosition)


            dataBlock
.setClean(plug)

       
else:
           
return OM.kUnknownParameter
   
def nodeInitializer():

    mFnTypedAttr
= OM.MFnTypedAttribute()
    mFnAttr
= OM.MFnNumericAttribute()

   
AttachToMesh.a_inMesh = mFnTypedAttr.create("inputMesh", "inMesh", OM.MFnData.kMesh)

   
AttachToMesh.a_positionOffset = mFnAttr.create("positionOffset", "posOff", OM.MFnNumericData.kFloat, 0.0)
    mFnAttr
.setKeyable(1)

   
AttachToMesh.a_outPosition = mFnAttr.create("outPosition", "outPos", OM.MFnNumericData.k3Double, 0.0)
    mFnAttr
.setKeyable(0)
    mFnAttr
.setWritable(0)
    mFnAttr
.setArray(1)
    mFnAttr
.setUsesArrayDataBuilder(1)

   
AttachToMesh.addAttribute(AttachToMesh.a_inMesh)
   
AttachToMesh.addAttribute(AttachToMesh.a_positionOffset)
   
AttachToMesh.addAttribute(AttachToMesh.a_outPosition)

   
AttachToMesh.attributeAffects(AttachToMesh.a_inMesh, AttachToMesh.a_outPosition)
   
AttachToMesh.attributeAffects(AttachToMesh.a_positionOffset, AttachToMesh.a_outPosition)


Thanks, folks!

PS... In Maya I piped a cubeShape's outMesh into my node's inMesh attribute. (Is it right? I guess so). And in the Hypergraph (Heat Map Display On) I see that my node is red, meaning that is making heavy calculations (0.05 s).
So far I kept the code as simple as possible! Does that mean that it will be super slow when I'll add more functionalities or use it with hi-poly meshes?

Enrico Losavio

unread,
Aug 1, 2014, 7:09:36 AM8/1/14
to python_in...@googlegroups.com
Well, I'll try to cut it to just one question.
I tried my best, and I think someway it should be working, but still the output array attribute is empty (all the values are set to the default value 0,0,0).
So I think there's something wrong in the way I fill up the output array attribute. I've been striving for days now, but I can't get out of this bog.
Can somebody please help me on how the MArrayDataBuilder works?
I shortened the code and bolded the questionable parts to make it more readable:
thank you guys!

class AttachToMesh(OMMPx.MPxNode):

   
def compute(self,plug,dataBlock):

       
if plug == AttachToMesh.a_outPosition:

            inMesh
= dataBlock.inputValue(AttachToMesh.a_inMesh).asMesh  ()
           
positionOffsetValue = dataBlock.inputValue(AttachToMesh.a_positionOffset).asFloat()


            dataHandleArrayOutPosition
= dataBlock.outputArrayValue(AttachToMesh.a_outPosition)
            dataBuilderOutPosition
= dataHandleArrayOutPosition.builder()

           
            geoIterator
= OM.MItGeometry(inMesh)            
            mFnMesh
= OM.MFnMesh(inMesh)
            vertexPosition
= OM.MPoint()

            normals
= OM.MFloatVectorArray()

            mFnMesh
.getVertexNormals(False, normals)

           
while not geoIterator.isDone():
                vertexPosition
.x = geoIterator.position().x + ( normals[geoIterator.index()].x * positionOffsetValue )
                vertexPosition
.y = geoIterator.position().y + ( normals[geoIterator.index()].y * positionOffsetValue )
                vertexPosition
.z = geoIterator.position().z + ( normals[geoIterator.index()].z * positionOffsetValue )
               
                dataHandleOutPosition = dataBuilderOutPosition.addElement(geoIterator.index())
                dataHandleOutPosition
.set3Double(vertexPosition.x, vertexPosition.y, vertexPosition.z)


                geoIterator
.next()
           
            dataHandleArrayOutPosition
.set(dataBuilderOutPosition)
            dataBlock
.setClean(plug)

   
def nodeInitializer():


   
AttachToMesh.a_inMesh = mFnTypedAttr.create("inputMesh", "inMesh", OM.MFnData.kMesh)


   
AttachToMesh.a_positionOffset = mFnAttr.create("positionOffset", "posOff", OM.MFnNumericData.kFloat, 0.0)

    AttachToMesh.a_outPosition = mFnAttr.create("outPosition", "outPos", OM.MFnNumericData.k3Double, 0.0)
    mFnAttr
.setKeyable(0)
    mFnAttr
.setWritable(0)
    mFnAttr
.setArray(1)
    mFnAttr
.setUsesArrayDataBuilder(1)

Rick Silliker

unread,
Aug 1, 2014, 10:19:34 PM8/1/14
to python_in...@googlegroups.com
Hello,

First time posting. This past week I also started to dive into the Python API more thoroughly, and started creating the very node I think you're trying to as well. You can find the code below, hopefully it helps. I know it's not perfect, but it's where I've gotten after a week of research on the subject.

import math
import maya.OpenMaya as om
import maya.OpenMayaMPx as ompx

class RivetNode(ompx.MPxNode):
#plugin id
kPluginNodeId = om.MTypeId(0x00000001)

#plugin attrs
targetMesh = om.MObject()
targetVert = om.MObject()
outTranslate = om.MObject()
outRotate = om.MObject()


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

def compute(self, plug, data):
"""check for connection of output before computing"""
'''
if plug != RivetNode.outMatrix:
return om.kUnknownParameter
'''
"""Get input plug data"""
inputMeshHandle = data.inputValue(RivetNode.targetMesh).asMesh()
inputVertIndex = data.inputValue(RivetNode.targetVert).asInt()

"""need to set incoming mesh as mesh, before it was just a handle"""
inMeshData = om.MFnMesh(inputMeshHandle)
inMeshVertData = om.MItMeshVertex(inputMeshHandle)

"""need a few spare instance objs"""
pp = om.MPoint()
pn = om.MVector()
pb = om.MPoint()
adjVerts = om.MIntArray()

"""get connected vert info"""
vertUtil = om.MScriptUtil()
vertUtil.createFromInt(0)
vertPtr = vertUtil.asIntPtr()
inMeshVertData.setIndex(inputVertIndex, vertPtr)
inMeshVertData.getConnectedVertices(adjVerts)

"""get the mesh data we need"""
pos = inMeshData.getPoint(inputVertIndex, pp, om.MSpace.kObject)
nor = inMeshData.getVertexNormal(inputVertIndex, True, pn, om.MSpace.kObject)
bi = inMeshData.getPoint(adjVerts[0], pb, om.MSpace.kObject)

"""need to convert to vectors"""
position = om.MVector(pp)
binormal = om.MVector(pb)

"""the math"""
a = binormal^pn
b = a^pn

"""manually making the matrix with a py list"""
listOfFloats = [pn.x, a.x, b.x, 0.0,
pn.y, a.y, b.y, 0.0,
pn.z, a.z, b.z, 0.0,
position.x, position.y, position.z, 1.0]

"""create matrix obj and add the values in"""
mm = om.MMatrix()
om.MScriptUtil.createMatrixFromList(listOfFloats, mm)

"""make transformation matrix to grab trans and rots"""
mt = om.MTransformationMatrix(mm)

translation = mt.getTranslation(om.MSpace.kWorld)

eulerRot = mt.eulerRotation()
rotation = [math.degrees(i) for i in (eulerRot.x, eulerRot.y, eulerRot.z)]

"""set values to output handles"""
outputTranslateHandle = data.outputValue(RivetNode.outTranslate)
outputTranslateHandle.set3Double(translation[0], translation[1], translation[2])
outputTranslateHandle.setClean()

outputRotateHandle = data.outputValue(RivetNode.outRotate)
outputRotateHandle.set3Double(rotation[0], rotation[1], rotation[2])
outputRotateHandle.setClean()


def creator():
return ompx.asMPxPtr(RivetNode())

def initialize():

"""output translate value attr"""
tAttr = om.MFnNumericAttribute()
RivetNode.outTranslate = tAttr.create('outputTranslate','outTranslate', om.MFnNumericData.k3Double)
tAttr.setWritable(False)
tAttr.setStorable(False)
RivetNode.addAttribute(RivetNode.outTranslate)

"""output rotate value attr"""
rAttr = om.MFnNumericAttribute()

RivetNode.outRotate = rAttr.create('outputRotate','outRotate', om.MFnNumericData.k3Double)
rAttr.setWritable(False)
rAttr.setStorable(False)
RivetNode.addAttribute(RivetNode.outRotate)

"""input mesh attr"""
tmAttr = om.MFnTypedAttribute()
RivetNode.targetMesh = tmAttr.create('targetMesh', 'tm', om.MFnData.kMesh)
RivetNode.addAttribute(RivetNode.targetMesh)
RivetNode.attributeAffects(RivetNode.targetMesh, RivetNode.outTranslate)
RivetNode.attributeAffects(RivetNode.targetMesh, RivetNode.outRotate)

"""vert index to find position/orientation attr"""
tvAttr = om.MFnNumericAttribute()

RivetNode.targetVert = tvAttr.create('targetVertex', 'tv', om.MFnNumericData.kInt)
RivetNode.addAttribute(RivetNode.targetVert)
RivetNode.attributeAffects(RivetNode.targetVert, RivetNode.outTranslate)
RivetNode.attributeAffects(RivetNode.targetVert, RivetNode.outRotate)

def initializePlugin(obj):
plugin = ompx.MFnPlugin(obj, 'Rick Silliker', '1.0', 'Any')
try:
plugin.registerNode('rivetNode', RivetNode.kPluginNodeId, creator, initialize)
except:
raise RuntimeError, 'Failed to register node'

def uninitializePlugin(obj):
plugin = ompx.MFnPlugin(obj)
try:
plugin.deregisterNode(RivetNode.kPluginNodeId)
except:
raise RuntimeError, 'Failed to register node'

Enrico Losavio

unread,
Aug 9, 2014, 7:41:13 AM8/9/14
to python_in...@googlegroups.com
Hey Rick! Nice job, and thank you for the hint. 
The node you wrote is similar to the one I'm trying to do, except that you output a single position (and rotation) attribute.
Instead, I'm completely bogged down on how to fill up an array of k3Double correctly... I've made my attempt but it doesn't work!
I really do not know where to turn. Can anyone land me a hand?

Many thanks
Reply all
Reply to author
Forward
0 new messages