How to find matching vertices in multiple maya meshes

Question:

I’m trying to compare the locations of vertices on one mesh to another and generate a list of paired vertices, (the ultimate purpose is to pair up vertices on a neck geo with the top verts of a body geo.)

The way I’m ‘pairing’ them is to just compare the distances between all vertices in both meshes and then match up the closest ones to eachother by ordering them in separate lists, (neck_geo_verts[0] is paired with body_geo_verts[0].)

I want to use OpenMaya as I’ve heard it considerably faster than cmds.xform.

Here’s my code so far getting the verts, although it’s using cmds and not the Maya API. I am having a really tough time finding what I need from the Maya documentation.

# The user selects an edge on both the bottom of the neck and top of the body, then this code gets all the vertices in an edge border on both of those geos and populates two lists with the vertices

import maya.cmds as mc
import maya.api.OpenMaya as om
import re

mc.unloadPlugin('testingPlugin.py')
mc.loadPlugin('testingPlugin.py')

def main():
    geoOneVerts = []
    geoTwoVerts = []

    edges = cmds.ls(selection=True, sn=True)

    geoOneEdgeNum = re.search(r"[([0-9_]+)]", edges[0])
    geoTwoEdgeNum = re.search(r"[([0-9_]+)]", edges[1])

    cmds.polySelect(add=True, edgeBorder=int(geoOneEdgeNum.group(1)))
    geoOneEdgeBorder = cmds.ls(selection=True, sn=True)
    geoOneEdgeVerts = cmds.polyInfo(edgeToVertex=True)

    for vertex in geoOneEdgeVerts:
        vertexPairNums = re.search(r":s*([0-9_]+)s*([0-9_]+)", vertex)
        geoOneVerts.append(vertexPairNums.group(1))
        geoOneVerts.append(vertexPairNums.group(2))

    cmds.polySelect(replace=True, edgeBorder=int(geoTwoEdgeNum.group(1)))
    geoTwoEdgeBorder = cmds.ls(selection=True, sn=True)
    geoTwoEdgeVerts = cmds.polyInfo(edgeToVertex=True)

    for vertex in geoTwoEdgeVerts:
        vertexPairNums = re.search(r":s*([0-9_]+)s*([0-9_]+)", vertex)
        geoTwoVerts.append(vertexPairNums.group(1))
        geoTwoVerts.append(vertexPairNums.group(2))

    geoOneVerts = list(set(geoOneVerts))
    geoTwoVerts = list(set(geoTwoVerts))

    # How do I use OpenMaya to compare the distance from the verts in both lists?

main()

EDIT: This code gives me two lists filled with the DAG names of vertices on two meshes. I’m unsure how to get the positions of those vertices to compare the distance between the vertices in both lists and I’m also unsure if I should be using maya.cmds for this as opposed to maya.api.OpenMaya considering the amount of vertices I’m going to be operating on.

EDIT2: Thanks to Theodox and hundreds of searches for the help. I ended up making a version that worked using boundary vertices and one that assumed paired vertices on both meshes would be in identical global space. Both of which I chose to use the Maya API and forewent Maya Commands completely for performance reasons.

Vesion1 (Using Boundary Verts):

import maya.OpenMaya as om

def main():
    geo1Verts = om.MFloatPointArray()
    geo2Verts = om.MFloatPointArray()

    selectionList = om.MSelectionList()
    om.MGlobal.getActiveSelectionList(selectionList)

    geo1SeamVerts = getSeamVertsOn(selectionList, 1)
    geo2SeamVerts = getSeamVertsOn(selectionList, 2)

    pairedVertsDict = pairSeamVerts(geo1SeamVerts, geo2SeamVerts)

def getSeamVertsOn(objectList, objectNumber):
    count = 0 
    indexPointDict = {}
    selectedObject = om.MObject()

    iter = om.MItSelectionList(objectList, om.MFn.kGeometric)
    while not iter.isDone():
        count += 1

        connectedVerts = om.MIntArray()

        if (count != objectNumber):
            iter.next()
        else:
            iter.getDependNode(selectedObject)
            vertexIter = om.MItMeshVertex(selectedObject)

            while not vertexIter.isDone():
                if (vertexIter.onBoundary()):
                    vertex = om.MPoint()
                    vertex = vertexIter.position()
                    indexPointDict[int(vertexIter.index())] = vertex

                vertexIter.next()

            return indexPointDict

def pairSeamVerts (dictSeamVerts1, dictSeamVerts2):
    pairedVerts = {}

    if (len(dictSeamVerts1) >= len(dictSeamVerts2)):
        for vert1 in dictSeamVerts1:
            distance = 0
            closestDistance = 1000000
            vertPair = 0

            for vert2 in dictSeamVerts2:
                distance = dictSeamVerts1[vert1].distanceTo(dictSeamVerts2[vert2])

                if (distance < closestDistance):
                    closestDistance = distance
                    vertPair = vert2

            pairedVerts[vert1] = vertPair

        return (pairedVerts)

    else:
        for vert1 in dictSeamVerts2:
            distance = 0
            closestDistance = 1000000
            vertPair = 0

            for vert2 in dictSeamVerts1:
                distance = dictSeamVerts2[vert1].distanceTo(dictSeamVerts1[vert2])

                if (distance < closestDistance):
                    closestDistance = distance
                    vertPair = vert2

            pairedVerts[vert1] = vertPair

        return (pairedVerts)

main()

Version2 (Assuming Paired Vertices Would Share a Global Space):

import maya.OpenMaya as om

def main():   
    selectionList = om.MSelectionList()
    om.MGlobal.getActiveSelectionList(selectionList)

    meshOneVerts = getVertPositions(selectionList, 1)
    meshTwoVerts = getVertPositions(selectionList, 2)

    meshOneHashedPoints = hashPoints(meshOneVerts)
    meshTwoHashedPoints = hashPoints(meshTwoVerts)

    matchingVertList = set(meshOneHashedPoints).intersection(meshTwoHashedPoints)

    pairedVertList = getPairIndices(meshOneHashedPoints, meshTwoHashedPoints, matchingVertList)

def getVertPositions(objectList, objectNumber):
    count = 0
    pointList = []

    iter = om.MItSelectionList(objectList, om.MFn.kGeometric)
    while not iter.isDone():
        count = count + 1
        if (count != objectNumber):
            iter.next()

        dagPath = om.MDagPath()
        iter.getDagPath(dagPath)
        mesh = om.MFnMesh(dagPath)

        meshPoints = om.MPointArray()
        mesh.getPoints(meshPoints, om.MSpace.kWorld)

        for point in range(meshPoints.length()):
            pointList.append([meshPoints[point][0], meshPoints[point][1], meshPoints[point][2]])
        return pointList

def hashPoints(pointList):
    _clamp = lambda p: hash(int(p * 10000) / 10000.00)

    hashedPointList = []

    for point in pointList:
        hashedPointList.append(hash(tuple(map(_clamp, point))))

    return (hashedPointList)

def getPairIndices(hashListOne, hashListTwo, matchingHashList):
    pairedVertIndices = []
    vertOneIndexList = []
    vertTwoIndexList = []

    for hash in matchingHashList:
        vertListOne = []
        vertListTwo = []

        for hashOne in range(len(hashListOne)):
            if (hashListOne[hashOne] == hash):
                vertListOne.append(hashOne)

        for hashTwo in range(len(hashListTwo)):
            if (hashListTwo[hashTwo] == hash):
                vertListTwo.append(hashTwo)

        pairedVertIndices.append([vertListOne, vertListTwo])

    return pairedVertIndices

main()
Asked By: Dustin Linnington

||

Answers:

API is significantly faster for the distance comparison method, but in this case I think the real killer is likely to be the algorithm. Comparing every vert to ever other is a lot of math.

Probably the easiest thing to do is to come up with a way to hash the vertices instead: turn each xyz point into a single value that can be compared with others without doing the distances: two verts with the same hash would necessarily be in the same position. You can tweak the hash algorithm to quantize the vert positions a bit to account for floating point error at the same time.

Here’s a way to hash a point (down to 4 significant digits, which you can tweak by changing the constant in _clamp) :

def point_hash(point):
    '''
    hash a tuple, probably a cmds vertex pos
    '''
    _clamp = lambda p: hash(int(p * 10000) / 10000.00)
    return hash(tuple(map(_clamp, point)))

As long as both sets of verts are hashed in the same space (presumably world space) identical hashes will mean matched verts. All you’d have to do is to loop through each mesh, creating a dictionary which keyed the vertex hash to the vertex index. Here’s a way to do it in cmds:

def vert_dict(obj):
    '''
    returns a dictionary of hash: index pairs representing the hashed verts of <obj>
    '''
    results = dict()
    verts = cmds.xform(obj + ".vtx[*]", q=True, t=True, ws=True)
    total = len(verts)/ 3
    for v in range(total):
        idx = v * 3
        hsh = point_hash (verts[idx: idx + 3])
        results[hsh] = v
    return results         

You can find the intersecting verts – the ones present in both meshes — by intersecting the keys from both dictionaries. Then convert the matching verts in both meshes back to vertex indices by looking up in the two dictionaries.

Unless the meshes are really heavy, this should be doable without the API since all the work is in the hash function which has no API analog.

The only likely issue would be making sure that the verts were in the same space. You would have to fall back on a distance based strategy if you can’t get the verts into the same space for some reason.

Answered By: theodox

If you want to get a more useable result from the op’s version 2 script (instead of returning nested and combined lists), you could do something like the following:

indices = lambda itr, val: (i for i, v in enumerate(itr) if v==val) #Get the index of each element of a list matching the given value.

matching = set(hashA).intersection(hashB)
return [i for h in matching 
    for i in zip(indices(hashA, h), indices(hashB, h))]

which will return a list of two element tuples representing the matched vertex pairs:

[(119, 69), (106, 56), (82, 32), (92, 42), ...

Also, you can use om.MSpace.kObject to compare the two mesh objects in local space depending on your specific needs.

Answered By: m3trik
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.