get closest vertex in maya using python

david | maya,OpenMaya,python,tutorials | Sunday, July 7th, 2013

Recently I was writing a python function where I needed to find the mesh vertex closest to a locator. I already knew how to get a list of vertices from my mesh, how to get their worldspace locations and how to iterate through them calculating the distance between each one and the locator to determine the closest. But the brute force approach would have been pretty slow on a dense mesh.

I'm not very familiar with the OpenMaya classes, but I was pretty sure one of them would have a method that would make this task much faster, so I began to google the problem.

Here is what I found... and what I had to do to get it to work. Advanced coders will probably find this trivial, but I'm guessing there are others like me that will find it interesting.

Google led me straight to the OpenMaya MFnMesh class which, as its name suggests, has a bunch of mesh related methods, and one of those called getClosestPoint looked like it might be what I needed. The docs describe it as "Returns the closest point on this surface to the given point."

Ok, so how to use it in my script? I'm comfortable with python and PyMel, but not so much with OpenMaya so back to google.

Next stop was a forum where a similar question had been asked and answered several years back... http://tech-artists.org/forum/showthread.php?1286-Maya-Select-vertex-based-on-position There is even some good looking code posted there, by dbsmith.

Before I read the whole discussion, I copied and pasted the code into the maya script editor and ran it. It appeared to work... but not quite... It was getting the wrong vertex. Usually one near to the locator, but definitely not the closest. Then I read the rest of the discussion. Even the original poster admitted "Looks like maya's getClosestPoint() doesn't behave the way I thought it would. It definitely isn't getting the closest point...."

I continued googling. Found a few interesting, related posts. But nothing that showed how to make MFnMesh.getClosestPoint actually get the closest point.

In fact the answer was in the API docs that I had started with. I just didn't see it because I was in a hurry to write the code, so I didn't scroll down to the detailed description. And because the method name and description are deceptively misleading! In fact, on further reading I found that getClosestPoint writes its output to "*int closestPolygon" which is described as "Storage for the closest polygon id".

Ah ha! The closest point is not a vertex or even a point in space. It is a polygon index!

correction - July 8 2013: After a more careful inspection of the docs, I can see that there is also "[out]   theClosestPoint     Storage for the closest point", which is the worldspace location of the closest point on the mesh. But still, I want the index of the closest vertex, so my code below remains unchanged.

So now I have a solution. I can use OpenMaya MFnMesh getClosestPoint to find the closest er... polygon, and then use some basic PyMel methods to get the polygon's vertices and loop through them. Some simple vector math would find the closest.

This solution is not 100% perfect. I can think of scenarios where the closest vertex to my locator may not be on the closest face. In practice however, it usually will - and in the few cases where it is not, it will probably be close enough, especially if the mesh is reasonably dense, or when the faces are evenly distributed.

Here's a simplified extract from my code, where the mesh and locator names are hard coded:

import maya.OpenMaya as OpenMaya
import pymel.core as pm

geo = pm.PyNode('pSphere1')
loc = pm.PyNode('locator1')
pos = loc.getRotatePivot(space='world')

nodeDagPath = OpenMaya.MObject()
try:
    selectionList = OpenMaya.MSelectionList()
    selectionList.add(geo.name())
    nodeDagPath = OpenMaya.MDagPath()
    selectionList.getDagPath(0, nodeDagPath)
except:
    raise RuntimeError('OpenMaya.MDagPath() failed on %s' % geo.name())

mfnMesh = OpenMaya.MFnMesh(nodeDagPath)

pointA = OpenMaya.MPoint(pos.x, pos.y, pos.z)
pointB = OpenMaya.MPoint()
space = OpenMaya.MSpace.kWorld

util = OpenMaya.MScriptUtil()
util.createFromInt(0)
idPointer = util.asIntPtr()

mfnMesh.getClosestPoint(pointA, pointB, space, idPointer)  
idx = OpenMaya.MScriptUtil(idPointer).asInt()

faceVerts = [geo.vtx[i] for i in geo.f[idx].getVertices()]
closestVert = None
minLength = None
for v in faceVerts:
    thisLength = (pos - v.getPosition(space='world')).length()
    if minLength is None or thisLength < minLength:
        minLength = thisLength
        closestVert = v
pm.select(closestVert)

3 Comments »

  1. Hi David,
    Great to come across your blog again :)
    I have been using python in Maya a bit lately, but mainly just the maya.cmds module; my goal is to learn more about the Maya Python API.

    I tried to replicate your code without using pyMel (as its something I havent looked at yet).
    Also I am using Maya 2012 which seems to require some changes to the MScriptUtil section (I got a colleague to help me with that bit).
    I got it working roughly; sometimes it picks the closest vertex but sometimes it doesnt, and sometimes it selects two vertexes.
    I think its because maya cmds lists the verts of a face in such a way that there can be 3 list entries for 4 vertexes.
    I was thinking that it might be good to rewrite the loop section using MItSelectionList. As I'm new to the API, I'm finding this difficult.
    Do you know an easy way to list the verts without grouping them into one list entry (even if it is something like vert[5:6])?
    Anyway, thanks again. My code (almost working) is below;

    import maya.OpenMaya as OpenMaya
    import maya.cmds as mc
    import math

    def distanceBetweenTwoPoints(a=[0, 0, 0], b=[0, 0, 0]):
    distanceX = a[0] - b[0]
    distanceY = a[1] - b[1]
    distanceZ = a[2] - b[2]
    distance = math.sqrt((distanceX * distanceX) + (distanceY * distanceY) + (distanceZ * distanceZ))
    return distance

    def getClosestVert():
    geo = 'pSphere1'
    loc = 'locator1'
    locPos = mc.xform(loc, query=True, translation=True, worldSpace=True)

    nodeDagPath = OpenMaya.MObject()
    try:
    selectionList = OpenMaya.MSelectionList()
    selectionList.add(geo)
    nodeDagPath = OpenMaya.MDagPath()
    selectionList.getDagPath(0, nodeDagPath)
    except:
    raise RuntimeError('OpenMaya.MDagPath() failed on %s' % geo.name())

    mfnMesh = OpenMaya.MFnMesh(nodeDagPath)

    pointA = OpenMaya.MPoint(locPos[0], locPos[1], locPos[2])
    pointB = OpenMaya.MPoint()
    space = OpenMaya.MSpace.kWorld

    util = OpenMaya.MScriptUtil(0)
    idPointer = util.asIntPtr()
    mfnMesh.getClosestPoint(pointA, pointB, space, idPointer)
    idx = util.getInt(idPointer)
    mc.select("pSphere1.f[{idx}]".format(idx=idx))
    closestFace = mc.ls(selection=True)
    # print closestFace
    faceVerts = mc.polyListComponentConversion(closestFace, tv=True, fromFace=True)
    print faceVerts

    closestVert = None
    minLength = None
    for vert in faceVerts:
    vertPos = mc.xform(vert, query=True, translation=True, worldSpace=True)
    print "vertPos is " + str(vertPos)
    print "locPos is " + str(locPos)
    thisLength = distanceBetweenTwoPoints(locPos, vertPos)
    print "thisLength is " + str(thisLength)
    if minLength is None or thisLength < minLength:
    minLength = thisLength
    closestVert = vert
    mc.select(closestVert)

    Comment by ginamoore — July 16, 2013 @ 4:39 pm

  2. Hey Gina! Great to hear from you. It's a pity my comments are not allowing correct indentation of your code. I'm probably going to have to make some changes in that regard. I'm sure other's would appreciate the maya.cmds example too.

    With the vert lists... you want a flat list... as in... mc.ls(fl=True)... so you can do:

    faceVerts = mc.ls(mc.polyListComponentConversion(closestFace, tv=True, fromFace=True), fl=True)

    Comment by david — July 16, 2013 @ 11:02 pm

  3. Hi David,
    I just returned to your post and found your response to my comment. Wonderful! thank you. I didnt know about the "flat" flag.
    Thanks again.

    Comment by ginamoore — August 19, 2013 @ 12:07 pm

RSS feed for comments on this post.

Leave a comment

You must be logged in to post a comment.

Powered by WordPress | Based on a theme by Roy Tanck