Iterate over UV border edges

maya

#1

Hi, is there a simple way to iterate over all UV border edges in Open Maya?

I’m currently converting a MEL script to OpenMaya to convert all border and UV edges to hard shading and all other edges to soft shading.

This is what I have so far:

import maya.api.OpenMaya as om2

selection_list = om.MGlobal.getActiveSelectionList()
selection_iter = om.MItSelectionList(selection_list)

while not selection_iter.isDone():
    node = selection_iter.getDependNode()    
    mesh = om2.MFnMesh(selection_iter.getDagPath())        
    vertices_with_same_uvs = []
    
    vertex_iter = om2.MItMeshVertex(node)
    while not vertex_iter.isDone():
        indices = vertex_iter.getUVIndices()        
        if all(x==indices[0] for x in indices):
            vertices_with_same_uvs.append(vertex_iter.index())
        vertex_iter.next()
        
    edge_iter = om2.MItMeshEdge(node)
    while not edge_iter.isDone():
        if edge_iter.onBoundary():
            mesh.setEdgeSmoothing(edge_iter.index(), False)
        elif edge_iter.vertexId(0) not in vertices_with_same_uvs and edge_iter.vertexId(1) not in vertices_with_same_uvs:
            mesh.setEdgeSmoothing(edge_iter.index(), False)
        else:
            mesh.setEdgeSmoothing(edge_iter.index(), True)
        edge_iter.next()
        
    selection_iter.next()

#2

I mean, your code works.
Unless there’s some magical “isEdgeOnUvBorder” method, the only thing I would change would be vertices_with_same_uvs = set(). That also means you’d have to change .append to .add in that second while loop.

Changing it to a set makes your code run in about 0.7s on a 100k vert sphere on my machine. On the same sphere, keeping it as a list, I wrote this entire reply and its still not done (edit: It took about 12 minutes on my machine!)… so yeah, make that change


#3

Yeah, the main win for using a set here, is when you’re doing the not in vertices_with_same_uvs checks, as a set has O(1) lookup complexity vs a list which has O(n) complexity, and given that you’re doing this in a large loop, all that time adds up fast.


#4

Thanks for that. I’ve updated it to use a set. Also, the previous code did not work correctly (Sorry I should have pointed that out). I’ve modified it so it should now be correctly setting all inner UV shell borders to soft:

import maya.api.OpenMaya as om2

# Iterate through all selected objects
selection_list = om2.MGlobal.getActiveSelectionList()
selection_iter = om2.MItSelectionList(selection_list)
while not selection_iter.isDone():
    node = selection_iter.getDependNode()    
    mesh = om2.MFnMesh(selection_iter.getDagPath())
    
    # Create dictionary with face uv indexes
    face_uv_indexes = {}
    face_vertex_iter = om2.MItMeshFaceVertex(node)
    while not face_vertex_iter.isDone():
        if face_vertex_iter.faceId() not in face_uv_indexes:
            face_uv_indexes[face_vertex_iter.faceId()] = {face_vertex_iter.getUVIndex()}
        else:
            face_uv_indexes[face_vertex_iter.faceId()].add(face_vertex_iter.getUVIndex())
            
        face_vertex_iter.next()
    
    # Iterate through all edges and set inner UV shell edges soft and other edges hard
    edge_iter = om2.MItMeshEdge(node)
    while not edge_iter.isDone():        
        faces = edge_iter.getConnectedFaces()[:]                
        if len(faces) == 2:            
            if len(face_uv_indexes[faces[0]].intersection(face_uv_indexes[faces[1]])) > 1:
                # Is inner UV shell edge, so set soft
                mesh.setEdgeSmoothing(edge_iter.index(), True)
            else:
                # Is UV shell edge, so set hard
                mesh.setEdgeSmoothing(edge_iter.index(), False)            
        else:
            # Is border edge, so set hard
            mesh.setEdgeSmoothing(edge_iter.index(), False)
            
        edge_iter.next()
        
    selection_iter.next()

#5

I have been accused on many occasions of trying to be too clever when I code (so take this with a grain of salt), but I use this one all the time, and I think it’s neat! :-D. Because I like to play code golf, there is a pattern I use whenever I’m building a dictionary where the values are lists (or sets in your case).

For instance, say you have the list [1, 2, 3, 4, 5, 1, 2, 3, 4, 5] and you want to build a dictionary where the key is the number from the list and the value is the list of all indexes it appears in, like this:
{1: [0, 5], 2: [1, 6], 3: [2, 7], 4: [3, 8], 5: [4, 9]}

You can do something like this:

myList = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
myDict = {}
for idx, val in enumerate(myList):
    myDict.setdefault(val, []).append(idx)

The trick is that setdefault not only sets myDict[val] = [] if val isn’t already in the dict, but it also returns whatever value is there. That lets you immediately .append to it. In your case, you could use it on lines 14-17.


#6

Because I’m not much of a fan of dict.setdefault, I prefer to use collections.defaultdict for this kind of thing instead.

Code always seems less readable with setdefault for some reason.

from collections import defaultdict
my_dict = defaultdict(list)
my_list = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
for idx,  val in enumerate(my_list):
    my_dict[idx].append(val)

documentation.