blendShape Data

2,725 views
Skip to first unread message

Enrico Losavio

unread,
Jan 27, 2015, 5:57:03 PM1/27/15
to python_in...@googlegroups.com
Hey everyone!

I'm trying to improve the workflow here in my studio, and I'm fighting against blendShape nodes.
I want to access the target data after the mesh objects are deleted from the scene, but apparently Maya APIs don't allow me to do that.
It seems strange that such an important node doesn't have a good API support, but as stated in this article there's a bug (since at least ten years...)

I noticed that as long as the mesh object is connected to the blendShape node, the node is holding position data (thus, moving all the vertices to fit the exact shape of the target). But when targets are deleted, the BS node holds only the deltas (ie the difference between the target mesh and the original mesh at the moment of deletion).

So basically, when I use the MFnBlendShapeDeformer.getTargets() method, Maya either gives me the target object (when it is connected, from which I can retrieve vertices position using MFnMesh.getPoints()) or returns Null (when there's no object connected)

I mean.. we're talking about freaking blendShapes! There must be a way to just query the data I'm looking for without flipping out!

Do you have any advice on how to work this problem around? 

Thanks a lot

Enrico Losavio

unread,
Jan 28, 2015, 7:44:01 AM1/28/15
to python_in...@googlegroups.com
Quick update..

I'm trying to solve the problem remaining in the APIs context. Which means that I don't want to enable the blend targets one at a time and take the output mesh object.
Now I'm trying to access the data using MPlugs, but I'm probably querying the wrong attribute.
The thing is that usually the target shape is connected to the inputGeomTarget, but when it is deleted that attribute, sadly, remains empty. This means that the offset data I'm looking for is stored somewhere else in the node, and my goal is to catch this data as -i guess- an MPointArray or MVectorArray object.

So.. does anyone knows how a blendShape node works internally? Or what inputPointsTarget and inputComponentsTarget attributes are for?

Thanks again!

Enrico

33th...@gmail.com

unread,
Jan 28, 2015, 11:14:47 PM1/28/15
to python_in...@googlegroups.com
I don't have maya in front of me, and I'm certainly no expert. But if you can get the deltas from the BS node, then why not just multiply your base shape point position by the deltas? I suppose it's slightly more overhead for you, but maya storing the deltas instead of point positions on the BS node makes sense to me.

Kev

33th...@gmail.com

unread,
Jan 28, 2015, 11:35:41 PM1/28/15
to python_in...@googlegroups.com, 33th...@gmail.com
sorry, I just re-read your posts. I wasn't much help, lol.

Joe Weidenbach

unread,
Jan 29, 2015, 12:05:57 AM1/29/15
to python_in...@googlegroups.com
I, also, am no expert, but here's what I do know from writing a few
custom deformers.

The dependency graph is made of dependency nodes that are connected to
each other. When a node is used for input, it's data is cached
internally in the deformer node. This is functionality that's built
into MPxDeformer, and comes basically for free if you use the deform()
method to handle your deformations. That node (in this case a mesh)
only updates that cache data if it's output plug is marked dirty. If
the connection is broken, Maya just holds onto that cache data and keeps
using it as though the node was there. This is by design, and is meant
to enhance performance by only running calculations if data has
changed. I haven't worked with MPxDeformer enough to know if you get
access to that cache from inside (that is, if you wrote a custom
blendshape node and accessed the data internally), but I'm almost
positive that it's completely closed off from outside.

What you could do, conceivably, if you need to reconstruct the original
shapes is to iterate through the targets list in the blendshape node,
setting the weights to 1 one at a time, and then duplicating the mesh or
reading the point data you need.

Maybe that helps?

Joe

On 1/28/2015 8:35 PM, 33th...@gmail.com wrote:
> sorry, I just re-read your posts. I wasn't much help, lol.
>


---
This email is free from viruses and malware because avast! Antivirus protection is active.
http://www.avast.com

Enrico Losavio

unread,
Jan 29, 2015, 9:11:14 AM1/29/15
to python_in...@googlegroups.com
So.. thanks everyone for the replies.

I managed to acced the data I was looking for. It was just a bit hidden and not-so-easy to read.
Since I haven't found a solution for this problem on the internet yet, i post mine here.

Given that (as written in the blendShape node documentation)
  • inputGeomTarget ... stores the The target geometry input ... (type: geometry)
  • inputPointsTarget ... stores the Delta values for points in a target geometry ... (type: pointArray)
  • inputComponentsTarget ... stores the Component list for points in a target geometry ... (type: componentList)
and that maya APIs dont have any convenient method to access this data, I decided to used MPlugs to reach these attributes.

If the shape target object is still connected to the blend shape node, you can simply use the MFnBlendShapeDeformer.getTargets() method
If the shape target object has been deleted or disconnected, then the inputGeomTarget attribute will be empty, and you will have to find a way to combine the two remaining attributes.

The code below retrieve this data (in this case just for the first target in the bsNode) and prints it out. I guess that, properly combined together, this data gives you a lot of control over what's happening inside the node, especially when dealing with complex rigs!

Hope this helps!

ps. let me know if you have any comment or suggestion...

Enrico

import maya.cmds as cmds
import maya.OpenMaya as OM

# gets the blendShape node MObject

bsNodeName = 'blendShape1'
bsNodeObj = OM.MObject()
sel = OM.MSelectionList()
sel.add(bsNodeName, 0)
sel.getDependNode(0, bsNodeObj)

# gets the plug for the inputTargetItem[] compound attribute and ...

dgFn = OM.MFnDependencyNode(bsNodeObj)
plug = dgFn.findPlug('inputTarget').elementByPhysicalIndex(0).child(0).elementByPhysicalIndex(0).child(0).elementByPhysicalIndex(0)

# if connected, retrieves the input target object and queries his points

if plug.child(0).isConnected():
    inputGeomTarget = plug.child(0).asMObject()
    targetPoints = OM.MPointArray()
    fnMesh = OM.MFnMesh(inputGeomTarget)
    fnMesh.getPoints(targetPoints)

# if not connected, retrieves the deltas and the affected component list

else:
    inputPointsTarget = plug.child(1).asMObject()
    inputComponentsTarget = plug.child(2).asMObject()
    
    # to read the offset data, I had to use a MFnPointArrayData
    
    fnPoints = OM.MFnPointArrayData(inputPointsTarget)
    targetPoints = OM.MPointArray()
    fnPoints.copyTo(targetPoints)
    
    # read the component list data was the trickiest part, since I had to use MFnSingleIndexedComponent to extract (finally), an MIntArray
    # with the indices of the affected vertices
    
    componentList = OM.MFnComponentListData(inputComponentsTarget)[0]
    fnIndex = OM.MFnSingleIndexedComponent(componentList)
    targetIndices = OM.MIntArray()
    fnIndex.getElements(targetIndices)

    
    if targetIndices.length() == targetPoints.length():
        for i in range(targetPoints.length()):
            print "vertex %d has offset " %targetIndices[i], targetPoints[i].x,targetPoints[i].y, targetPoints[i].z

33th...@gmail.com

unread,
Jan 29, 2015, 12:34:49 PM1/29/15
to python_in...@googlegroups.com
good stuff, Nice to see you got things working. That's quite the plug to access, lol.

It'd be interesting to see, after you get the base shape point positions and multiply them by the deltas to get each shapes pp, if iterating through the shapes, turning them on one by one and getting the pp would be any faster.

Marcus Ottosson

unread,
Jan 29, 2015, 5:28:54 PM1/29/15
to python_in...@googlegroups.com
Why remove the shapes to begin with? Honest question, as I typically leave shapes connected or referenced. Is it a scene-size issue?



--
Marcus Ottosson
konstr...@gmail.com


Enrico Losavio

unread,
Feb 2, 2015, 6:54:14 AM2/2/15
to python_in...@googlegroups.com
Yeah, well. I'm not sure whether it's faster, but it's definitely more elegant, since you don't have to leave maya APIs.
I guess you could then rewrite the script in C++ as a command plugin to make it really really fast and efficient.
Still, having access to these deltas allows you to work with a wider range of data and opens up to new possibilities!

And well, deleting BS targets helps keeping the scene lighter (and possibly faster).
We are now working on a rig with several tens of targets, each one of which has something like one million polygons... Deleting the shapes freed up about 700mb!

Marcus Ottosson

unread,
Feb 2, 2015, 7:38:40 AM2/2/15
to python_in...@googlegroups.com

(and possibly faster)

Unless the shapes are also being manipulated during playback, I would be surprised if this were true, considering that the input would be deemed not-dirty regardless of anything being plugged into it.

Still, having access to these deltas allows you to work with a wider range of data and opens up to new possibilities!

On the contrary, I would say.

By removing the shapes to begin with you limit yourself greatly. Does a blendshape node even cache enough of the mesh to fully re-produce it? I’d imagine it only bothers to store pointpositions, but I could be mistaken.

We are now working on a rig with several tens of targets, each one of which has something like one million polygons… Deleting the shapes freed up about 700mb!

That’s a good reason, I’ve done the same in similar situations. What I found less destructive however was to reference the shapes - they can be for example Maya scenefiles, plain obj or alembic - that make the scenes even lighter as the blendShape node wouldn’t need to keep track of deltas either.

Out of curiosity, are you really rebuilding tens of shapes at millions of polygons each using Python?

Enrico Losavio

unread,
Mar 8, 2015, 3:56:38 PM3/8/15
to python_in...@googlegroups.com
Well, when targets are deleted Maya is not storing points position anymore, but only deltas of the affected vertices. That is: from now on, every change applied to the origShape (as long as vertex count and indexing remain untouched) will not be overridden by the blendShape node, but just added up. This makes the workflow incredibly smooth, since you don't need to remodel all the BS at this point. If you need to correct any of them, you just have to trigger it on, duplicate the mesh. sculpt the edits and plug it back into the bs node (and delete it ;) )

Plus, using my script i've noticed that often Maya (probably because of some floating-point, double-precision data related issue) saves a lot of junk deltas, whose value is approximable to zero. So I wrote another script to get rid of deltas with a length smaller than a certain threshold, and by doing that the file shrank by 100 mb more. Furthermore, the overall number of deltas removed is something around four millions, which definitely means a lot less computation for Maya.

So at the end of the day, my file is at least 800 mb smaller, the scene is faster, and i have the power to change my base mesh whenever i want without bothering too much about reshaping the targets.

And lastly, yes! I was trying to rebuild tens of shapes at millions of polygons each using Python. It is not the fastest way, but I wanted to be able to do it using the APIs only. Let's say it is part of a self-imposed learning plan!

This is my approach to the question, because i need this level of freedom. Referencing the targets is also another great way to keep you scene light, but I prefer to avoid referencing since i believe it is weak and dangerous. I know it is necessary when animating, but yet i would try to reduce it as much as possible (thus having no references before the rigging file)!

Enrico

rindo...@gmail.com

unread,
Dec 7, 2016, 1:13:41 PM12/7/16
to Python Programming for Autodesk Maya
I was wondering why you are not iterating over OM.MFnComponentListData(inputComponentsTarget) but just take the first element with componentList = OM.MFnComponentListData(inputComponentsTarget)[0]

For some reason in my case inputComponents is a collection of lists.

Enrico Losavio

unread,
Oct 27, 2018, 5:45:27 PM10/27/18
to Python Programming for Autodesk Maya
(two and a half years later)

I was wondering the same! 😅 

I haven't touched this code in a long time. When I finally got it to work I decided not to change, as there are a couple of things that I still don't fully understand and I don't wanna break it.
This is the typical case of "It works!... But why?!"

Today I was just looking back into it, cause I was investigating a small issue (which is actually specific to one file only, so maybe just a quirk of maya) where inputComponentsTarget and inputPointsTarget have different lengths, but the blendShape node still works fine... For most of the poses, the index list 

I was very surprised, cause a delta can't really be applied without knowing which vertex to affect. Maybe the indices are stored somewhere else? Maybe they're cached?


Now... I have also written some code to actually write my custom deltas in the blendShape node. Here is an excerpt.

index_list = OM.MIntArray()      # indices to be written
deltas_list = OM.MPointArray()   # deltas to be written (maya wants points instead of vectors)  ¯\_(ツ)_/¯

####################################################################################
### values for index_list and deltas_list are assigned outside this code snippet ###
####################################################################################

# initializes function sets, needed to create the MObjects and set their data
dg_component_fn = OM.MFnComponentListData()
dg_component_data = dg_component_fn.create()
singleComponent_fn = OM.MFnSingleIndexedComponent()
singleComponent_data = singleComponent_fn.create(OM.MFn.kMeshVertComponent)
singleComponent_fn.addElements(index_list)

# write the index data in the inputComponentsTarget plug
if not singleComponent_data.isNull():
    dg_component_fn.add(singleComponent_data)
    inputComponentsTarget_plug.setMObject(dg_component_data)

print dg_component_fn.length()      # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< this line prints 1 (in my test index_list and deltas_list had 11476 elements each)

# writes the delta data in the inputPointsTarget plug
dg_pointArray_fn = OM.MFnPointArrayData()
dg_pointArray_data = dg_pointArray_fn.create(deltas_list)
if not dg_pointArray_data.isNull():
    inputPointsTarget_plug.setMObject(dg_pointArray_data)

print dg_pointArray_fn.length()     # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< this line prints 11476 (correct!)



It has always worked fine in the last few years, and it still works correctly also with this file.
However, even after re-writing the blendShape targets in the node, the indices still don't have the right length.
With very few exceptions,the length of the indices in inputComponentsTarget is a very small value, between 1 and 5.


Here is an excerpt of the code to read the deltas from the blendShape node. 

inputPointsTarget_data = inputPointsTarget_plug.asMDataHandle().data()
inputComponentsTarget_data = inputComponentsTarget_plug.asMDataHandle().data()

# to read the deltas, use a MFnPointArrayData
targetPoints = OM.MPointArray()
fnPoints = OM.MFnPointArrayData(inputPointsTarget_data)
fnPoints.copyTo(targetPoints)

# use MFnSingleIndexedComponent to extract an MIntArray with the indices
dg_component_fn = OM.MFnComponentListData(inputComponentsTarget_data)[0]       # <<<<<<<<<<<< why does this need to be only the first element?
if dg_component_fn.isNull():
    continue
singleComponent_fn = OM.MFnSingleIndexedComponent(dg_component_fn)
targetIndices = OM.MIntArray()
singleComponent_fn.getElements(targetIndices)

print "indices: ", targetIndices.length(), "points: ", targetPoints.length()   # <<<<<<<<<<<< prints "indices: 1 points: 11476"



To be honest I am still a bit confused by some bits of this code. I think I understand the combined use of MFnComponentListData and MFnSingleIndexedComponent, but I'm not so familiar with these classes and the documentation is pretty scarce. Also google wasn't very helpful, as brought me back to this very thread that I started long time ago...

Since I noticed this issue on one file only, I don't know if you'll be able to replicate the same scenario.
However, maybe you can notice any anomaly in the way I'm handling this data to put it in / take it out the plug.

Can anyone shed some light on what's going on here?
Any help would be greatly appreciated!

Thanks,
Enrico

Michael Boon

unread,
Mar 13, 2020, 2:00:45 AM3/13/20
to Python Programming for Autodesk Maya
I have been working through this post and another by Remi, trying to figure out how to get blendShape target offsets in the OpenMaya API. It's not easy!
However I have figured out the answer to your question Enrico.

Your
 OM.MFnComponentListData(inputComponentsTarget_data)
stores all the components in groups of successive component IDs.

For example, if the components were [0, 1, 2, 3, 22, 23, 100, 101], it would have length 3.
OM.MFnComponentListData(inputComponentsTarget_data)[0]
would contain [0,1,2,3]
OM.MFnComponentListData(inputComponentsTarget_data)[1]
would contain [22,23]
and
OM.MFnComponentListData(inputComponentsTarget_data)[2]
would contain [100,101]
If you concatenate the contents of all those lists, you get the same number of components as you have points.
Reply all
Reply to author
Forward
0 new messages