How to move lot of vertices with python in Maya?

Hello,

I am currently scripting a tool to flip and mirror objects using a base mesh as a reference (similar to abSymMesh, but aiming for faster performance), and I need to manipulate vertex positions.

Initially, I attempted to use xform and iterated over all the vertices, but I found this approach to be somewhat slow for objects with more than 6000 vertices.

I considered using setAttr because I’ve previously used it for transferring weights, and it allowed me to input a list of vertices and weights efficiently. However, I encountered issues when trying to apply it to the positions of vertices.

Therefore, I decided to leverage the Maya API 2.0, which has worked quite well. However, I am facing challenges with implementing undo functionality, as it does not work “out of the box” with the setPoints method.

As I am relatively new to Maya and scripting within it, I realize I may be lacking some foundational knowledge. From what I have read, it appears that I need to create a command using MpxCommand to encapsulate my code for moving vertices. Is this correct, or is there a simpler approach I am overlooking?

Additionally, how can I ensure that this operates correctly within a single Python file? Because I have also come to understand that a command needs to be initialized when Maya starts.

Thank you in advance for any guidance or suggestions you can provide.

Variant of way:
From Marcus Ottosson (aka mottosso): Undo/Redo support for Maya Python API 2.0

Background Story:
Maya-Python API - Access to Undo Queue
https://groups.google.com/g/python_inside_maya/c/fqX9mNfg5j0

Even if you do not use this way to solve the problem, you will usefully expand your consciousness :slight_smile:

I did see that option but wasn’t totally sure it was the right and easiest way to go.
Now that you’ve pointed it out, I will definitely give it a try !

Thank you for the answer

Thanks a lot!

I gave that solution a shot, and with partial functions, it works perfectly! The only hiccup is that it needs to be “installed,” and since I’m not an admin in my studio, I can’t add the script to everyone’s setup.

Is there possibly another workaround that doesn’t require admin rights or installation on every individual’s setup?

Thanks again for all the help!

Let me guess … the points were moving, but you were getting something that looked sorta like the mesh was scaling by 2 times?

That’s because Maya stores mesh point positions where you can’t really write to them via cmds, but also stores point offsets in the .pnts attribute. You weren’t overwriting the original points when you were using setAttr, you were setting the offset values which got added to the original positions.

And as you found out, this can lead to some really confusing behavior.


Now to possibly answer your question … This worked for me, but I know it’s not applicable to all use cases:

Duplicate your mesh
Set the points on that duplicate using the API
Use a blendshape to update the points on the original mesh
Delete the history.

from maya import OpenMaya as om
from maya import cmds

cube = cmds.polyCube(sx=50, sy=50, sz=50, constructionHistory=False)[0]

cmds.undoInfo(openChunk=True, chunkName='BSTest')
try:
    dup = cmds.duplicate(cube)[0]
    shp = cmds.listRelatives(dup, shapes=True)[0]
    sel = om.MSelectionList()
    sel.add(shp)
   
    mo = om.MObject()
    sel.getDependNode(0, mo)
    fn = om.MFnMesh(mo)

    pts = om.MPointArray()
    fn.getPoints(pts)
    pts2 = om.MPointArray()
    pts2.setLength(pts.length())
    for i in range(pts.length()):
        pts2.set(pts[i] / 3, i)
    fn.setPoints(pts2)

    bs = cmds.blendShape(dup, cube)
    cmds.blendShape(bs, e=1, w=[0, 1.0])
    cmds.delete(dup)
    cmds.polyEvaluate(cube, face=True)  # force evaluate
    cmds.delete(cube, constructionHistory=True)
finally:
    cmds.undoInfo(closeChunk=True)
4 Likes

Thanks for suggesting that workaround! I ended up having to go with apiundo since I needed my script for editing shapes and other scenarios where the blendshape solution didn’t quite fit. But I’ll definitely keep your advice in mind for future projects! :blush:

I’ve decided to make the script flexible, allowing it to either utilize apiundo or not. This way, if anyone wants to have the undo functionality, they simply need to place the apiundo.py file in their scripts location.

Thanks for helping !

Just wanted to share a little update on this topic. After giving the setAttr method another try, I found a way to make it work by using xform to calculate the correct values. So, this method serves as an alternative to the API 2.0 approach. It’s a bit slower and might not be suitable for all cases, but it could be helpful in some situations. Here’s a quick overview of how I implemented it:

def copyVertsPos(srcMesh, destMesh):
    cmds.undoInfo(openChunk=True)
    try:
        srcVertsXForm = cmds.xform("{0}.vtx[*]".format(srcMesh), q=True, t=True, os=True)
        destVertsXForm = cmds.xform("{0}.vtx[*]".format(destMesh), q=True, t=True, os=True)
        destVertsAttr = [v for verts in cmds.getAttr("{0}.vtx[*]".format(destMesh)) for v in verts]
        
        newVertsListAttr = []
        
        for i in range(len(srcVertsXForm)):
            newVertsListAttr.append(destVertsAttr[i] + srcVertsXForm[i] - destVertsXForm[i])
            
        mel_cmd = ['setAttr("{0}.vtx[*]"'.format(destMesh)]
        for v in newVertsListAttr:
            mel_cmd.append(",{0}".format(v))
        mel_cmd.append(')')
        
        mel.eval("".join(mel_cmd))
        cmds.dgdirty(destMesh)
    finally:
        cmds.undoInfo(closeChunk=True)

I’m still pretty new to all of this, so please excuse any bad things in my code. I’m sharing it mainly to illustrate the idea. I’m eager to learn and improve, so any feedback or suggestions are warmly welcomed!

Thanks