salvaom
I've come across this problem and I thought somebody here would be able to help me. I'm doing a little script for getting/setting the weights of a blendShape node in Maya, and the first thing I noticed is that is all stored in a MPlug that is an array, but the interesting thing is that that array is empty until each single value on the array is queried or setted:

bs.png 

I have the codes for getting and setting the weights per-vertex, but for some reason, setting the weights is really slow (9min/40.000vtx), just slightly faster than using maya.cmds.setAttr. I was wondering if I'm doing something wrong on my code or if there is a faster way of approaching this. This is my code for getting:


Code:

def getBaseWeights(self):
    ''' Returns the base weights
    '''
    time = dt.now()
    if not self.api:
        self.init_api()
    basePlug = self.getBaseWeightPlug()
    baseWeights = []
    for i in xrange(self.vertexCount()):
        w = basePlug.elementByLogicalIndex(i)
        weight = [i, w.asFloat()]
        baseWeights.append(weight)
    print '@getBaseWeights took %s' % (dt.now() - time)
    return baseWeights


and this one for setting:

Code:

def setBaseWeights(self, weights, default=1, override=False):
    ''' Sets the baseWeights of a blendshape node.
 
    :param weights: An array with the weights to be set. The format must be 
     in the following form: [[vertex_number, value], ...]
    :type weights: list
    
    :param default: unused
    
    :param override: Overrides all weights with this value if not False
    :type override: (float, int)
 
    '''
    time = dt.now()
    if not self.api:
        self.init_api()
 
    basePlug = self.getBaseWeightPlug()
 
    for vNum, weight in weights:
        if vNum > self.vertexCount():
            continue
        if override and not isinstance(override, (float, int)):
            weight = float(override)
        basePlug.elementByLogicalIndex(vNum).setFloat(weight)
 
    print '@setBaseWeights took %s' % (dt.now() - time)



Is there any more agile way to accomplish this?

Thank you in advance!
Quote 0 0
pshipkov

It should not be that slow. It is instant to read/write data for 40000 points here.
There are couple of problems in your code. Here is how i would do it:

Code:

p = om.MPlug()
sl = om.MSelectionList()
sl.add("blendShape1.inputTarget[0].baseWeights")
sl.getPlug(0, p)
ids = om.MIntArray()
p.getExistingArrayAttributeIndices(ids)
count = ids.length()
weights = [0.0]*count

# read
for i in range(count): weights[i] = p.elementByPhysicalIndex(i).asFloat()

# write
for i in range(count): p.elementByLogicalIndex(ids[i]).setFloat(weights[i])


Few things to pay attention to:
- i stuff all the logical ids in integer array and then iterate on them below in the read loop. You are not doing that and for sparse array your code will fail.
- the "read" loop goes through physical ids. This is faster than iterating on the logical ones since it does not perform binary search every time array element is accessed.
- in the "write" loop things go by logical ids to make sure the right elements are modified. You probably want to delete all existing elements before running the "write" loop in order to make sure only with the values you are setting.
- notice that i am not storing data in dictionary (or how you did it - in nested lists). I noticed that many people tend to do that out of habit. That often has significant performance penalty. I think that why things are so slow in your code. Even with the new dictionary::viewkeys() method available in Python 2.7+ the CPU is still doing extra work at the end to associate keys and values and this is slow.

Hope these few notes help.

Quote 0 0
pshipkov
One last thing - xrange is deprecated in newer Python versions - that's something to consider if your code is going to live long enough :)
Quote 0 0
salvaom
First of all, thank you very much for the help with this matter, I'm starting to learn API now by myself and I'm stumbling into some complications from time to time.
 
Regarding the matter of using the vertexCount as the base of my script, there is a reason for that. I have been unable to query all the weights of the baseWeights MPlug, because until it's painted with the Artisan tool or modified in any way, that array is empty. You can try to run this code to replicate my problem:
 
Code:

import maya.OpenMaya as om
import maya.cmds as cmds
 
pS1 = cmds.polySphere()
pS2 = cmds.polySphere()
bs = cmds.blendShape(pS1[0], pS2[0], name='bs')
 
p = om.MPlug()
sl = om.MSelectionList()
sl.add("%s.inputTarget[0].baseWeights" % bs[0])
sl.getPlug(0, p)
ids = om.MIntArray()
p.getExistingArrayAttributeIndices(ids)
count = ids.length()
# Result: [] # 
 
weights = [0.0]*count
# Result: [] # 
 
for i in range(count):
    weights[i] = p.elementByPhysicalIndex(i).asFloat()
 
print weights
# Result: [] #
 

So I can't iterate through  the elements because the array is empty. If I flood the blendShape with any value, all those appear in the array.

For setting, I have run your piece of code and it does work much much faster, thanks a lot. Also, if it's not very complicated, I would love to know the difference between the logical and the physical index.
 
Plus, the reason I was appending the index with the weight inside the variable is because I am not sure if vertices in Maya can skip numbers ([1, 2, 5, ...]) and I wanted to make sure that each vertex ID was stored as well.
 
Again, thanks. I'll keep the xrange in mind also!
Quote 0 0
pshipkov
If vertex count is what works for you - go for it. I just provided an example. :)

physical vs logical indexing:
lets say you have the next few weight attributes:
weightList.weight[0]
weightList.weight[2]
weightList.weight[5]

if you query logical indexes you will get this: 0 2 5
if you query physical ones you will get this: 0 1 2

the physical indexes represent the location of each existing (weight in this case) attribute in the weightList multi attribute array. If you loop through them things are very fast since you directly access the attribute objects.

the logical indexes represent which points on the geometry object each weight is affecting. Of course this is when we talk about weight maps. In the general case the logical index can mean anything. This is slower to loop through since every time maya runs binary search internally. So when you access logical attributes in a loop like this:

for(logicalAttribute in listOfLogicalAttributes) access logicalAttribute;

what actually happens under the hood is this:

for(logicalAttribute in listOfLogicalAttributes)
{
   logicalIndex = logicalAttribute.getIndex();
    for(i=0; i<numberOfAttributes; ++i)
    {
        if(logicalIindex == listOfPysicalAttributes[i].getIndex())
        {
            access listOfPysicalAttributes[i];
        }
    }
}

This is pseudo-code, but you can see how much extra work the computer will be doing for large number of attributes.
Quote 0 0
salvaom
Thank you very much, it is much more clear to me now. I appreciate a lot you taking the time to explain. This one works like a charm now. Now I'm jumping into C++ now, so let's see how this goes.
Quote 0 0

Add a Website Forum to your website.