API 1.0
import sys import math import maya.cmds as cmds import maya.OpenMaya as om import maya.OpenMayaMPx as mpx nodeName = 'myFirstDeformer' nodeID = om.MTypeId(0x55555) class MyDeformer(mpx.MPxDeformerNode): amplitudeAttr = om.MObject() displacementAttr = om.MObject() matrixAttr = om.MObject() def __init__(self): mpx.MPxDeformerNode.__init__(self) def deform(self, dataBlock, geomIterator, localToWorldMatrix, geomIndex): # step 1: access built-in attribute value using attribute name and attribute handle envelopeAttr = mpx.cvar.MPxGeometryFilter_envelope envelopeHandle = dataBlock.inputValue(envelopeAttr) envelopeValue = envelopeHandle.asFloat() # step 1.5: access custom attribute value amplitudeHandle = dataBlock.inputValue(MyDeformer.amplitudeAttr) amplitudeValue = amplitudeHandle.asFloat() displacementHandle = dataBlock.inputValue(MyDeformer.displacementAttr) displacementValue = displacementHandle.asFloat() # step 1.55: access custom attribute value connected to an accessory node matrixHandle = dataBlock.inputValue(MyDeformer.matrixAttr) matrixValue = matrixHandle.asMatrix() transMatrix = om.MTransformationMatrix(matrixValue) # matrix type translateValue = transMatrix.getTranslation(om.MSpace.kObject) # vector type # step 2: access input mesh inputMesh = self.getDeformerInputGeom(dataBlock, geomIndex) # step 2.5: access mesh normals meshFn = om.MFnMesh(inputMesh) normalVectorArray = om.MFloatVectorArray() # create float vector array to store normal vector meshFn.getVertexNormals(False, normalVectorArray, om.MSpace.kObject) # (average normal or not?, the array to store, normal space) # step 3: iterate the mesh vertices and deform it newVertexPosArray = om.MPointArray() # to store new vertices position while not geomIterator.isDone(): vertexPos = geomIterator.position() vertexIndex = geomIterator.index() normalVector = om.MVector(normalVectorArray[vertexIndex]) # get weight from weightPainting and multiply it to the deform, this is done inside # built-in function weightValue(dataBlock, geomIndex, vertexIndex) weight = self.weightValue(dataBlock, geomIndex, vertexIndex) vertexPos.x = vertexPos.x + math.sin(vertexIndex + displacementValue + translateValue[0]) * normalVector.x * amplitudeValue * envelopeValue * weight vertexPos.y = vertexPos.y + math.sin(vertexIndex + displacementValue + translateValue[0]) * normalVector.y * amplitudeValue * envelopeValue * weight vertexPos.z = vertexPos.z + math.sin(vertexIndex + displacementValue + translateValue[0]) * normalVector.z * amplitudeValue * envelopeValue * weight newVertexPosArray.append(vertexPos) geomIterator.next() geomIterator.setAllPositions(newVertexPosArray) # override built-in function that allows to create accessory node along with deformer def accessoryNodeSetup(self, dagModifier): # step1: create the accessory node using the supplied dagModifier locator = dagModifier.createNode('locator') # step2: access accessory node's attribute(can't use mplug type, has to be mobject type) # access dependency node function set dependNodeFn = om.MFnDependencyNode(locator) matrixPlug = dependNodeFn.findPlug('worldMatrix') # this returns mplug type attribute, we need mobject type attribute matrixAttr = matrixPlug.attribute() # step3: connect mobject type(required) together # param: accessory node(mobject), accessory attr(mobject), deformer node(mobject: using self.thisMObject()), deformer attr(mobject) mConnectStatus = dagModifier.connect(locator, matrixAttr, self.thisMObject(), MyDeformer.matrixAttr) '''now the accessory node's worldMatrix is driving to the in-matrix of the deformer node''' return mConnectStatus def accessoryAttribute(self): # returns the deformer node attribute connected return MyDeformer.matrixAttr def getDeformerInputGeom(self, dataBlock, geomIndex): inputAttr = mpx.cvar.MPxGeometryFilter_input inputHandle = dataBlock.outputArrayValue(inputAttr) # use outputArray instead of inputArray to avoid re-computation inputHandle.jumpToElement(geomIndex) inputElementHandle = inputHandle.outputValue() inputGeomAttr = mpx.cvar.MPxGeometryFilter_inputGeom inputGeomHandle = inputElementHandle.child(inputGeomAttr) # this is different from how we usually get handler inputGeomMesh = inputGeomHandle.asMesh() return inputGeomMesh def nodeCreator(): # return pointer to instance of our deformer node class return mpx.asMPxPtr(MyDeformer()) def nodeInitializer(): # step1: define attribute function set (numericAttr & matrixAttr) numericAttrFn = om.MFnNumericAttribute() matrixAttrFn = om.MFnMatrixAttribute() # step2: create custom attribute and set its property MyDeformer.amplitudeAttr = numericAttrFn.create('ampplitude', 'amp', om.MFnNumericData.kFloat, 0.0) numericAttrFn.setMin(-1.0) numericAttrFn.setMax(1.0) numericAttrFn.setReadable(False) MyDeformer.displacementAttr = numericAttrFn.create('displacement', 'dis', om.MFnNumericData.kFloat, 0.0) numericAttrFn.setMin(-5.0) numericAttrFn.setMax(5.0) numericAttrFn.setReadable(False) MyDeformer.matrixAttr = matrixAttrFn.create('matrix', 'm') matrixAttrFn.setStorable(False) matrixAttrFn.setConnectable(True) # step2.5: access built-in attribute using OpenMayaMpx.cvar.MPxGeometryFilter_outputGeom outputGeom = mpx.cvar.MPxGeometryFilter_outputGeom # step3: attach attribute MyDeformer.addAttribute(MyDeformer.amplitudeAttr) MyDeformer.addAttribute(MyDeformer.displacementAttr) MyDeformer.addAttribute(MyDeformer.matrixAttr) # step4: add circuit (relationship) MyDeformer.attributeAffects(MyDeformer.amplitudeAttr, outputGeom) MyDeformer.attributeAffects(MyDeformer.displacementAttr, outputGeom) MyDeformer.attributeAffects(MyDeformer.matrixAttr, outputGeom) cmds.makePaintable(nodeName, 'weights', attrType='multiFloat', shapeMode='deformer') def initializePlugin(mobject): mplugin = mpx.MFnPlugin(mobject) try: # Parameter: node_name, node_id, node_creatorFunc, node_initFuc, nodeType(common type include-kDeformerNode, kDependNode) mplugin.registerNode(nodeName, nodeID, nodeCreator, nodeInitializer, mpx.MPxNode.kDeformerNode) except: sys.stderr('fail to register node: ' + nodeName) raise def uninitializePlugin(mobject): mplugin = mpx.MFnPlugin(mobject) try: mplugin.deregisterNode(nodeID) except: sys.stderr('fail to de-register node: ' + nodeName) raiseIn the last chapter, we know how to create custom numeric type attribute using MFnNumericAttribute. Sometimes in our node, we want to access existing built-in attribute. We do so by using OpenMayaMPx.cvar.MPxDeformerNode_(attributeName) before Maya 2016, we use OpenMayaMPx.cvar.MPxGeometryFilter_(attributeName) after 2016.
In the sample code, we define our custom function getDeformerInputGeom(self, dataBlock, geomIndex) to obtain the input mesh to the deformer node. We will discuss this later.
Accessory node acts like a secondary driver node connected to our deformer so they can influence the deformation. In the sample code, our accessory node is a locator which when we connects its world matrix, it will change our mesh’s deformation when translating.
Registration: In our previous chapter, we register our node using registerNode() with node type: omMPx.MPxNode.kDependNode, in deformer node, we use omMPx.MPxNode.kDeformerNode as our node type.
Inheritence: We now inherit our class from omMPx.MPxDeformerNode instead of omMPxNode there’s still compute() in MPxDeformerNode class, but we want to write our deformation algorithm in deform().
Accessory Node: accessoryNodeSetup(self, dagModifier) and accessoryAttribute(self) is overrided to allow us to control accessory node along with our deformer.
Node Creator
def nodeCreator(): return mpx.asMPxPtr(MyDeformer())Only API 1.0 is available.
Node Initializer
def nodeInitializer(): '''1: create reference to numericAttribute and matrixAttribute function sets''' numericAttrFn = om.MFnNumericAttribute() matrixAttrFn = om.MFnMatrixAttribute() ''' 2: create attribute using the function set''' MyDeformer.inNumAttr = numericAttrFn.create('num', 'n', om.MFnNumericData.kFloat, 0.0) numericAttrFn.setMin(-1.0) numericAttrFn.setMax(1.0) numericAttrFn.setReadable(False) MyDeformer.inMatAttr = numericAttrFn.create('matrix', 'm') matrixAttrFn.setStorable(False) matrixAttrFn.setConnectable(True) ''' 2.5: access built-in attribute using OpenMayaMpx.cvar.MPxGeometryFilter_outputGeom''' outputGeom = mpx.cvar.MPxGeometryFilter_outputGeom ''' 3: attach attribute''' MyDeformer.addAttribute(MyDeformer.inNumAttr) MyDeformer.addAttribute(MyDeformer.inMatAttr) ''' 4: add circuit (relationship in->out)''' MyDeformer.attributeAffects(MyDeformer.inNumAttr, ouputGeom) MyDeformer.attributeAffects(MyDeformer.inMatAttr, ouputGeom) ''' 5: make attribute paintable''' cmds.makePaintable(nodeName, 'weights', attrType='multiFloat', shapeMode='deformer')we access the output Geometry attribute so we can later add relationship to it.
RegisterNode
mplugin.registerNode(nodeName, nodeID, nodeCreator, nodeInitializer, om.MPxNode.kDeformNode)De-registerNode
mplugin.deregisterNode(nodeID)–To access a value from an attribute, we use handle = dataBlock.input/outputValue(MyNode.attr)
if we have a custom attribute ‘inNumAttr’: inNumHandle = dataBlock.inputValue(MyDeformer.inNumAttr) inNumValue = inNumHandle.asFloat() if we have a built-in attribte ‘envelope’: # we first get our attribute name ‘envelope’ envelopeAttr = mpx.cvar.MPxGeometryFilter_envelope envelopeHandle = dataBlock.inputValue(envelopeAttr) envelopeValue = envelopeHandle.asFloat()
–To get normal for individual vertices on our input mesh, we first need to obtain our input mesh using our own function: getDeformerInputGeom(self, dataBlock, geomIndex). And using mesh function set MeshFn’s getVertexNormals() we store the normal vector in om.MFloatVectorArray() type array.
–To deform our mesh: we use the geometry iterator to perform iteration on each mesh vertex and re-calculate its position. We combine the use of geoIterator.position() and geomIterator.setPosition(point) or geomIterator.setAllPositions(pointArray).
– To access weight value on each vertex, we use built-in function weightValue(dataBlock, geomIndex, vertexIndex). In which, geomIndex is provided in deform() and vertexIndex is from geomIterator.
At this point, I can’t fully interpret the meaning of this segment.
The dagModifer is supplied in the accessory node. We use dagModifier’s connect function to connect the accessory node’s attribute to our deformer node’s attribute. In this case, we have accessory’s attribute: worldMatrix (a built-in attribute obtained from MFnDependencyNode.findPlug()) and our custom defined MyDeformer.inMatAttr.
One thing to note is that, the .connect() only takes MObject which we cannot supply MPlug type object matrixPlug = …findPlug(‘attributeName’), we perform additional step matrixAttr = matrixPlug.attribute() to get the MObject type attribute.
Now we supply .connect() with parameters: accessory node (MObject type), accessory node’s attribute (MObject type), deformer node (MObject type) and deformer node’s attribute (MObject type) as follow:mConnectStatus = dagModifier.connect(locator, matrixAttr, self.thisMObject(), MyDeformer.inMatAttr)