Monday, July 2, 2012

Some "Mind Glitches" about MFnSkinCluster and MPlug...

It's been a while since anything appeared here, but that is due mainly because in the last half a year I have quite seriously moved more and more towards the technical side of life in 3D, and so there weren't so many updates to show for drawing...

However, I now intend to write a little on what I am finding now that I am being more involved in the tools and plugin dev at my new job at Bron Studios.

I have lately been digging more and more into different areas of Maya's API, and there are certain things I am sure I am bound to forget and will need again some day (or perhaps someone else will be looking for the explanations, and maybe these will shed light on what might be going on).

My current endeavor is into creating a nice and solid vertex based skin saving/loading and transfer. As I was looking for different approaches to get the skin data out of a skinCluster, I obviously started down the path of using the MFnSkinCluster. However, I quickly discovered that the information that I get there is rather cumbersome as I need to actually do a full mesh component iteration on an objects vertices to get the weighting data. That is hardly a fast process, and so I tried looking for other possible solutions. I knew that there is ways of getting the data from the skinCluster via the regular Maya Python interface through the skinCluster attributes "weightList" and it's "weights" attributes. The way this attribute stores data is in a compound attribute which is basically a nested list. For each vertex index in the mesh there is a "weightList" entry associated with that index that stores the weights for that vertex. It can be accessed with something like this:

import maya.cmds as m

obj = 'skinCluster2'

items = m.getAttr (obj + '.weightList', mi = True)
for i in items:
    print i
    print m.getAttr (obj + '.weightList[%s].weights'%i)

This will return to you the index of the vert being parsed, as well as a list containing it's weights. However, the problem I encountered here is that to figure out which verts contain which influence objects, you need to do a fair amount of sorting: you will need to loop through the influences, get the verts that each influence affects, then sort them to group each verts' influences to form a coherent table. To me, this is as cumbersome as the MFnSkinCluster, and is also slower, as we aren't parsing through the API.

As I looked around, I sumbled on a wonderful page by Tyler Thornock that utilizes both the API and the attribute parsing to get the skin weights. But as I was going through his method, I started figuring out what each line did, why, and how (as I always tend to). You can find his code HERE. The following line is what got me thinking:
wPlug.selectAncestorLogicalIndex(vId, wlAttr)

I couldn't figure out what this "selectAncestorLogicalIndex" was for. But after digging around I found that this basically set a compound attribute to display a specific part of it's data. In other words, when Tyler was getting plug data from the "weightList" of the skinCluster, and then the "weights" (the wPlug), he needed to set the weights plug to display information for a specific weightList index. Since the wPlug contained all of the info for all of the weightLists, you can't just go through it as any other list. And so the "selectAncestoralLogicalIndex" is used to specify which part of the wPlug to parse.

To show it a little more practically, when MPlugs for the attributes were created:

wlPlug = skinFn.findPlug('weightList')

wPlug = skinFn.findPlug('weights')

...if you would try to check the amount of elements in wPlug with "wPlug.numElements()" Maya would actually crash. However, as soon as you set the LogicalIndex to be one of the verts, and tried to get that again you'd get a result of the amount of influences that vertex is affected by:

wlPlug = skinFn.findPlug('weightList')

wPlug = skinFn.findPlug('weights')

wlAttr = wlPlug.attribute()

wPlug.selectAncestorLogicalIndex(0, wlAttr)

print wPlug.numElements()

This is it for now, I am going to dig a little further, but hopefully after writing this, I will not forget how it works!