Funny story: I actually got to use the code I wrote for you the other day at work!
That means I got to flesh out that code into something a bit more production ready. (Also, this geometry connectivity stuff is my absolute favorite task, so I maaaayyyy have gone a bit overboard )
One annoying thing (as you found out) is the string parsing. I handle that with mayaSelRange
Then I get the neighbors with buildNeighborDict
. No pickwalking required. Also, I have a very strong aversion to doing anything with selection during the execution of a script.
Then with a little thought, I figured out how to handle multiple loops and loops that donβt connect back to themselves. Thats what sortLoops
does.
from maya import cmds
def mayaSelRange(vals):
"""Convert maya cmds.ls() component selection list into indices
Arguments:
vals (list): A list of components like what you get out of cmds.ls(sl=True)
Returns:
list: A list of integer indices
"""
out = []
for val in vals:
nn = val.split('[')[1][:-1].split(':')
nn = list(map(int, nn))
out.extend(range(nn[0], nn[-1] + 1))
return out
def buildNeighborDict(vertColl):
""" Parse vertices into a dictionary of neighbors, limited to the original vertex set
Arguments:
vertColl (list): A list of verts like what you get out of cmds.ls(sl=True)
Returns:
dict: A dictionary formatted like {vertIndex: [neighborVertIdx, ...], ...}
"""
# Get the object name
objName = vertColl[0].split('.')[0]
verts = set(mayaSelRange(vertColl))
neighborDict = {}
for v in verts:
vname = '{0}.vtx[{1}]'.format(objName, v)
edges = cmds.polyListComponentConversion(vname, fromVertex=True, toEdge=True)
neighbors = cmds.polyListComponentConversion(
edges, fromEdge=True, toVertex=True
)
neighbors = set(mayaSelRange(neighbors))
neighbors.remove(v)
neighborDict[v] = list(neighbors & verts)
return neighborDict
def sortLoops(neighborDict):
"""Sort vertex loop neighbors into individual loops
Arguments:
neighborDict (dict): A dictionary formatted like {vertIndex: [neighborVertIdx, ...], ...}
Returns:
list of lists: A list of lists containing ordered vertex loops.
Only if the loop is closed, the first and last element will be the same.
"""
neighborDict = dict(neighborDict) # work on a copy of the dict so I don't destroy the original
loops = []
# If it makes it more than 1000 times through this code, something is probably wrong
# This way I don't get stuck in an infinite loop like I could with while(neighborDict)
for _ in range(1000):
if not neighborDict:
break
vertLoop = [neighborDict.keys()[0]]
vertLoop.append(neighborDict[vertLoop[-1]][0])
# Loop over this twice: Once forward, and once backward
# This handles loops that don't connect back to themselves
for i in range(2):
vertLoop = vertLoop[::-1]
while vertLoop[0] != vertLoop[-1]:
nextNeighbors = neighborDict[vertLoop[-1]]
if len(nextNeighbors) == 1:
break
elif nextNeighbors[0] == vertLoop[-2]:
vertLoop.append(nextNeighbors[1])
else:
vertLoop.append(nextNeighbors[0])
# Remove vertices I've already seen from the dict
# Don't remove the same vert twice if the first and last items are the same
start = 0
if vertLoop[0] == vertLoop[-1]:
start = 1
for v in vertLoop[start:]:
del neighborDict[v]
loops.append(vertLoop)
else:
raise RuntimeError("You made it through 1000 loops, and you still aren't done? Something must be wrong")
return loops