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)