Make function using Maya API 2.0 setWeights faster

804 views
Skip to first unread message

Jesse K.

unread,
Sep 3, 2019, 8:42:19 AM9/3/19
to Python Programming for Autodesk Maya


Hi All,

I have written the following code for a specific case. I want to copy the weight of a vertex to a Nurbs Surface CV in basically the same location.
However I have multiple Nurbs Surfaces under one transform that are all connected to one skinCluster.

import maya.api.OpenMaya as om
import maya.api.OpenMayaAnim as oma
from maya import cmds
import time


def geo_weights_to_surface(source, target, tol=0.005, space=om.MSpace.kWorld):
start = time.time()
# Mesh
mesh_sel = om.MSelectionList().add(source)
mesh_dag_path = mesh_sel.getDagPath(0)
mesh_dag_path.extendToShape()
mesh_node = om.MFnDependencyNode(mesh_dag_path.node())
mesh_attr = mesh_node.attribute('inMesh')
mesh_plug = mesh_node.findPlug(mesh_attr, 0)
mesh_sc_array = mesh_plug.connectedTo(True, False)
mesh_sc_node = mesh_sc_array[0].node()
mesh_sc = oma.MFnSkinCluster(mesh_sc_node)
mesh_influences = mesh_sc.influenceObjects()
# Surface
surf_sel = om.MSelectionList().add(target)
surf_dag_path = surf_sel.getDagPath(0)
shape_nr = surf_dag_path.numberOfShapesDirectlyBelow()

# Append shapes to array
shapes = om.MDagPathArray()
for idx in range(shape_nr):
dag_path = surf_sel.getDagPath(0)
dag_path.extendToShape(idx)
shapes.append(dag_path)

mesh_itr = om.MItMeshVertex(mesh_dag_path)
while not mesh_itr.isDone():
vtx_pos = mesh_itr.position(space)
vtx = mesh_itr.currentItem()
for item in range(len(shapes)):
surf_itr = om.MItSurfaceCV(shapes[item])
while not surf_itr.isDone():
while not surf_itr.isRowDone():
cv_pos = surf_itr.position(space)
check = cv_pos.isEquivalent(vtx_pos, tol)
if check:
surf_dag_path = surf_sel.getDagPath(0)
surf_dag_path.extendToShape(item)
shape_node = om.MFnDependencyNode(surf_dag_path.node())
surf_attr = shape_node.attribute('create')
surf_plug = shape_node.findPlug(surf_attr, 0)
surf_sc_array = surf_plug.connectedTo(True, False)
surf_sc_node = surf_sc_array[0].node()
surf_sc = oma.MFnSkinCluster(surf_sc_node)
for mesh_inf in range(len(mesh_influences)):
mesh_inf_idx = mesh_sc.indexForInfluenceObject(
mesh_influences[mesh_inf])
vtx_weight = mesh_sc.getWeights(mesh_dag_path, vtx, mesh_inf_idx)
vtx_blend_weight = mesh_sc.getBlendWeights(mesh_dag_path, vtx)
cv_uv = surf_itr.uvIndices()
cv_component = om.MFnNurbsSurface(surf_dag_path).cv(cv_uv[0],
cv_uv[1])
surf_inf_idx = surf_sc.indexForInfluenceObject(
mesh_influences[mesh_inf])
surf_sc.setWeights(surf_dag_path, cv_component, surf_inf_idx,
vtx_weight[0], normalize=True)
surf_sc.setBlendWeights(surf_dag_path, cv_component,
vtx_blend_weight)
surf_itr.next()
surf_itr.nextRow()
mesh_itr.next()
print 'Copying weights took', time.time() - start, 'seconds.'


The code does exactly what I want, but I think I've made it quite slow by adding the surface iterator inside of the vertex iterator.
What would be the most efficient way to make it faster? Separate the loops?

Thanks in advance!

Michael Boon

unread,
Sep 11, 2019, 8:40:37 PM9/11/19
to Python Programming for Autodesk Maya
Yeah that will be very slow for large meshes. I'm not sure if there's a straightforward way to fix it.
You could double the speed by keeping a list of booleans to mark when a cv has been paired with a vert, so you don't check that cv again, but I expect that doubling the speed is not good enough.
You might want to look into MFnMesh.closestIntersection. For any cv (and its surface normal), that should get you the closest point on the mesh without needing to check all the verts. Then you can use the returned triangle and barycentric coordinates to get the closest vertex quickly.

It's also possible there's a faster way built-in, using some part of the Transfer Attributes functionality. I'm not sure.
Message has been deleted

Jesse K.

unread,
Sep 13, 2019, 5:05:02 AM9/13/19
to Python Programming for Autodesk Maya
Hi Michael!

Thanks so much for the tip. I had a look at the function you mentioned and wrote this as improvement using that. From what I've tested it performs on meshes up to 900k faces in roughly 0.7 seconds compared to roughly 8 seconds for the old version.
I'm pretty sure this is still not the fastest way to do this, but it's already way faster than it was before. So if you have any other improvement points I'd like to hear it!

def geo_weights_to_surface(source, target, tol=0.005, space=om.MSpace.kWorld):

start = time.time()
# Mesh
mesh_sel = om.MSelectionList().add(source)
mesh_dag_path = mesh_sel.getDagPath(0)
mesh_dag_path.extendToShape()
    mesh = om.MFnMesh(mesh_dag_path)

mesh_node = om.MFnDependencyNode(mesh_dag_path.node())
mesh_attr = mesh_node.attribute('inMesh')
mesh_plug = mesh_node.findPlug(mesh_attr, 0)
mesh_sc_array = mesh_plug.connectedTo(True, False)
mesh_sc_node = mesh_sc_array[0].node()

mesh_sc = oma.MFnSkinCluster(mesh_sc_node)
mesh_influences = mesh_sc.influenceObjects()
# Surface
surf_sel = om.MSelectionList().add(target)
surf_dag_path = surf_sel.getDagPath(0)
shape_nr = surf_dag_path.numberOfShapesDirectlyBelow()

# Append shapes to array
shapes = om.MDagPathArray()
for idx in range(shape_nr):
dag_path = surf_sel.getDagPath(0)
dag_path.extendToShape(idx)
shapes.append(dag_path)

    for item in range(len(shapes)):

surf_dag_path = surf_sel.getDagPath(0)
surf_dag_path.extendToShape(item)
shape_node = om.MFnDependencyNode(surf_dag_path.node())
surf_attr = shape_node.attribute('create')
surf_plug = shape_node.findPlug(surf_attr, 0)
surf_sc_array = surf_plug.connectedTo(True, False)
surf_sc_node = surf_sc_array[0].node()
surf_sc = oma.MFnSkinCluster(surf_sc_node)
        surf = om.MFnNurbsSurface(shapes[item])


surf_itr = om.MItSurfaceCV(shapes[item])

while not surf_itr.isDone():
while not surf_itr.isRowDone():
                pos = surf_itr.position()
float_pos = om.MFloatPoint(pos.x, pos.y, pos.z)
uvs = surf_itr.uvIndices()
cv_normal = surf.normal(uvs[0], uvs[1], space=space)
dir_vector = om.MFloatVector(cv_normal.x, cv_normal.y, cv_normal.z)
facenr = mesh.closestIntersection(float_pos, dir_vector, space, 0.05, True)[2]
face_sel = om.MSelectionList().add(source + '.f[' + str(facenr) + ']')
face_component = face_sel.getComponent(0)[1]

face_itr = om.MItMeshFaceVertex(mesh_dag_path, face_component)
while not face_itr.isDone():
if face_itr.position().isEquivalent(pos, tol):
vtx_idx = face_itr.vertexId()
idx_sel = om.MSelectionList().add(source + '.f[' + str(vtx_idx) + ']')
vtx = idx_sel.getComponent(0)[1]

for mesh_inf in range(len(mesh_influences)):
mesh_inf_idx = mesh_sc.indexForInfluenceObject(
mesh_influences[mesh_inf])
vtx_weight = mesh_sc.getWeights(mesh_dag_path, vtx, mesh_inf_idx)
vtx_blend_weight = mesh_sc.getBlendWeights(mesh_dag_path, vtx)
cv_uv = surf_itr.uvIndices()
cv_component = om.MFnNurbsSurface(surf_dag_path).cv(cv_uv[0], cv_uv[1])
surf_inf_idx = surf_sc.indexForInfluenceObject(
mesh_influences[mesh_inf])
surf_sc.setWeights(surf_dag_path, cv_component, surf_inf_idx,
vtx_weight[0], normalize=True)
surf_sc.setBlendWeights(surf_dag_path, cv_component, vtx_blend_weight)
                    face_itr.next()
surf_itr.next()
surf_itr.nextRow()

Michael Boon

unread,
Sep 23, 2019, 12:35:47 AM9/23/19
to Python Programming for Autodesk Maya
I'm glad that helped.

You're still doing a lot of work for every vertex of every polygon (meaning most verts are hit many times), and also for every influence object for each of those verts.
I'm not completely clear on what you're doing there, but I think you can still make it significantly faster if you want to.

I think you could use hitTriangle, hitBary1 and hitBary2 (returned from mesh.closestIntersection) to get 3 vertices and their relative distances to your hit location, rather than getting all the verts for each polygon. Then you could choose the closest vert and avoid a bunch of work.

If you do want to average the skinning from multiple verts, you could look up all your verts' skinning influences and weights first, so you don't have to look each vert up once for every polygon it is part of. That might make it 2x faster or more if you're lucky.

You can move indexForInfluenceObject out of the loops and save a lot of calls to that. I assume (but I'm not sure) that a dict lookup would be faster than that call. You can move cv_uv and cv_component out of that loop too since they will return the same thing every time.

When you call surf_sc.setWeights you have normalize=True. It would be faster to leave that false and then normalize all weights after you're done. It's also possible this is the slowest call in your whole algorithm and it might be worth trying to build your own lists and do a single call to setWeights right at the end. You could use the cprofile module to measure your algorithm and find out which function calls are taking the most time.

You can get away without composing strings for your faces or verts. That should be faster too. Here's an example for a face. There's an equivalent for vertices and for face-vertices too.
face_itr = om.MItMeshPolygon(mesh_dag_path) # Do this only once per mesh
face_itr
.setIndex(facenr) # Do this in your "while not surf_itr.isRowDone()" loop
Message has been deleted

Jesse K.

unread,
Oct 18, 2019, 10:46:57 AM10/18/19
to Python Programming for Autodesk Maya
Hi Michael,

Thanks a lot for the extra suggestions. I had some time to take a look at this and followed your advice.
I ended up using the hitBary1 and hitBary2 to get the closest component by using the face_itr.getTriangle for the returned relative triangle index of the closestIntersection.
That actually made things a lot faster already (about 2.5x)

I moved cv_uv and cv_component out of the loop too.
Disabling normalize made it slightly faster too, but the largest performance gain by fair was actually moving the setWeights out of the loop.
I kept it inside of the while loop because the setWeights won't take an MObjectArray. But I gained a lot of performance by putting the weights and indexes of the influence objects in arrays and applying those later.

I used the cProfile module too and as you already suggested the setWeights and setBlendWeights were the 3 and second slowest calls respectively with closestIntersection being the slowest call out of everything.

Kyle Burris

unread,
Jan 3, 2020, 4:12:22 PM1/3/20
to Python Programming for Autodesk Maya
For what its worth I found that using MFnSkinCluster to set weights was slower than setting the value on the plug its self. However I never tested this on meshes above 100k so your mileage might vary.
Reply all
Reply to author
Forward
0 new messages