Fastest way to query vertex position in Maya

I am working on a couple projects that require me to query vertex position data, which got me curious to what might be the fastest approach. I set up a little test and went through all the different python approaches to querying vertex data in Maya. The approaches consists off:

  • Maya Python API 1.0
  • Maya Python API 2.0
  • Maya Cmds
  • Pymel

The test results where more or less as expected, nothing new arose. But it is quite interesting to see how the different approaches are integrated with Maya and what this could imply for their usability. The test case is a polySphere smoothed 5 times which gives it:

  • vertices’s: 399370
  • edges: 798732
  • faces: 399366

I also thought it would be sensible to make every approach structure the data in the same manner, which would possibly give it a bit more “real world” importance. Anyways, the data is structured so the first indices’s represents the vertex ID and the second level of indices’s represent the x, y,z of the vertex. If all the functions outputted to a variable called “vertexList” I could go:

vertexList[0]

to look at the position of the first vertex of the object queried. And go:

vertexList[0][0]

to retrieve the x value of the first vertex.

Results

Times

Maya Python API 1.0 : 0.006
Maya Python API 2.0 : 0.006
Maya Cmds : 0.456
Pymel : 23.769

Maya Python API 1.0

cProfile.run(‘vertexOm1()’)
         46 function calls in 0.006 seconds
   Ordered by: standard name
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.005    0.005 <maya console>:45(vertexOm1)
        1    0.001    0.001    0.006    0.006 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:10299(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:10301(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:10306(__init__)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:10311(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:10316(getDagPath)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:2870(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:2872(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:2874(__init__)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:2879(<lambda>)
        4    0.000    0.000    0.000    0.000 OpenMaya.py:34(_swig_setattr_nondynamic)
        4    0.000    0.000    0.000    0.000 OpenMaya.py:47(_swig_setattr)
        4    0.000    0.000    0.000    0.000 OpenMaya.py:50(_swig_getattr)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:5703(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:5706(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:5710(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:5713(__init__)
        1    0.000    0.000    0.005    0.005 OpenMaya.py:5784(getPoints)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:9301(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:9303(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:9305(__init__)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:9310(<lambda>)
        1    0.000    0.000    0.000    0.000 factories.py:2422(apiSetAttrWrap)
        1    0.005    0.005    0.005    0.005 {_OpenMaya.MFnMesh_getPoints}
        1    0.000    0.000    0.000    0.000 {_OpenMaya.MGlobal_getActiveSelectionList}
        1    0.000    0.000    0.000    0.000 {_OpenMaya.MSelectionList_getDagPath}
        1    0.000    0.000    0.000    0.000 {_OpenMaya.new_MDagPath}
        1    0.000    0.000    0.000    0.000 {_OpenMaya.new_MFnMesh}
        1    0.000    0.000    0.000    0.000 {_OpenMaya.new_MPointArray}
        1    0.000    0.000    0.000    0.000 {_OpenMaya.new_MSelectionList}
        1    0.000    0.000    0.000    0.000 {hasattr}
        1    0.000    0.000    0.000    0.000 {method ‘disable’ of ‘_lsprof.Profiler’ objects}
        4    0.000    0.000    0.000    0.000 {method ‘get’ of ‘dict’ objects}

Maya Python API 2.0

cProfile.run(‘vertexOm2()’)
         6 function calls in 0.006 seconds
   Ordered by: standard name
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.005    0.005 <maya console>:73(vertexOm2)
        1    0.001    0.001    0.006    0.006 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {getActiveSelectionList}
        1    0.000    0.000    0.000    0.000 {method ‘disable’ of ‘_lsprof.Profiler’ objects}
        1    0.000    0.000    0.000    0.000 {method ‘getDagPath’ of ‘OpenMaya.MSelectionList’ objects}
        1    0.005    0.005    0.005    0.005 {method ‘getPoints’ of ‘OpenMaya.MFnMesh’ objects}

Maya Cmds

cProfile.run(‘vertexCmds()’)
         8 function calls in 0.456 seconds
   Ordered by: standard name
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.428    0.428 <maya console>:87(vertexCmds)
        1    0.027    0.027    0.456    0.456 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {built-in method ls}
        1    0.327    0.327    0.327    0.327 {built-in method xform}
        1    0.000    0.000    0.000    0.000 {iter}
        1    0.000    0.000    0.000    0.000 {method ‘disable’ of ‘_lsprof.Profiler’ objects}
        1    0.000    0.000    0.000    0.000 {method ‘split’ of ‘str’ objects}
        1    0.101    0.101    0.101    0.101 {zip}

Pymel

cProfile.run(‘vertexPm()’)
         30751396 function calls (26358413 primitive calls) in 23.769 seconds
   Ordered by: standard name
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   23.395   23.395 <maya console>:105(vertexPm)
        1    0.343    0.343   23.769   23.769 <string>:1(<module>)
        1    0.001    0.001   23.393   23.393 <string>:1(getPoints)
        4    0.000    0.000    0.000    0.000 OpenMaya.py:10299(<lambda>)
        4    0.000    0.000    0.000    0.000 OpenMaya.py:10301(<lambda>)
        4    0.000    0.000    0.000    0.000 OpenMaya.py:10306(__init__)
        4    0.000    0.000    0.000    0.000 OpenMaya.py:10311(<lambda>)
        3    0.000    0.000    0.000    0.000 OpenMaya.py:10314(length)
        3    0.000    0.000    0.000    0.000 OpenMaya.py:10316(getDagPath)
        4    0.000    0.000    0.000    0.000 OpenMaya.py:10318(add)
   798724    0.323    0.000    2.827    0.000 OpenMaya.py:11303(<lambda>)
   399362    0.179    0.000    0.539    0.000 OpenMaya.py:11305(<lambda>)
   399362    0.031    0.000    0.031    0.000 OpenMaya.py:11316(<lambda>)
       16    0.000    0.000    0.000    0.000 OpenMaya.py:1545(<lambda>)
        7    0.000    0.000    0.000    0.000 OpenMaya.py:1546(hasFn)
        3    0.000    0.000    0.000    0.000 OpenMaya.py:2870(<lambda>)
        3    0.000    0.000    0.000    0.000 OpenMaya.py:2872(<lambda>)
        3    0.000    0.000    0.000    0.000 OpenMaya.py:2874(__init__)
        3    0.000    0.000    0.000    0.000 OpenMaya.py:2879(<lambda>)
        7    0.000    0.000    0.000    0.000 OpenMaya.py:2885(node)
        5    0.000    0.000    0.000    0.000 OpenMaya.py:2905(partialPathName)
   798739    0.888    0.000    2.176    0.000 OpenMaya.py:34(_swig_setattr_nondynamic)
        2    0.000    0.000    0.000    0.000 OpenMaya.py:4051(<lambda>)
        2    0.000    0.000    0.000    0.000 OpenMaya.py:4054(<lambda>)
        2    0.000    0.000    0.000    0.000 OpenMaya.py:4058(<lambda>)
        2    0.000    0.000    0.000    0.000 OpenMaya.py:4061(__init__)
        2    0.000    0.000    0.000    0.000 OpenMaya.py:4071(typeName)
        2    0.000    0.000    0.000    0.000 OpenMaya.py:4082(findPlug)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:4173(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:4176(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:4180(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:4184(__init__)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:4300(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:4303(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:4307(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:4310(__init__)
   798739    0.327    0.000    2.503    0.000 OpenMaya.py:47(_swig_setattr)
   399377    0.310    0.000    0.361    0.000 OpenMaya.py:50(_swig_getattr)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:5703(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:5706(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:5710(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:5713(__init__)
        1    0.000    0.000    0.005    0.005 OpenMaya.py:5784(getPoints)
        2    0.000    0.000    0.000    0.000 OpenMaya.py:9087(<lambda>)
        2    0.000    0.000    0.000    0.000 OpenMaya.py:9089(<lambda>)
        2    0.000    0.000    0.000    0.000 OpenMaya.py:9091(__init__)
        2    0.000    0.000    0.000    0.000 OpenMaya.py:9096(<lambda>)
        9    0.000    0.000    0.000    0.000 OpenMaya.py:9097(object)
        9    0.000    0.000    0.000    0.000 OpenMaya.py:9100(isValid)
        9    0.000    0.000    0.000    0.000 OpenMaya.py:9101(isAlive)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:9301(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:9303(<lambda>)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:9305(__init__)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:9310(<lambda>)
   399362    0.164    0.000    0.399    0.000 OpenMaya.py:9311(__getitem__)
        1    0.000    0.000    0.000    0.000 OpenMaya.py:9315(length)
   399362    1.151    0.000    6.306    0.000 OpenMaya.py:9340(__init__)
   798724    0.098    0.000    0.098    0.000 OpenMaya.py:9345(<lambda>)
   399362    0.156    0.000    0.488    0.000 OpenMaya.py:9349(assign)
        9    0.000    0.000    0.000    0.000 allapi.py:160(isValidMObjectHandle)
      4/3    0.000    0.000    0.000    0.000 allapi.py:216(toApiObject)
        6    0.000    0.000    0.000    0.000 arguments.py:13(isIterable)
        2    0.000    0.000    0.000    0.000 arguments.py:344(listForNone)
        4    0.000    0.000    0.000    0.000 arguments.py:49(isMapping)
   399362    0.210    0.000    1.772    0.000 arrays.py:1207(_shapecheck)
   798724    2.362    0.000    3.138    0.000 arrays.py:1216(_defaultshape)
   399362    0.642    0.000    4.030    0.000 arrays.py:1296(_expandshape)
        2    0.000    0.000    0.000    0.000 common.py:21(capitalize)
   399362    1.223    0.000   11.755    0.000 datatypes.py:282(__new__)
   399362    1.812    0.000   10.211    0.000 datatypes.py:295(__init__)
   399362    0.865    0.000    2.502    0.000 datatypes.py:369(assign)
        1    0.000    0.000    0.000    0.000 enum.py:346(getIndex)
        1    0.967    0.967   23.387   23.387 factories.py:1334(<lambda>)
        1    0.000    0.000    0.000    0.000 factories.py:1571(castInput)
        1    0.000    0.000    0.000    0.000 factories.py:1600(castInputEnum)
        1    0.000    0.000    0.000    0.000 factories.py:1612(fromInternalUnits)
        1    0.000    0.000    0.000    0.000 factories.py:1664(castResult)
        1    0.000    0.000    0.000    0.000 factories.py:1696(initReference)
        1    0.000    0.000   23.387   23.387 factories.py:1699(castReferenceResult)
        1    0.000    0.000    0.000    0.000 factories.py:1748(isStatic)
        1    0.000    0.000   23.392   23.392 factories.py:2064(wrappedApiFunc)
8785964/4392982    7.520    0.000    7.520    0.000 factories.py:2404(__getattribute__)
   798728    1.272    0.000    7.447    0.000 factories.py:2422(apiSetAttrWrap)
        2    0.000    0.000    0.000    0.000 factories.py:3077(getVirtualClass)
        1    0.000    0.000    0.000    0.000 general.py:1572(__init__)
        2    0.000    0.000    0.000    0.000 general.py:1641(__new__)
        2    0.000    0.000    0.000    0.000 general.py:1913(__init__)
        2    0.000    0.000    0.000    0.000 general.py:1923(__apimfn__)
        2    0.000    0.000    0.000    0.000 general.py:36(_getPymelTypeFromObject)
        2    0.000    0.000    0.000    0.000 general.py:4224(validComponentIndexType)
        2    0.000    0.000    0.000    0.000 general.py:65(_getPymelType)
        1    0.000    0.000    0.000    0.000 general.py:907(listRelatives)
        2    0.000    0.000    0.000    0.000 general.py:923(<genexpr>)
        1    0.000    0.000    0.000    0.000 general.py:945(ls)
        1    0.000    0.000    0.000    0.000 nodetypes.py:1132(getChildren)
        1    0.000    0.000    0.001    0.001 nodetypes.py:1365(__getattr__)
        7    0.000    0.000    0.000    0.000 nodetypes.py:1386(__setattr__)
        9    0.000    0.000    0.000    0.000 nodetypes.py:146(__apimobject__)
        1    0.000    0.000    0.000    0.000 nodetypes.py:1496(getShapes)
        1    0.000    0.000    0.000    0.000 nodetypes.py:157(__str__)
        1    0.000    0.000    0.000    0.000 nodetypes.py:160(__unicode__)
        1    0.000    0.000    0.000    0.000 nodetypes.py:323(__getattr__)
        1    0.000    0.000    0.000    0.000 nodetypes.py:396(attr)
        1    0.000    0.000    0.000    0.000 nodetypes.py:410(_attr)
        1    0.000    0.000    0.000    0.000 nodetypes.py:61(__melobject__)
        1    0.000    0.000    0.000    0.000 nodetypes.py:71(__repr__)
        5    0.000    0.000    0.000    0.000 nodetypes.py:744(_updateName)
        5    0.000    0.000    0.000    0.000 nodetypes.py:762(name)
        2    0.000    0.000    0.000    0.000 nodetypes.py:815(__apiobject__)
        7    0.000    0.000    0.000    0.000 nodetypes.py:819(__apimdagpath__)
        9    0.000    0.000    0.000    0.000 nodetypes.py:845(__apihandle__)
        2    0.000    0.000    0.000    0.000 pmcmds.py:119(wrappedCmd)
        4    0.000    0.000    0.000    0.000 pmcmds.py:73(getMelRepresentation)
        1    0.000    0.000    0.000    0.000 utilitytypes.py:537(__get__)
        1    0.000    0.000    0.000    0.000 utilitytypes.py:542(newfunc)
        7    0.000    0.000    0.000    0.000 {_OpenMaya.MDagPath_node}
        5    0.000    0.000    0.000    0.000 {_OpenMaya.MDagPath_partialPathName}
        2    0.000    0.000    0.000    0.000 {_OpenMaya.MFnDependencyNode_findPlug}
        2    0.000    0.000    0.000    0.000 {_OpenMaya.MFnDependencyNode_typeName}
        1    0.005    0.005    0.005    0.005 {_OpenMaya.MFnMesh_getPoints}
        9    0.000    0.000    0.000    0.000 {_OpenMaya.MObjectHandle_isAlive}
        9    0.000    0.000    0.000    0.000 {_OpenMaya.MObjectHandle_isValid}
        9    0.000    0.000    0.000    0.000 {_OpenMaya.MObjectHandle_object}
        7    0.000    0.000    0.000    0.000 {_OpenMaya.MObject_hasFn}
   399362    0.235    0.000    0.235    0.000 {_OpenMaya.MPointArray___getitem__}
        1    0.000    0.000    0.000    0.000 {_OpenMaya.MPointArray_length}
   399362    0.333    0.000    0.333    0.000 {_OpenMaya.MPoint_assign}
        4    0.000    0.000    0.000    0.000 {_OpenMaya.MSelectionList_add}
        3    0.000    0.000    0.000    0.000 {_OpenMaya.MSelectionList_getDagPath}
        3    0.000    0.000    0.000    0.000 {_OpenMaya.MSelectionList_length}
        3    0.000    0.000    0.000    0.000 {_OpenMaya.new_MDagPath}
        1    0.000    0.000    0.000    0.000 {_OpenMaya.new_MFnDagNode}
        2    0.000    0.000    0.000    0.000 {_OpenMaya.new_MFnDependencyNode}
        1    0.000    0.000    0.000    0.000 {_OpenMaya.new_MFnMesh}
        1    0.000    0.000    0.000    0.000 {_OpenMaya.new_MFnTransform}
        2    0.000    0.000    0.000    0.000 {_OpenMaya.new_MObjectHandle}
        1    0.000    0.000    0.000    0.000 {_OpenMaya.new_MPointArray}
   399362    0.105    0.000    0.105    0.000 {_OpenMaya.new_MPoint}
        4    0.000    0.000    0.000    0.000 {_OpenMaya.new_MSelectionList}
   399364    0.094    0.000    0.094    0.000 {built-in method __new__ of type object at 0x000000001E28F530}
        1    0.000    0.000    0.000    0.000 {built-in method listRelatives}
        1    0.000    0.000    0.000    0.000 {built-in method ls}
   399368    0.057    0.000    0.057    0.000 {getattr}
  2396178    1.039    0.000    1.039    0.000 {hasattr}
       54    0.000    0.000    0.000    0.000 {isinstance}
        2    0.000    0.000    0.000    0.000 {issubclass}
        5    0.000    0.000    0.000    0.000 {iter}
  3194904    0.237    0.000    0.237    0.000 {len}
        2    0.000    0.000    0.000    0.000 {map}
        6    0.000    0.000    0.000    0.000 {method ‘append’ of ‘list’ objects}
  1198086    0.138    0.000    0.138    0.000 {method ‘count’ of ‘list’ objects}
        1    0.000    0.000    0.000    0.000 {method ‘disable’ of ‘_lsprof.Profiler’ objects}
  2396200    0.242    0.000    0.242    0.000 {method ‘get’ of ‘dict’ objects}
        2    0.000    0.000    0.000    0.000 {method ‘iteritems’ of ‘dict’ objects}
        5    0.000    0.000    0.000    0.000 {method ‘pop’ of ‘dict’ objects}
        1    0.000    0.000    0.000    0.000 {method ‘split’ of ‘unicode’ objects}
        2    0.000    0.000    0.000    0.000 {method ‘upper’ of ‘unicode’ objects}
        4    0.000    0.000    0.000    0.000 {operator.isMappingType}
        1    0.005    0.005    0.005    0.005 {range}
  1597448    0.433    0.000    0.433    0.000 {reduce}

In the results for each approach you can see how many calls each method uses to finish the task. From the data you can see that Maya Cmds is written tightly to Maya’s command engine, but not as fast as API methods. And it’s clear that Pymel was never written for speed in mind, but for writing Maya scripts in an Object Oriented Programming fashion. Which makes it interesting with the introduction of Maya Python API 2.0, which is made more tightly integrated with the C++ API, using even less calls than the Maya Cmds module. And is used in a more true Pythonic way similar to Pymel just faster and the ability to write plugins with!

Code
Here’s the code used for the test. It might not be the fastest way for each approach, in which I would’t mind a comment letting me know!

import maya.OpenMaya as om
import maya.api.OpenMaya as om2
import maya.cmds as cmds
import pymel.core as pm

import cProfile

"""
A comparison of the different methods of retrieving vertex positions in Maya.

Assuming that all approaches should behave in similar manner, which is:

1. You select your object
 2. Run the function
 3. receive a list where:
 - the index corresponds to vertex ID
 - within each index there's a list of the x,y,z coordinates of that vertex
 i.e. returnedList = [[x,y,z],[x,y,z],[x,y,z],...]

test case:

A standard polygon sphere, smoothed 4 times resulting in:
 verts: 399370
 edges: 798732
 faces: 399366

data collection:

Using cProfile to run the different functions and receive their results.
 i.e cProfile.run('vertexCmds()')

#############################
reproduce the test case:

import maya.cmds as cmds

cmds.polySphere()

for i in range(5):
 exec "cmds.polySmooth()"

"""

def vertexOm1():
 """
 Using Maya Python API 1.0
 """
 #___________Selection___________

 # 1 # initialize a selectionList holder
 selectionLs = om.MSelectionList()

 # 2 # get the selected object in the viewport and put it in the selection list
 om.MGlobal.getActiveSelectionList(selectionLs)

 # 3 # initialize a dagpath object
 dagPath = om.MDagPath()

 # 4 # populate the dag path object with the first object in the selection list
 selectionLs.getDagPath(0,dagPath)

 #___________Query vertex position ___________

 # initialize a Point array holder
 vertPoints = om.MPointArray()

 # create a Mesh functionset from our dag object
 mfnObject = om.MFnMesh(dagPath)

 # call the function "getPoints" and feed the data into our pointArray
 mfnObject.getPoints(vertPoints)

 return vertPoints

def vertexOm2():
 """
 Using Maya Python API 2.0
 """
 #___________Selection___________
 # 1 # Query the selection list
 selectionLs = om2.MGlobal.getActiveSelectionList()

 # 2 # Get the dag path of the first item in the selection list
 selObj = selectionLs.getDagPath(0)

 #___________Query vertex position ___________
 # create a Mesh functionset from our dag object
 mfnObject = om2.MFnMesh(selObj)

 return mfnObject.getPoints()

def vertexCmds():
 """
 Using Maya Cmds
 """
 #___________Selection___________
 # query the currently selected object
 selTemp = str(cmds.ls(selection=True))
 # edit retrieved string so we can feed it into commands
 sel = selTemp.split("'")[1]


 #___________Query vertex position ___________
 # use the xform command with the ".vtx[*]" to retireve all vertex positions at once
 vertPosTemp = cmds.xform(sel + '.vtx[*]', q=True, ws=True, t=True)

 # split the resulting list into sets of 3
 vertPos = zip(*[iter(vertPosTemp)]*3)

 return vertPos

def vertexPm():
 """
 Using Pymel
 """
 #___________Selection___________
 # use the ls command and "[0]" to make sure we are using the first object
 sel = pm.ls(selection=True)[0]

 #___________Query vertex position ___________
 # use the .getPoints also found in openMaya, but here run through the pymel bindings
 return sel.getPoints(space='object')

cProfile.run('vertexOm1()')
cProfile.run('vertexOm2()')
cProfile.run('vertexCmds()')
cProfile.run('vertexPm()')

Leave a Reply

Your email address will not be published.