Array Attribute on maya python api deformer node

1,942 views
Skip to first unread message

degne...@googlemail.com

unread,
Nov 19, 2017, 5:12:19 PM11/19/17
to Python Programming for Autodesk Maya
hi together,

I have been trying to write a custom deformer using the maya python api which basically is a shrink wrap deformer using a custom vector to project each vertex to the another mesh.

The deformer works fine as is. I really would like to add one option though which I cannot figure out how to achieve as I am new to the api and the logics of it in general. I would like to add an array attribute to the deformer node which for each input geometry vertex saves the index of another vertex which should be included into the calculation of the deform function.

My question is: How do I create the array attribute and also how do I populate and access it in the deform function?

I guess I have already somewhat figured out how to create the Array using setArray() function and also adding child attributes to it but I still have troubles understanding where and how to populate and access the array attribute. There is a lot of information out there concerning MPlugs and arrayDataBuilder but I simply have not been able to understand it. Can somebody provide me with an example or explain me how to properly do using the python api? Any help is greatly appreciated!

This is the code, that I was able put together with the examples available online:

import maya.OpenMaya as OpenMaya
import maya.OpenMayaAnim as OpenMayaAnim
import maya.OpenMayaMPx as OpenMayaMPx
import maya.cmds as cmds


class surfaceSlideDeformer(OpenMayaMPx.MPxDeformerNode):
kPluginNodeId = OpenMaya.MTypeId(0x00000012)
kPluginNodeTypeName = "surfaceSlideDeformer"

bindData = OpenMaya.MObject()
sampleWeights = OpenMaya.MObject()



def __init__(self):
OpenMayaMPx.MPxDeformerNode.__init__(self)
self.accelParameters = OpenMaya.MMeshIsectAccelParams() # speeds up intersect calculation


def deform(self, block, geoItr, matrix, index):

# get ENVELOPE
envelope = OpenMayaMPx.cvar.MPxGeometryFilter_envelope
envelopeHandle = block.inputValue(envelope)
envelopeVal = envelopeHandle.asFloat()

if envelopeVal!=0:
# get target MESH (as worldMesh)
targetHandle = block.inputValue(self.targetMesh)
inTargetMesh = targetHandle.asMesh()

if not inTargetMesh.isNull():

#get target fn mesh
inTargetFn = OpenMaya.MFnMesh(inTargetMesh)

inMesh = self.get_input_geom(block, index)

inMeshFn = OpenMaya.MFnMesh(inMesh)
inPointArray = OpenMaya.MPointArray()
inMeshFn.getPoints(inPointArray, OpenMaya.MSpace.kWorld)

# create array to store final points and set to correct length
length = inPointArray.length()
finalPositionArray = OpenMaya.MPointArray()
finalPositionArray.setLength(length)
finalPositionArray = inPointArray

# loop through all points
while not geoItr.isDone():
w = self.weightValue(block, index, geoItr.index())


point = inPointArray[geoItr.index()]

raySource = OpenMaya.MFloatPoint(point.x, point.y, point.z)
rayDirection = OpenMaya.MVector()
inMeshFn.getVertexNormal(geoItr.index(), False, rayDirection)
rayDirection = OpenMaya.MFloatVector(rayDirection.x, 0.0, rayDirection.z)

hitPoint = OpenMaya.MFloatPoint()

# rest of the args
hitFacePtr = OpenMaya.MScriptUtil().asIntPtr()
idsSorted = False
testBothDirections = True
faceIds = None
triIds = None
accelParams = self.accelParameters
hitRayParam = None
hitTriangle = None
hitBary1 = None
hitBary2 = None
maxParamPtr = 99999999

hit = inTargetFn.closestIntersection(raySource,
rayDirection,
faceIds,
triIds,
idsSorted,
OpenMaya.MSpace.kWorld,
maxParamPtr,
testBothDirections,
accelParams,
hitPoint,
hitRayParam,
hitFacePtr,
hitTriangle,
hitBary1,
hitBary2)

if hit:

finalPositionArray.set(OpenMaya.MPoint(point.x + envelopeVal*w*(hitPoint.x-point.x), point.y + envelopeVal*w*(hitPoint.y-point.y), point.z + envelopeVal*w*(hitPoint.z-point.z)), geoItr.index())
else:
finalPositionArray.set(point, geoItr.index())

geoItr.next()
inMeshFn.setPoints(finalPositionArray, OpenMaya.MSpace.kWorld)

def get_input_geom(self, block, index):
input_attr = OpenMayaMPx.cvar.MPxGeometryFilter_input
input_geom_attr = OpenMayaMPx.cvar.MPxGeometryFilter_inputGeom
input_handle = block.outputArrayValue(input_attr)
input_handle.jumpToElement(index)
input_geom_obj = input_handle.outputValue().child(input_geom_attr).asMesh()
return input_geom_obj

def creator():
return OpenMayaMPx.asMPxPtr(surfaceSlideDeformer())

def initialize():
gAttr = OpenMaya.MFnGenericAttribute()
mAttr = OpenMaya.MFnMatrixAttribute()
nAttr = OpenMaya.MFnNumericAttribute()
tAttr = OpenMaya.MFnTypedAttribute()
cAttr = OpenMaya.MFnCompoundAttribute()

outMesh = OpenMayaMPx.cvar.MPxGeometryFilter_outputGeom

surfaceSlideDeformer.targetMesh = gAttr.create("targetMesh", "target")
gAttr.addDataAccept(OpenMaya.MFnData.kMesh)
surfaceSlideDeformer.addAttribute(surfaceSlideDeformer.targetMesh)

surfaceSlideDeformer.sampleWeights = nAttr.create('sampleWeights','sampleWeights',OpenMaya.MFnNumericData.k2Int)
nAttr.setArray(True)

surfaceSlideDeformer.bindData = cAttr.create('bindData','bindData')
cAttr.setArray(True)
cAttr.setStorable(True)
cAttr.addChild(surfaceSlideDeformer.sampleWeights)

surfaceSlideDeformer.addAttribute(surfaceSlideDeformer.bindData)

surfaceSlideDeformer.attributeAffects(surfaceSlideDeformer.bindData, outMesh)
surfaceSlideDeformer.attributeAffects(surfaceSlideDeformer.targetMesh, outMesh)

cmds.makePaintable('surfaceSlideDeformer', 'weights', attrType='multiFloat', shapeMode='deformer')


def initializePlugin(obj):
plugin = OpenMayaMPx.MFnPlugin(obj, 'Degner', '1.0', 'Any')


try:
plugin.registerNode('surfaceSlideDeformer', surfaceSlideDeformer.kPluginNodeId, creator, initialize,
OpenMayaMPx.MPxNode.kDeformerNode)
except:
raise RuntimeError, 'Failed to register node'


def uninitializePlugin(obj):
plugin = OpenMayaMPx.MFnPlugin(obj)
try:
plugin.deregisterNode(surfaceSlideDeformer.kPluginNodeId)
except:
raise RuntimeError, 'Failed to deregister node'

Angelo

unread,
Nov 24, 2017, 4:07:03 PM11/24/17
to Python Programming for Autodesk Maya
def inititialize(self)
    self.aBindData = cAttr.create('bindData', 'bindData')
    cAttr.setArray(True)
def deform(self, data, itGeo, matrix, index):
    bindArrayData = data.inputArrayValue(self.aBindData)
    size = bindArrayData.elementCount()
    
    for i in range(size):
        bindData = bindArrayData.inputValue()
        someVariable = bindData.child(someAttribute).someDataType
        
        try:
            bindArrayData.next()
        except:
            pass

This is how i get data from a compound array attribute. I recommend using om.MFnTypedAttribute.MDoubleArray to store weight data since you can obtain all the data in one call which is faster. You only need to use MArrayDataBuilder when you need to add data on an existing array attribute (like outputting an array of matrices). If all you're doing is accessing an array, you can just iterate through them using either inputArrayData.next(), inputArrayData.jumpToLogicalElement() or inputArrayData.jumpToPhysicalElement() The docs for MArrayDataHandle and MArrayDataBuilder should describe this pretty well.

徐一雄

unread,
Jan 12, 2019, 5:05:01 AM1/12/19
to Python Programming for Autodesk Maya
Hello Angelo,
Thank you for your reply.
I have some weird effect when I use my custom jiggle deformer on two different meshes by only one deform node.
First, my custom deform node has some paintable attributes and all of them are the child of one MFnCompoundAttribute().
When I move one mesh, the other mesh or some components of the other mesh will perform jiggle effect. That is not what I want.
I think the problem is due to reading(storing) the float array attribute by different meshes.
So, my question is how to get paintable attributes (mainly MFloatArray()) of a MFnCompoundAttribute()?

Thank you very much.
Yixiong Xu

在 2017年11月25日星期六 UTC+8上午5:07:03,Angelo写道:

Tenghao Wang

unread,
Jan 13, 2019, 3:20:21 AM1/13/19
to python_in...@googlegroups.com
Hey Yixiong,

"When I move one mesh, the other mesh or some components of the other mesh will perform jiggle effect. That is not what I want."
I looked at your code a little bit, it looks like you commented this part: # MFnCompoundAttr.addChild(JiggleDeformerNode.worldMatrix)
You should understand "perGeometry" is an array compound attribute and stored information for each input mesh, so you need to have worldmatrix for each input geometry.

# access compound array attibute:
mGeometryHandle = dataBlock.inputArrayValue(perGeo)
# access to each geometry handle, looks like you already implemented "jump2Element" to access to each element in the compound array attribute.
jump2Element(mGeometryHandle, geoIndex)
mPerGeometryHandle = mGeometryHandle.inputValue()
worldMatrix = mPreGeomretyHandle.child(jiggleDeformerNode.worldMatrix)

# access to all the paintable maps
jiggleMap = _jiggleMap[geoIndex] 

if you are using Python: _jiggleMap is a list and each element is the jiggleMap list for each input mesh.
if you are using C++: _jiggleMap is map: std::map<unsigned int, MFloatArray> _jiggleMap

So basically,  the in the deform(self, dataBlock, geoIterator, local2WorldMatrix, geoIndex) function, you have to implement your jiggle algorithm for each input geometry by using "geoIndex".
Let me know if you have more questions.

Tenghao Wang
Sr. Technical Artist
Visual Concepts

--
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/a14eb246-950a-449d-81c2-b431848f8f83%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

徐一雄

unread,
Jan 13, 2019, 11:02:14 PM1/13/19
to Python Programming for Autodesk Maya
Hi Tenghao,
Thank you for your reply.
I mainly use python for programming.
I have changed my code.
As you said, I can get the world matrix by code:
worldMatrix = mPreGeomretyHandle.child(jiggleDeformerNode.worldMatrix).asMatrix()

For getting the jiggle map and other custom paintable map.
Why do you use following code?
jiggleMap = _jiggleMap[geoIndex] 

I don't find any variable named _jiggleMap.
Is it the maya inside type variable?
Can you explain it? Or is there any explaination in maya document?

In my opinion, I used to get jiggleMap by:
hJiggleMap = mPreGeomretyHandle.child(jiggleDeformerNode.jiggleMap)

jiggleMapArray = []
for i in range(geoIterator.count()):
      jump2Element(hJiggleMap, i)
      jiggleMapArray.append(hJiggleMap.inputValue().asFloat())

Looking forward to your reply.
Yixiong Xu
2019.01.14

在 2019年1月13日星期日 UTC+8下午4:20:21,Tenghao Wang写道:
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_maya+unsub...@googlegroups.com.

徐一雄

unread,
Jan 14, 2019, 9:36:48 PM1/14/19
to Python Programming for Autodesk Maya
Hi Tenghao,
The following code is the main code how I get Paintable map values:
# perGeometry
hGeo = dataBlock.inputArrayValue(JiggleDeformerNode.perGeo)

self.jump2Element(hGeo, geoIndex)
hGeo.jumpToElement(geoIndex)

hPerGeo = hGeo.inputValue()

hJiggleMap = om.MArrayDataHandle(hPerGeo.child(JiggleDeformerNode.jiggleMap))
hStiffMap = om.MArrayDataHandle(hPerGeo.child(JiggleDeformerNode.stiffMap))
hDampMap = om.MArrayDataHandle(hPerGeo.child(JiggleDeformerNode.dampMap))

matrix = hPerGeo.child(JiggleDeformerNode.worldMatrix).asMatrix()

jiggleMapArray = []
stiffMapArray = []
dampMapArray = []

# loop through the geoIterator.count() (i.e. mesh geometry) for getting the jiggleMap Value.
for i in range(geoIterator.count()):

      self.jump2Element(hJiggleMap, i)
      hJiggleMap.jumpToElement(i)
      jiggleMapArray.append(hJiggleMap.inputValue().asFloat())

      self.jump2Element(hDampMap, i)
      hDampMap.jumpToElement(i)
      dampMapArray.append(hDampMap.inputValue().asFloat())

      self.jump2Element(hStiffMap, i)
      hStiffMap.jumpToElement(i)
      stiffMapArray.append(hStiffMap.inputValue().asFloat())

And my jump2Element method is following:

def jump2Element(self, arrayHandle, index):

      if index not in xrange(arrayHandle.elementCount()):

      builder = arrayHandle.builder()

      builder.addElement(index)


I don't know how to get the  _jiggleMap or  _jiggleMap[geoIndex] as you said.
Can you help me on my code?
Thank you very much.
Yixiong Xu
2019.01.15
arrayHandle.set(builder)在 2017年11月25日星期六 UTC+8上午5:07:03,Angelo写道:

Tenghao Wang

unread,
Jan 14, 2019, 9:50:08 PM1/14/19
to python_in...@googlegroups.com
Hey Yixiong,

_jiggleMap is a user defined list. We need to save the jiggleMap weights for each input geometry.
hJiggleMap.inputValue().asFloat() only returns the weight for the current vertex ID on the current input geometry.
jiggleMapArray stores all the weight for the current input geometry but _jiggleMap will store all the jiggleMapArray for all the input geometry. 
So I get the jiggleMapArray by using _jiggleMap[geoIndex].

If you do not save this data, you will definitely see this issue: "When I move one mesh, the other mesh or some components of the other mesh will perform jiggle effect" Because your weights actually messed up.

To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

--
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/933be14a-0b60-4943-ade6-45e6ce44f111%40googlegroups.com.
Message has been deleted

徐一雄

unread,
Jan 28, 2019, 9:31:46 PM1/28/19
to Python Programming for Autodesk Maya
Hi Tenghao,
Thank you for your reply.
I figure it out and using dict() to solve the problem.
Thank you.
Yixiong Xu
2019.01.29

在 2019年1月15日星期二 UTC+8上午10:50:08,Tenghao Wang写道:
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_maya+unsub...@googlegroups.com.

--
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_maya+unsub...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages