Why iterators, and how do they work? in Maya API / OpenMaya

Hey guys.

Yea I know the title sounds a little negative, but I’m really just curious here.

Why do we have Iterators like MItMeshFace and MItMeshVertex instead of just functions in the functionsets?
Is it to avoid copying out huge lists, editing them and copying them back in?

Do iterators do some kind of magic by the way they are traversing the datastructures?

Is it bad practice to not use next to traverse them, but instead just jump around in the index?

The last question is because I used a MItMeshVertex iterator in parallel with an MItMeshEdge iterator,
to go through all vertices in a mesh, find connected edges and just feed those returned indices into the MItMeshEdge iterator to work on.
Because i could not figure out how to make an iterator only run through a predefined list of indices and because it seemed expensive to initialize a new iterator every time (Not that I know…).

I since realized I could have used the edge indices to query through an MFnMesh and I still feel I did something wrong with not using a iterator “as I should” so I wanted to ask the experts for help.

Thanks!

For those curious, my script is for baking curvature into vertices and though it’s a little messy it works fine. Here it is:


#Color by Curvature
#by Martin Baadsgaard
#v. 0.8.1

import pymel.core as pm
import pymel.core.datatypes as dt
import maya.api.OpenMaya as om
from pymel.all import *
import random as rand

class colorByCurvature ():
    def __init__(self, *args):
        if pm.window("CbCWin", exists=True):
            deleteUI("CbCWin")
        self.abortBool = False
    def doColoring(self, *args):
        #Get vertices and also a clean list of their indices
        
        colorList = []
        vertNormal = om.MVector()
        length = 0.0

        selection = om.MGlobal.getActiveSelectionList()

        if not selection.isEmpty():
            dag = selection.getDagPath(0)
            mesh = om.MFnMesh(dag)
            mainList = []
            vert_itr = om.MItMeshVertex(dag)
            edge_itr = om.MItMeshEdge(dag)
           
            self.progressor.setEnable(True)  
            self.progressor.setMaxValue(vert_itr.count()) 
            while not vert_itr.isDone():
                vertNormal = vert_itr.getNormal()
                vertEdges = vert_itr.getConnectedEdges()
                #Get edge properties                
                edgeAngles = []
                edgeLengths = []
                edgeLenMax = 0
                p1 = vert_itr.position()

                for ind in vertEdges:
                    edge_itr.setIndex(ind)
                    edgeLength = edge_itr.length()
                    edgeLengths.append(edgeLength)
                    edgeLenMax = max(edgeLenMax, edgeLength)
                    conVerts = [edge_itr.vertexId(x) for x in [0,1]]
                    
                    if conVerts[0] == vert_itr.index(): 
                        p2 = edge_itr.point(1)
                    else: 
                        p2 = edge_itr.point(0)

                    px = p2-p1
                    om.MVector.normalize(px)
                    edgeAngles.append(px)
                
                
                
                edgeDotMax = 0.0
                edgeDotMaxLen = 0
                
                edgeDotMin = 1000000.0
                edgeDotMinLen = 1
                
                
                #Calculate edge weightings, We find the highest and lowest only, to avoid "dilution" by number of edges branching this vertex
                for edgeW in range(len(edgeAngles)):
                    
                    edgeLen = edgeLengths[edgeW]/edgeLenMax
                    edgeLen = 1 + ((self.edgeDistBias.getValue())/100)*(edgeLen-1)
                    edgeDot = vertNormal*edgeAngles[edgeW]
                    if edgeDot > edgeDotMax:
                        edgeDotMax = edgeDot
                        edgeDotMaxLen = edgeLen
                        
                    if edgeDot < edgeDotMin:
                        edgeDotMin = edgeDot
                        edgeDotMinLen = edgeLen
                        

                #print("Maximum edge angle: " + str(edgeDotMax)) 
                #print("Minimum edge angle: " + str(edgeDotMin)) 
                edgeDotAverage = -(edgeDotMax*edgeDotMaxLen+edgeDotMin*edgeDotMinLen)*self.edgeContrast.getValue()+0.5
                
                

                col = om.MColor()
                col.r = edgeDotAverage
                col.g = edgeDotAverage
                col.b = edgeDotAverage
                col.a = 1
                colorList.append(col)
                self.progressor.step(1)
                vert_itr.next()
            print mesh
            mesh.setVertexColors(colorList, range(mesh.numVertices))
            mesh.updateSurface()
        pm.polyColorPerVertex (colorDisplayOption=True)

        self.progressor.setEnable(False)
        self.progressor.setProgress(0)
        

               
    def toggleView(self, *args):
       mel.toggleShadeMode()
    
    def ui(self):
        
        self.win = pm.window("CbCWin", title="Color vertices by Curvature")
        self.layout = pm.columnLayout()
        self.edgeDistBias = pm.floatSliderGrp(label="Edge Distance Bias", columnWidth3=(100, 40, 120), field=True)
        self.edgeContrast = pm.floatSliderGrp(label="Curvature Contrast", columnWidth3=(100, 40, 120), field=True, value=0.5, min=0, max=2)
        self.blendBtn = pm.button(label="Color by Curvature", command=self.doColoring)
        self.viewButton = pm.button(label="Toggle Vertex Color Display", command=self.toggleView)
        #self.angleWeightBool = pm.checkBox(label="Use Angleweighted Normals", value=True)
        self.progressor = pm.progressBar(enable=False, progress=0, width=260)
        #pm.setParent('..')
        self.win.show()



myUI = colorByCurvature()
myUI.ui()

There are two main reasons for iterators.

First, C++ is a low-level language (I’m being polite here, if this wasn’t a family forum I’d use another adjective!) . In order to do a vanilla iteration in C++ you have to do boilerplate like:

for (int i ; i < mesh.numVerts() ; i++) {
    MPoint this_vert = mesh.GetPoint(i);
    // ... do actual work
}

This is a mental tax. More importantly, though, it’s based on an assumption that isn’t always correct… You might want to iterate in lots of ways, not just from 0 to N directly. Iterators are popular because they let you separate out the logic of traversing a series of values from the processing. You might, for example, want to evaluate the terms lazily (when you’re ready to work on them, instead of dumping them all into a list in memory). You might want to combine iterations and sub-iterations over lot of things into one pass. Or you might just want to do it efficiently at a low level using something like a c++ linked list of pointers instead of a list of complete objects.

In all these cases, an iterator object lets you split out the business of iterating… which may have all sorts of complexities of its own… from the things you actually want to work on. If you look at how hard it is to loop over something like Maya’s face-vertex combinations or UV-physical vertex pairs you can see why this is valuable.

So it’s hardly surprising that they show up in Maya – they’re generally a good practice in lots of contexts.

1 Like

Alright, cool, thanks!

So I guess my “jumping around” by just changing iterator index is not a problematic approach.

Yea I kind of like how iterators work.
Just trying to piece together some best practices.

So I should not consider it a performance question whether I want to work on lists through MFnMesh or use iterators like MItMeshVertex, except for the fact that I might create gigantic lists by calling, like, MFnMesh.getVertices or something.

Thankyou for clearing it up :slight_smile:

Well, jumping around large memory structures like vertex data has serious drawbacks. You really want your algorithm to be as sequential as possible. Read up on cpu caching. Also, I find iterators to be least readable due to their hidden “magic”, compared with traditional loops or raw pointer arithmetic.

In python API work the iterators will probably be faster than writing the equivalent to a for {int i; i<x; i++} loop. In raw C++ it depends a lot on what you’re doing.

I’d be curious to hear more about how to use maya api iterators for non-sequential component access. Is the OP’s example of calling setIndex an on MItMeshEdge iterator the best method, or is gathering the vertex indices in a preliminary loop and then building an iterator that only travels those indices a better method? Is there another way?

How you judge “better”? Easy to write and easy to read, best performance, other?

Iterators work best for readability (most of the time, if used in “while more elements available, go to next” fashion). In Maya’s case, it might also mean they point directly to internal data (while “getPoints” would duplicate data in memory).

Performance is really something that needs to be measured. Like commented above, python API is much more performant with iterator, while in C++ your fastest memory access is probably something like…



Vertex * listOfVertices = <contiguous block of structs>
const size_t listLength = <known length in advance>;

Vertex * const end = listOfVertices+listLength;
for (Vertex *curr=listOfVertices;curr!=end;curr++){
    const Vertex &v = *curr; // probably not necessary, but additional hint for a compiler that we're not changing "curr" value inside loop block

    // do something with v.x, v.y, v.z
}


I understand this duplicates a lot of what std::vector would provide, but I still find raw pointers superior in speed. there’s not much magic that std::vector can do for you if you know the size of your structure in advance, and it’s much more implicit what you’re doing.

In any case, only do this is you find your algorithm to be absolute bottleneck of performance. In many cases for Maya that happens to be it’s own API, for example, updating MPlug values is vastly slower than computing them. Otherwise, fall back to higher level structures like stl or Maya containers and abstractions in order to not sacrifice the readability and robustness of your code.