Saturday, August 1, 2015
Custom MPxCommand
I built a command that searches the dependencyGraph for a given node type. You specify the node type to search for and the direction to search in. YOu then call it just as though it is another maya command. So rad as this can now be built into my code as if it were just part of maya.
So my command is: findConnectedNodeType. You call it like:
node = cmds.findConnectedNodeType('blendColors','up')
The return value is either False or the found node name. The command looks through the upstream dependencyGraph until the first instance of that node is found. It then stops the iteration and returns the node name.
Cool stuff I learnt.
To return from a MPxCommand you must use the public function of MPxCommand setResult.
self.setResult(return value goes here)
This comes at the end of the doIt() function.
Enumerators are also making a bit more sense now that I am using them more. For example I had to use MItDependencyGraph. You can specify the direction to search, up or down stream. The help docs show this for the direction.
MItDependencyGraph ( MObject & rootNode,
MPlug & rootPlug,
MIteratorType & infoObject,
Direction direction = kDownstream,
Traversal traversal = kDepthFirst,
Level level = kNodeLevel,
MStatus * ReturnStatus = NULL
)
and this:
enum Direction
Direction within the DG relative to root Node or Plug.
A bit cryptic at first if you arent familiar with how to read this. So To insert this correctly into the MItDependencyGraph class call you need to do this.
OpenMaya.MItDependencyGraph(OpenMaya.MItDependencyGraph.kDownstream)
So because this enumerator is a child of the class MItDependencyGraph , it must be specified in context of this class hierarchy. (OpenMaya.MItDependencyGraph). You can then set any number of the enum options that the help displays, in this case kDownstream or kUpstream and the class call will know to look for that enum if you set it (you don't have to specify anything and maya will use the default)
Ok. Hope that is a bit clearer than mud. Till next time.
Thursday, July 30, 2015
Stretch Node Maya API Plugin Comparisons
Stretch Node Python API Plugin demo from Tim Forbes on Vimeo.
Test stretch node plugin using matrix connections rather than channel connections. This video compares two test plugins briefly. One plugin using Matrix connections, the other using Transform channel connections. This is a test purely to look at different types of data handling and how I can use Mayas API to simplify node graphs for common rigging techniques.
Wednesday, July 29, 2015
Maya API Matrix Research
Build a locator. Move it somewhere other than the origin (origin is fine but the data would just read 0.0,0.0,0.0. I wanted something in there as it makes it easier to verify that the data is returning correctly.
I then started writing some API code that would:
#get the active selection
#loop through selected nodes and for each node
#find the plug for the matrix attribute
#get that plug as matrix data
#extract the transformation data
#print the x,y and z coordinates.
I chose this test as that is exactly what is going on in my stretch node. In the node the .matrix attr is connected into the node, and from that plug I try to retrieve the x,y and z position. However it is returning incorrectly.
So the code in my Maya test scene is working, so the error must be in the creation of the node attribute, or the way in which I extract the data from the data block. Anyways, here is the code I used to the the t.x,t.y and t.z of a selected object from the matrix plug. I hope you find it useful.
import maya.OpenMaya as OpenMaya
dagPathFn = OpenMaya.MDagPath()
#get active selection
mSelList = OpenMaya.MSelectionList()
OpenMaya.MGlobal.getActiveSelectionList(mSelList)
selItr = OpenMaya.MItSelectionList(mSelList)
#iterate through selection and get the world space positon from the matrix plug
while not selItr.isDone():
selItr.getDagPath(dagPathFn)
nObj = dagPathFn.node()
#we got the node from the dagPathFn#now we get the dependency node
objDepNodeFn = OpenMaya.MFnDependencyNode(nObj)
#find the .matrix plug
mxPlug = objDepNodeFn.findPlug('matrix')
print mxPlug.name()
print objDepNodeFn.name()
#get plug as MObject which we will attach MFnMatrixData to.
mxObj = mxPlug.asMObject()
mxDataFn = OpenMaya.MFnMatrixData(mxObj)
#query the transformation, returns MTransformationMatrix
trfnMX = mxDataFn.transformation()
#get the translate in world space
v = trfnMX.getTranslation(OpenMaya.MSpace.kWorld)
#print it out!
print v.x,v.y,v.z
selItr.next()
Positon From Matrix
startMXDataH = block.inputValue(stretchNodeMX.aStartMX).asMatrix()
Now this was not playing nce with me. As I went further down the track I was not getting the correct information returned.
So here's what I have..I'll delve deeper tomorrow...
def compute(self,plug,block):
#method 1
#get matrix and translate from finding the dependency node and working from there
#returns the correct t.x,t.y and t.z
#-12.0540640814 5.6978884254 7.7806477623
sNode = self.thisMObject()
plugArray = OpenMaya.MPlugArray()
depNodeFn = OpenMaya.MFnDependencyNode(sNode)
startPlug = depNodeFn.findPlug(stretchNodeMX.aStartMX)
startPlug.connectedTo(plugArray,True,False)
stObjMx = OpenMaya.MMatrix()
if plugArray.length()>0:
for i in range(0,plugArray.length()):
stObj = plugArray[i].node()
stDagNodeFn = OpenMaya.MFnDagNode(stObj)
stObjMx = stDagNodeFn.transformationMatrix()
stP = OpenMaya.MTransformationMatrix(stObjMx)
vec = stP.getTranslation(OpenMaya.MSpace.kWorld)
print vec.x,vec.y,vec.z
#method 2
#get the matrix data from the input plug and work from there
#returns the incorrrect t.x,t.y and t.z
#5.26354424712e-315 0.0 0.0078125
startMXDataH = block.inputValue(stretchNodeMX.aStartMX)
startMX = startMXDataH.asMatrix()
#just print the "translate" parts of the matrix - does not return what I'd expect.
print startMX(0,0),startMX(0,1),startMX(0,2)
#method 3
#returns the incorrrect t.x,t.y and t.z
#0.0 5.26354424712e-315 0.0
#get the transformation matrix to query the translation from there. Once again it returns the incorrect imformation
mxData = OpenMaya.MFnMatrixData()
mxData.create(startMX)
stPTransformationMatrix = mxData.transformation()
stPTranslation = stPTransformationMatrix.getTranslation(OpenMaya.MSpace.kWorld)
print stPTranslation.x,stPTranslation.y,stPTranslation.z
block.setClean(plug)
Obviously something's going screwy with my coding here, and my use of the API. But anyways, it's all about digging through it and working out whats wrong...to be continued tomorrow. Something's going wrong with either my creation of the matrix attribute and the kind of data there..but I'm pretty sure thats good. So it must be in the way I am querying the data from the dataBlock...hmmm.
TBC...
Tuesday, July 28, 2015
Stretch Node
The node is really very easy to write, and the longest part of it is actually setting up the attributes on the node and how they affect each other. Once you have a template for a MPxNode you can just copy and paste it as a starting point. However it is always nice to write this out and make sure you understand it rather than just copying and pasting it.
So this stretch node has 3 inputs. the startPoint and endPoint, as well as a length attribute which the user sets to the length at which stretching will start. There are then two outputs. Distance, which just returns the distance between the two in points at any given time (useful for determining the point at which stretching will occur), and the main output which outputs the final stretch value which will generally get piped into the scaleX of the necessary joints.
What awesome things did I learn when writing this plugin. The attribute that enables MPoint to return the distance between two points.
posA = OpenMaya.MVector()
posB = OpenMaya.MVector()
p0 = OpenMaya.MPoint(posA)
p1 = OpenMaya.MPoint(posB)
distance = p0.distanceTo(p1)#returns double
Next awesome thing.In the initialize() function we call this:
plugin = OpenMayaMPx.MFnPlugin(obj,'Tim Forbes','0.0','Any')
We then register the plugin type. Now this is different depending on what plugin you are creating, and will require different input data. Somewhere to get caught out, especially if you copy your template from a OpenMayaMPx.MPxCommand type plugin...like I did :p
The differences here are below:
OpenMayaMPx.MPxCommand will be plugin.registerCommand('command name', creator)
The command only requires the command name and the creator function, however the node requires a few extra things as seen below.
OpenMayaMPx.MPxCommand will be plugin.registerNode('stretchNode', stretchNode.kPluginNodeId,creator,initialize)
Because we are creating a node that will be connected to the dependency graph we need a unique node ID, we need the node name, the creator function and also the initialize function. The initialize function is where all the node attributes are added. The creator function is where our pointer is created to access the plugin within maya.
The unique Node Id is created using the inbuilt maya function: kPluginNodeId = OpenMaya.MTypeId(0x00000882)
For home use I have generated an arbitrary ID, however in production you would want to make sure you used an ID that would not clash with in house node IDs.Currently I built the node to take on the translates of the two input points. This was for ease of building and proof of concept. My next version will take wither the world matrix or transforms, thus making the node more adaptable to different rigging scenarios. Anyway, I built this node into a rig and you can view it working below.
Stretch Node from Tim Forbes on Vimeo.
Maya Python API Stretch Node
Next Up. Add in matrix connections to the StretchNode. Following that I'm going to make a MPxCommand plugin that traverses the dependency graph to find connected skin clusters. This will be useful in a convoluted node graph if you need to manupulate the skin cluster in a specific way, or really any specific node I guess:-)
Saturday, July 25, 2015
Creature TD Reel
Tim Forbes Creature TD Reel 2015 from Tim Forbes on Vimeo.
My latest Creature TD reel with my favorite work. Latest additions to the reel are Chappie and TMNT. I hope to add some more updated personal work soon.
Big props to all the crew at Image Engine for the amazing work.
Music By Arcade Fire. "Sprawl 1" from album "The Suburbs".