[Maya API] Get the vertices of selected object

Hey all,

I am trying to write a little check to ensure that the lowest vertices on a selected object is not below Y 0. My thinking was I would just get each of the vertices for the selected object and just loop through checking the translate of Y for each of them and if I see 0 or less stop.

I have tried to get the list of vertices but I am struggling to find what I need. I am pretty new to working with MAYA and somewhat struggle with their docs as well, so if there are other resources people use for MAYA that would also be a massive help.

Thanks

1 Like

Here’s a python function that does what you want I believe

import maya.OpenMaya as om


def getVerticesBelowHeight(mesh_name, height):
    """
    Gets all the vertices that are below the given height (y-axis).
    Args:
        mesh_name (string): Name of mesh to find vertices below given y value
        height (float): Y axis value to find vertices that are below the y value.
    Returns:
        (list): mesh_name.vtx[i] of all vertices that are below the height value in y-axis.
    """
    # Get Api MDagPath for object
    vertices = []
    mesh_path = om.MDagPath()
    mesh_selection = om.MSelectionList()
    mesh_selection.add(mesh_name)
    mesh_selection.getDagPath(0, mesh_path)

    # Iterate over all the mesh vertices and get vertices below the given height
    vertex_i = om.MItMeshVertex(mesh_path)
    while not vertex_i.isDone():

        if vertex_i.position(om.MSpace.kWorld).y < height:
            vertices.append(mesh_name + '.vtx[' + str(vertex_i.index()) + ']')

        next(vertex_i)

    return vertices

Here’s a relatively easy newbie version that doesn’t need the API, in case that’s the stumbling block:

import maya.cmds as cmds


def get_verts_below(obj, comparison):
    results = []
    
    # get all the vertices in the object
    vertlist = cmds.polyListComponentConversion(obj, tv=True)
    
    # this get all of their world-space positions as a flat list:    
    raw_positions = cmds.xform(vertlist, q=True, translation=True, worldSpace=True)
    
    # grab the y components, which which will be index 1 of every three (ie,  012012012...)
    y_positions = [raw_positions[i] for i in range(len(raw_positions)) if i % 3 == 1]
        
    # grab all the ones that are below your threshold.  `enumerate` counts indices for you:
    for index, ypos in enumerate(y_positions):
        if ypos <= comparison:
            results.append(f"{obj}.vtx[{index}]")
    
    #return the list
    return results
                
get_verts_below("pSphere1", 0)
# ['pSphere1.vtx[0]', 'pSphere1.vtx[1]', 'pSphere1.vtx[2]', 'pSphere1.vtx[3]', 'pSphere1.vtx[4]', 'pSphere1.vtx[5]', 'pSphere1.vtx[6]', 'pSphere1.vtx[7]', 'pSphere1.vtx[8]', 'pSphere1.vtx[9]', 'pSphere1.vtx[10]', 'pSphere1.vtx[11]', 'pSphere1.vtx[12]', 'pSphere1.vtx[13]', 'pSphere1.vtx[14]', 'pSphere1.vtx[15]', 'pSphere1.vtx[16]', 'pSphere1.vtx[17]', 'pSphere1.vtx[18]', 'pSphere1.vtx[19]', 'pSphere1.vtx[20]', 'pSphere1.vtx[21]', 'pSphere1.vtx[22]', 'pSphere1.vtx[23]', 'pSphere1.vtx[24]', 'pSphere1.vtx[25]', 'pSphere1.vtx[26]', 'pSphere1.vtx[27]', 'pSphere1.vtx[28]', 'pSphere1.vtx[29]', 'pSphere1.vtx[30]', 'pSphere1.vtx[31]', 'pSphere1.vtx[32]', 'pSphere1.vtx[33]', 'pSphere1.vtx[34]', 'pSphere1.vtx[35]', 'pSphere1.vtx[36]', 'pSphere1.vtx[37]', 'pSphere1.vtx[38]', 'pSphere1.vtx[39]', 'pSphere1.vtx[40]', 'pSphere1.vtx[41]', 'pSphere1.vtx[42]', 'pSphere1.vtx[43]', 'pSphere1.vtx[44]', 'pSphere1.vtx[45]', 'pSphere1.vtx[46]', 'pSphere1.vtx[47]', 'pSphere1.vtx[48]', 'pSphere1.vtx[49]', 'pSphere1.vtx[50]', 'pSphere1.vtx[51]', 'pSphere1.vtx[52]', 'pSphere1.vtx[53]', 'pSphere1.vtx[54]', 'pSphere1.vtx[55]', 'pSphere1.vtx[56]', 'pSphere1.vtx[57]', 'pSphere1.vtx[58]', 'pSphere1.vtx[59]', 'pSphere1.vtx[60]', 'pSphere1.vtx[61]', 'pSphere1.vtx[62]', 'pSphere1.vtx[63]', 'pSphere1.vtx[64]', 'pSphere1.vtx[65]', 'pSphere1.vtx[66]', 'pSphere1.vtx[67]', 'pSphere1.vtx[68]', 'pSphere1.vtx[69]', 'pSphere1.vtx[70]', 'pSphere1.vtx[71]', 'pSphere1.vtx[72]', 'pSphere1.vtx[73]', 'pSphere1.vtx[74]', 'pSphere1.vtx[75]', 'pSphere1.vtx[76]', 'pSphere1.vtx[77]', 'pSphere1.vtx[78]', 'pSphere1.vtx[79]', 'pSphere1.vtx[80]', 'pSphere1.vtx[81]', 'pSphere1.vtx[82]', 'pSphere1.vtx[83]', 'pSphere1.vtx[84]', 'pSphere1.vtx[85]', 'pSphere1.vtx[86]', 'pSphere1.vtx[87]', 'pSphere1.vtx[88]', 'pSphere1.vtx[89]', 'pSphere1.vtx[90]', 'pSphere1.vtx[91]', 'pSphere1.vtx[92]', 'pSphere1.vtx[93]', 'pSphere1.vtx[94]', 'pSphere1.vtx[95]', 'pSphere1.vtx[96]', 'pSphere1.vtx[97]', 'pSphere1.vtx[98]', 'pSphere1.vtx[99]', 'pSphere1.vtx[100]', 'pSphere1.vtx[101]', 'pSphere1.vtx[102]', 'pSphere1.vtx[103]', 'pSphere1.vtx[104]', 'pSphere1.vtx[105]', 'pSphere1.vtx[106]', 'pSphere1.vtx[107]', 'pSphere1.vtx[108]', 'pSphere1.vtx[109]', 'pSphere1.vtx[110]', 'pSphere1.vtx[111]', 'pSphere1.vtx[112]', 'pSphere1.vtx[113]', 'pSphere1.vtx[114]', 'pSphere1.vtx[115]', 'pSphere1.vtx[116]', 'pSphere1.vtx[117]', 'pSphere1.vtx[118]', 'pSphere1.vtx[119]', 'pSphere1.vtx[120]', 'pSphere1.vtx[121]', 'pSphere1.vtx[122]', 'pSphere1.vtx[123]', 'pSphere1.vtx[124]', 'pSphere1.vtx[125]', 'pSphere1.vtx[126]', 'pSphere1.vtx[127]', 'pSphere1.vtx[128]', 'pSphere1.vtx[129]', 'pSphere1.vtx[130]', 'pSphere1.vtx[131]', 'pSphere1.vtx[132]', 'pSphere1.vtx[133]', 'pSphere1.vtx[134]', 'pSphere1.vtx[135]', 'pSphere1.vtx[136]', 'pSphere1.vtx[137]', 'pSphere1.vtx[138]', 'pSphere1.vtx[139]', 'pSphere1.vtx[140]', 'pSphere1.vtx[141]', 'pSphere1.vtx[142]', 'pSphere1.vtx[143]', 'pSphere1.vtx[144]', 'pSphere1.vtx[145]', 'pSphere1.vtx[146]', 'pSphere1.vtx[147]', 'pSphere1.vtx[148]', 'pSphere1.vtx[149]', 'pSphere1.vtx[150]', 'pSphere1.vtx[151]', 'pSphere1.vtx[152]', 'pSphere1.vtx[153]', 'pSphere1.vtx[154]', 'pSphere1.vtx[155]', 'pSphere1.vtx[156]', 'pSphere1.vtx[157]', 'pSphere1.vtx[158]', 'pSphere1.vtx[159]', 'pSphere1.vtx[160]', 'pSphere1.vtx[161]', 'pSphere1.vtx[162]', 'pSphere1.vtx[163]', 'pSphere1.vtx[164]', 'pSphere1.vtx[165]', 'pSphere1.vtx[166]', 'pSphere1.vtx[167]', 'pSphere1.vtx[168]', 'pSphere1.vtx[169]', 'pSphere1.vtx[170]', 'pSphere1.vtx[171]', 'pSphere1.vtx[172]', 'pSphere1.vtx[173]', 'pSphere1.vtx[174]', 'pSphere1.vtx[175]', 'pSphere1.vtx[176]', 'pSphere1.vtx[177]', 'pSphere1.vtx[178]', 'pSphere1.vtx[179]', 'pSphere1.vtx[180]', 'pSphere1.vtx[181]', 'pSphere1.vtx[182]', 'pSphere1.vtx[183]', 'pSphere1.vtx[184]', 'pSphere1.vtx[185]', 'pSphere1.vtx[186]', 'pSphere1.vtx[187]', 'pSphere1.vtx[188]', 'pSphere1.vtx[189]', 'pSphere1.vtx[190]', 'pSphere1.vtx[191]', 'pSphere1.vtx[192]', 'pSphere1.vtx[193]', 'pSphere1.vtx[194]', 'pSphere1.vtx[195]', 'pSphere1.vtx[196]', 'pSphere1.vtx[197]', 'pSphere1.vtx[198]', 'pSphere1.vtx[199]', 'pSphere1.vtx[380]']

This does the same thing and uses the same logic, but it’s a bit faster, especially on big meshes. The difference is that we don’t create the list of results in memory and append to it, instead we use yield to return the results one at a time, and we also don’t make the intermediate list of y values:

def get_verts_below_iterator(obj, comparison):
    # results = []  --- no need to declare a result list 
    
    # get all the vertices in the object
    vertlist = cmds.polyListComponentConversion(obj, tv=True)
    
    # this get all of their world-space positions as a flat list:    
    raw_positions = cmds.xform(vertlist, q=True, translation=True, worldSpace=True)
    
    # using parens instead of square brackes turns this into a generator
    y_positions = (raw_positions[i] for i in range(len(raw_positions)) if i % 3 == 1)
        
    # grab all the ones that are below your threshold.  `enumerate` counts indices for you:
    for index, ypos in enumerate(y_positions):
        if ypos <= comparison:
            yield (f"{obj}.vtx[{index}]")

The big difference here is that get_verts_below_iterator() will produce a stream of vertices that are below the threshold instead of a list. You could still make them into a list if you are doing something where you need them all at once --but if you don’t, you save time by not moving around big chunks of memory.

2 Likes

Well, I’ll try to contribute :slight_smile:
Since the xform variant has already been described in detail, we will try other approaches.
First, you can list all the mesh vertices with the string:
'mesh_name.vtx[*]'
and then pass that string as an argument to the cmds.ls command.
However, if you need a list with individual components, you can immediately specify the flatten = True key.
cmds.ls('mesh_name.vtx[*]', flatten = True)
With the cmds.polyListComponentConversion command, you cannot get a flatten list of components in one go, as this command always returns a non flatten list of components.
Also, if possible, try not to use the polyListComponentConversion command because it has very poor performance.
Of course, creating a flatten list for all mesh vertices at once may not seem like the best idea.
Using the command cmds.polyEvaluate(mesh_name, vertex = True) we can get the number of vertices in a given mesh and use it in a range loop.
In both cases, the difference in speed will be insignificant.
Of course, when possible and acceptable, use Maya Python API 2. It’s not as cumbersome as writing code in Maya C++ API (In principle, it’s as fast as writing with regular Maya Python), but almost always gives a huge increase in execution speed (sometimes this gives a speed increase of tens of thousands of times).
I assumed that you would not only want to get a list of down vertices, but also their y-positions.
Therefore, my implementations return a dictionary in which the names of the vertices are the keys and their y-positions are the values.
Also, I suggested that it would be useful to implement the simplest check for a valid select.
This is a very simple check: If a transform, shape, or mesh components is selected, the select is returned as a shape. If the selection is empty, or if more than one shape is returned, then a warning message is displayed.
For classic Maya python code, the check is implemented in a separate function.
Implementations by no means claim to be exhaustive and are not suitable for use in production! This is just an attempt to demonstrate possible and diverse approaches to solving the problem.

Harmony and good luck!

import maya.cmds as cmds
import maya.api.OpenMaya as om2

def maya_useNewAPI():
    pass

maya_useNewAPI = True

# Displays a warning if the selection is incorrect or empty
def check_select_warning():
    cmds.warning('\n\t Select one polymesh or one polymesh shape or components in one shape!\n')


# Maya Python API 2 version.
# In this version, valid selection check is implemented directly in this function itself.
def check_vertices_y_position_version_00(control_position = 0.0):
    sel = om2.MGlobal.getActiveSelectionList()
    if not sel.length():
        check_select_warning()
        return 0
    else:
        nodes = []
        for i in range(sel.length()):
            try:
                item_node = sel.getDagPath(i).extendToShape()
                if not item_node in nodes:
                    if not nodes:
                        nodes.append(item_node)
                    else:
                        check_select_warning()
                        return 0
            except:
                pass
        if not nodes:
            check_select_warning()
            return 0
    node = nodes[0]
    it = om2.MItMeshVertex(node)
    node_str_path_name = node.fullPathName()
    vertices_below_zero_list_and_pozition = {}
    while not it.isDone():
        y_pozition = it.position(4).y        # "4" or "space = 4" or "om2.MSpace.kWorld"
        if y_pozition < control_position:    # om2.MSpace.kInvalid == 0, kTransform == 1, kObject == 2, kPreTransform == 2, kPostTransform == 3, kWorld == 4, , kLast == 5
            vertex = '{}.vtx[{}]'.format(node_str_path_name, it.index())
            vertices_below_zero_list_and_pozition[vertex] = y_pozition
        it.next()
    if vertices_below_zero_list_and_pozition:
        print(('\n\t The selected mesh (\"{}\") contains {} vert. with a Y-coordinate below control position (in world space).\n'
              ).format(node_str_path_name, len(vertices_below_zero_list_and_pozition))
             )
        return vertices_below_zero_list_and_pozition
    else:
        print(('\n\t The selected mesh (\"{}\") not contains vertices with a Y-coordinate below control position (in world space).\n'
              ).format(node_str_path_name)
             )
        return 0


# This function implements a valid selection check.
# Transforms, Shapes, and Components Return a Sape/
# If no shape (only one) is returned (or the selection is empty), a warning is displayed.
def check_correct_select():
    all_selection = set(cmds.ls(selection = True, flatten = True, transforms = True, shapes = True, long = True, objectsOnly = True))
    to_shapes = set()
    for item in all_selection:
        if cmds.objectType(item) == 'transform':
            to_shapes.update(set(cmds.listRelatives(all_selection, shapes = True, fullPath = True)))
        if cmds.objectType(item) == 'mesh':
            to_shapes.add(item)
    if to_shapes and len(to_shapes) == 1:
        selected_mesh = list(to_shapes)[0]
    else:
        check_select_warning()
        return 0
    return selected_mesh


# Classic version with using Maya Python Commands (cmds).
# Variant with cmds.ls('selected_mesh.vtx[*]', flatten = True)
def check_vertices_y_position_version_01(control_position = 0.0):
    selected_mesh = check_correct_select()
    if selected_mesh:
        all_vertices = '{}.vtx[*]'.format(selected_mesh)    # vtx[*] - means all vertices from this mesh
        all_vertices_flatten_list = cmds.ls(all_vertices, flatten = True, long = True, absoluteName = True)
        vertices_below_zero_list_and_pozition = {}
        for item in all_vertices_flatten_list:
            y_pozition = cmds.pointPosition(item, world = True)[1]
            if y_pozition < control_position:
                vertices_below_zero_list_and_pozition[item] = y_pozition
        if vertices_below_zero_list_and_pozition:
            print(('\n\t The selected mesh (\"{}\") contains {} vert. with a Y-coordinate below control position (in world space)!\n'
                  ).format(selected_mesh, len(vertices_below_zero_list_and_pozition))
                 )
            return vertices_below_zero_list_and_pozition
        else:
            print(('\n\t The selected mesh (\"{}\") not contains vertices with a Y-coordinate below control position (in world space).\n'
                  ).format(selected_mesh)
                 )
            return 0
    else:
        return 0


# Classic version with using Maya Python Commands (cmds).
# Variant with cmds.polyEvaluate(selected_mesh, vertex = True).
def check_vertices_y_position_version_02(control_position = 0.0):
    selected_mesh = check_correct_select()
    if selected_mesh:
        vertices_count = cmds.polyEvaluate(selected_mesh, vertex = True)    # Get count vertices from this mesh.
        vertices_below_zero_list_and_pozition = {}
        for i in range(vertices_count):
            vertex = '{}.vtx[{}]'.format(selected_mesh, i)
            y_pozition = cmds.pointPosition(vertex, world = True)[1]    # Get vertex position in world space.
            if y_pozition < control_position:
                vertices_below_zero_list_and_pozition[vertex] = y_pozition
        if vertices_below_zero_list_and_pozition:
            print(('\n\t The selected mesh (\"{}\") contains {} vert. with a Y-coordinate below control position (in world space)!\n'
                  ).format(selected_mesh, len(vertices_below_zero_list_and_pozition))
                 )
            return vertices_below_zero_list_and_pozition
        else:
            print(('\n\t The selected mesh (\"{}\") not contains vertices with a Y-coordinate below control position (in world space).\n'
                  ).format(selected_mesh)
                 )
            return 0
    else:
        return 0

down_vertices = check_vertices_y_position_version_00()
# or
down_vertices = check_vertices_y_position_version_01()
# or
down_vertices = check_vertices_y_position_version_02()

Useful information:

Python in Maya

Maya Developer Help

Maya Python API 2.0 Reference

Python Commands

pointPosition

polyEvaluate

ls

listRelatives

polyListComponentConversion

xform

1 Like

Obviously, if you don’t use the Maya API, then the method using the cmds.xform command in request mode is the fastest:
cmds.xform('mesh_name.vtx[*]', query = True, translation = True, worldSpace = True)
Using the Maya Python API, we can take a similar approach by requesting a list of the positions of all the mesh’s vertices in one go, using the getPoints from MFnMesh function:

import maya.cmds as cmds
import maya.api.OpenMaya as om2

def maya_useNewAPI():
    pass

maya_useNewAPI = True

# For clarity, we skip checking the correctness of the select,
# and display only the names of down-vertices in the list

def fast(control_position = 0.0):
    sel = om2.MGlobal.getActiveSelectionList()
    node = sel.getComponent(0)[0]
    node_str_path_name = node.fullPathName()
    fn_mesh = om2.MFnMesh(node)
    points = fn_mesh.getPoints(space = 4)    # "4" or "space = 4" or "om2.MSpace.kWorld"
    down_vertices = []
    for i in range(len(points)):
        if points[i].y < control_position:
            down_vertices.append('{}.vtx[{}]'.format(node_str_path_name, i))
    return down_vertices

down_vertices = fast()

All this is of course wonderful, but I would like to know how much some methods are faster than others?
For testing, let’s take the first mesh containing 800 vertices (at the same time, 400 vertices will have “y-positions” less than 0.0).
For testing, let’s take the second mesh containing 800,000 vertices (at the same time, 400,000 vertices will have “y-positions” less than 0.0).
Obviously, some iterative methods will slightly increase the lag as the number of vertices increases.
If we take the performance of the function (using getPoints) as a unit, then the performance will be related something like this:

				Features:										Time 01		Ratio 01	Time 02		Ratio 02
1. Using `getPoints` from `maya.api.OpenMaya.MFnMesh`		=	0.00032 s.		1		00.2613 s.		1
2. Using `position` from `maya.api.OpenMaya.MItMeshVertex`	=	0.00067 s.		2.1		00.6901 s.		2.6
3. Using `translation` `query` from `cmds.xform`			=	0.00225 s.		7.1		02.1703 s.		8.3
4. Using `cmds.polyEvaluate` and `cmds.pointPosition`		=	0.02753 s.		86		29.2716 s.		112
5. Using `flatten` from `cmds.ls` and `cmds.pointPosition`	=	0.03010 s.		95		33.1634 s.		126

The results do not mean that cmds.pointPosition is an order of magnitude slower than cmds.xform!
The fact is that we execute cmds.xform only once, and the cmds.pointPosition command is executed for each vertex!
For a single vertex, cmds.pointPosition is one and a half times faster than cmds.xform!

However, in our use case, a function using cmds.xform is an order of magnitude (10 times) more efficient than a function using cmds.pointPosition iteratively.
And, of course, functions using maya.api.OpenMaya are one or two orders of magnitude faster than the usual maya.cmds (10-100 times).
However, the iterative approach using maya.api.OpenMaya.MItMeshVertex is almost twice as slow as using getPoints once.
And the backlog increases with the growth of the number of processed vertices.

Your Captain… :slight_smile:

1 Like

I want to thank you all for the help with this I did end up going with the xform solution that @Theodox gave with some alterations as I just needed it to return if any of the vertexes where below 0. Special thanks to @VVVSLAVA you have provided me with some incredible information and the length you went to with your answer is fantastic. Thank you very much with this it should help me with getting to grips with maya and python.

1 Like

I’m glad if I could help a little :slight_smile:
By the way, I advise you to pay especially close attention to the second code example from @Theodox (using “yield” to return a generator).
This is indeed a very important fundamental concept in Python.
If reading the official Python documentation doesn’t give you an exhaustive understanding of generators/iterators, then I recommend Kevin Samuel’s popular exposition:

What does the “yield” keyword do?

PS: I apologize for my terrible and sloppy English…

1 Like