Is there a command in maya that will delete all stray verts on a mesh?

Am very surprised “mesh” > “clean up…”, does not offer a way to remove all verts that are “stray verts”.

Maybe it has its own command, that is found somewhere else? For the life of me, I have not found it and its proving to be frustrating deleting these kinds of verts manually.

am on maya 2025.

Double click a face while in face mode which will select all linked, then invert selection to get anything not connected to it.

PolyClean
PolyClean will attempt to remove components that are invalid in the description of a poly mesh.
In query mode, return type is based on queried flag.

flag cleanVertices(cv)

If true, the operation will look for and delete vertices that are not associated with any face in the mesh.
PolyClean

Or use the module to check and correct polygeometry:
Mesh → Cleanup… → Invalid Component
Cleanup Options

To the existing replies, Ralf doesn’t mean stray or unconnected or invalid, if you look at the attached image, he’s just talking about verts that sit in the middle of an edge. Completely valid, but no use in this context. I think these are known as “winged” or “non-winged” vertices (I never remember which way round it is :smiley: ).

Looking at your image it would appear these verts only exist in the first place Ralf because you selected edge loops and hit delete to delete them, which leaves their vertices behind. You can mitigate this somewhat by using Ctrl-Delete to delete edges instead, which WILL remove their vertices. You can also remove them in one go by going into vertex mode, selecting ALL vertices (marquee or just double click one) and hitting delete. This will delete only “winged” vertices and keep the “non-winged” (or vice-versa).

However, this method is not 100% safe on every mesh. It should work fine on solid/closed meshes, but will also sometimes delete verts that describe important angles on border edges for example, so use with caution.

I actually wrote a script that did this kind of vertex clean-up many years ago (probably 20 years ago!) I’ll see if I can find it and I’ll post it here if nobody else does before that.

2 Likes

Personal workflow: I usually end up doing a “select all verts”, then doing a “shrink selection” using the < (ie. shift + comma) to exclude the border. Then I hit delete. It may leave extra verts along the border, but those are easily cleaned up individually.

Of course, you could also “select all”, then just deselect the (hopefully just a couple) of corner verts.

At some point its just personal preference.

1 Like

Aah, so that is what they are called! lol, I would not remember this too, I am not even sure what “winged” (or non winged) seeks to describe.

Yes “ctrl”+ “delete” is a solid approach, but sometimes I tend to forgot that, there are so many things viying for my attention in Maya its insane… Also, as am sure you know, some tools let you insert these winged points too, like the multi cut tool’s subdivisions setting.

Hitting delete in vertex mode, on selected vertices is a great solution, I was under the impression this would delete all verts but I was so wrong.

I would love to see that script, lol.

Thank you man and to everyone else too.

Removing “winged vertices” is not a trivial task and depends on the context.
In general, there is a standard command designed specifically for this purpose:
polyDelVertex
polyDelVertex

But, as noted above, this “head-on” approach has its drawbacks:
The vertices on the border of the mesh will be deleted, which will certainly disrupt the corners of the mesh.
And vertices may be deleted, the absence of which can significantly affect the shape and desired topology of the mesh.
This is especially harmful if the mesh is planned to be edited further.
Obviously, in most cases the user wants to delete only those winged vertices that lie on the same line with two adjacent vertices.
That is, if the angle between two edges connected to this vertex is zero, or very close to zero.
Perhaps the user would like to delete only those winged vertices that insignificantly affect the change in shape/topology.
That is, to delete only those winged vertices whose angle between edges is very insignificant.

This means that the user should be able to specify separately the allowed values ​​of edge angles for border and other winged vertices.
Probably, the code should process the selected vertices for each mesh, and all vertices if a mesh (shape or transform) is selected without selecting polycomponents on it.

import maya.cmds as cmds
import maya.api.OpenMaya as om2
maya_useNewAPI = True
import math

def get_selected_nodes_and_components():
    # Get a list of selected scene elements:
    selection = om2.MGlobal.getActiveSelectionList()
    # Create an iterator to process the `Selection List`:
    it_sel = om2.MItSelectionList(selection)
    # We will write the selected elements we need into the dictionary:
    nodes_dict = dict()
    # Let's check all selected elements and select for further processing only the selected polyshapes with the vertices selected on them.
    # If a polyshape is selected without vertices, then we will assume that all vertices on this polyshape should be processed.
    while not it_sel.isDone():
        # We check that the current element is a DAG node:
        if not it_sel.itemType():    # int kDagSelectionItem = 0.
            node, component = it_sel.getComponent()
            node_api_type = node.apiType()    # 296 - poly shape, 110 - transform.
            if node_api_type == 296 or node_api_type == 110:
                if node_api_type == 110:
                    # If the node type is 'transform', then we expand the node to 'shape':
                    node = node.extendToShape()
                    # We check that the node type is 'poly shape', and not, for example, 'camera':
                    if node.apiType() != 296:
                        # If this is not the case, then we proceed to processing the next element in 'MItSelectionList':
                        it_sel.next()
                        continue
                # We check that the selected components on the polyshape are vertices, i.e. have the type 'kMeshVertComponent'.
                # Or the component type 'Invalid' if no components are selected.
                component_api_type = component.apiType()    # 554 - 'kMeshVertComponent', 0 - 'kInvalid'.
                # Add a node to the library. Use the string representation of the node as the key,
                # and use the node's 'MDagPath' (and the component as 'MObject') as the value.
                if component_api_type == 0 or component_api_type == 554:
                    node_name_str = node.fullPathName()
                    if component_api_type == 0:
                        component = 0
                    if node_name_str not in nodes_dict.keys():
                        nodes_dict[node_name_str] = ([node, component])
                    else:
                        if component_api_type:
                            nodes_dict[node_name_str] = ([node, component])
        it_sel.next()
    return nodes_dict

# Let's calculate the absolute value of the angle (in radians) between the edges of the current vertex.
def get_edges_angle(it_v, it_e):
    edges = it_v.getConnectedEdges()
    it_e.setIndex(edges[0])
    a_pos = it_e.point(0, space = 4)
    b_pos = it_e.point(1, space = 4)
    it_e.setIndex(edges[1])
    c_pos = it_e.point(0, space = 4)
    d_pos = it_e.point(1, space = 4)
    a_vec = om2.MVector(a_pos)
    b_vec = om2.MVector(b_pos)
    c_vec = om2.MVector(c_pos)
    d_vec = om2.MVector(d_pos)
    ab_vec = b_vec - a_vec
    cd_vec = d_vec - c_vec
    return abs(ab_vec.angle(cd_vec))


# Process all selected vertices with the iterator.
# If the vertex does not lie on the border of the mesh and has a connection with only two edges, then:
# Option #1: by default, add the vertex to the list for deletion.
# Option #2: only if the angle between the edges coming from the vertex is less than the specified one,
# then add the vertex to the list for deletion.
# If the vertex lies on the border of the mesh and has a connection with only two edges, then:
# Option #1: by default, do not delete it.
# Option #2: if the angle between the edges coming from the vertex is less than the specified one,
# then add the vertex to the list for deletion.
def vertices_processing(del_vertices_on_border = False, border_edges_angle = 0.001,
                        check_edges_angle = False, edges_angle = 0.001):
    # We convert degrees to radians:
    edges_angle_rad = abs(math.radians(edges_angle))
    border_edges_angle_rad = abs(math.radians(border_edges_angle))
    # We launch the function to get a list of selected polygons and polycomponents (vertexes) on them:
    nodes_dict = get_selected_nodes_and_components()
    # We create a Selection List, to which we will add vertices corresponding to the specified parameters:
    del_vertices_sel_list = om2.MSelectionList()
    for item in nodes_dict:
        vertices_index_array = om2.MIntArray()
        # We create an iterator for vertices:
        node = nodes_dict[item][0]
        if nodes_dict[item][1]:
            it_v = om2.MItMeshVertex(node, nodes_dict[item][1])
        else:
            it_v = om2.MItMeshVertex(node)
        # We create an auxiliary iterator for edges:
        it_e = om2.MItMeshEdge(nodes_dict[item][0])
        while not it_v.isDone():
            # We process the vertex if it connects only two edges.
            if it_v.numConnectedEdges() == 2:
                # We process the vertex if it is located on the border of the poly mesh.
                if it_v.onBoundary():
                    # We process the vertex if it is set to remove winged vertices on the border of the mesh.
                    if del_vertices_on_border:
                        edges_angle = get_edges_angle(it_v, it_e)
                        if edges_angle <= border_edges_angle_rad:
                            vertices_index_array.append(it_v.index())
                # We process vertex meshes that are not on the border.
                else:
                    if check_edges_angle:
                        edges_angle = get_edges_angle(it_v, it_e)
                        if edges_angle <= edges_angle_rad:
                            vertices_index_array.append(it_v.index())
                    else:
                        vertices_index_array.append(it_v.index())
            it_v.next()
        # Create vertices components for MFnSingleIndexedComponent
        vertices_sic = om2.MFnSingleIndexedComponent()
        vertices_components = vertices_sic.create(om2.MFn.kMeshVertComponent)
        # Add vertices from the previously created array.
        vertices_sic.addElements(vertices_index_array)
        # In the Selection List we add a poly mesh as a node,
        # and vertices as components in the MFnSingleIndexedComponent.
        del_vertices_sel_list.add((node, vertices_components))    # !!! ((node, components))
    return del_vertices_sel_list


# Initial settings of parameters by the user:
check_edges_angle = True
edges_angle = 0.001            # degrees
del_vertices_on_border = True
border_edges_angle = 0.001     # degrees


# We start the process of searching for "winged vertices":
del_vertices_sel_list = vertices_processing(del_vertices_on_border, border_edges_angle, check_edges_angle, edges_angle)
Select found winged vertices:
om2.MGlobal.setActiveSelectionList(del_vertices_sel_list)
# Remove found winged vertices:
# cmds.delete()

Here’s a quick demo:

First option for setting up the parameters:

check_edges_angle = True
edges_angle = 0.001            # degrees
del_vertices_on_border = True
border_edges_angle = 0.001     # degrees

Second option for setting parameters:

check_edges_angle = False
edges_angle = 0.001            # degrees
del_vertices_on_border = True
border_edges_angle = 0.001     # degrees

I wish everyone positivity, harmony and good luck!

Edit:
Thanks to @tfox_TD for pointing out the error:
I decided to correct the name of the parameter border_del to a more precise one: del_vertices_on_border
But, I did not correct the name of the parameter in the declaration and call of the function)
The correct one will be of course like this:

def vertices_processing(del_vertices_on_border = False, border_edges_angle = 0.001,
                        check_edges_angle = False, edges_angle = 0.001):

and

del_vertices_sel_list = vertices_processing(del_vertices_on_border, border_edges_angle, check_edges_angle, edges_angle)

I fixed this in the code I posted earlier.

2 Likes

Very insightfull post, I will give this ago in a few minutes.
thank you for sharing this with us.

1 Like