How to speed up deleting mesh components (Maya Python API-2)?

How to speed up deleting mesh components (Maya API)?

The mesh is imported into the scene from the “.obj” - file
This Triangulated Mesh containing ~ 900 000 edges.
It is necessary to remove the edges (added by triangulation) to restore the original topology of the mesh.
The indexes of “unnecessary” edges are loaded from an external text file.

Mesh does not have Construction History.
Construction History - Off
Undo - Off

Select edges - 280 000 edges:

Python sorted list to → MIntArray to - > MFnSingleIndexedComponent (MFn.kMeshEdgeComponent) to - > MSelectionList to → MGlobal.setActiveSelectionList

This entire sequence of operations is completed in less than 0.04 seconds.

Removing selected edges by any of these methods:

cmds.delete()
or
cmds.polyDelEdge(ch=False)
or
press key Delete
or
Delete Edge / Vertex” on Maya UI menu

Time = ~ 32 secunds.

Is it possible to significantly reduce the time to delete mesh components (edges)?
Is it possible to do this with some kind of dirty hack or directModifier?
Any ideas?

Have you tried suspending refresh during the deletion?

cmds.refresh(suspend=True)
try:
    cmds.delete()
finally:
    cmds.refresh(suspend=False)

Considering it’s a single delete action I’m not hopeful, but it might be worth trying.

I would expect it to be significantly quicker in Maya.standalone hence the refresh suspend suggestion :wink:

Have you tried disabling the undo stack?

“suspend refresh”, in this case, has absolutely no effect on the edge deleting time in the mesh.

Execution of the script in Maya UI or Maya standalone also does not affect the execution time of the script as a whole. And, practically, it does not affect the execution time of individual operations / commands.

Yes, I always put commands like this at the beginning of scripts:

cmds.constructionHistory(toggle=False)
cmds.undoInfo(state=False)
cmds.refresh(su=True)
cmds.flushUndo

Here is the text of the script. The commented out lines are for the standalone version.
At the end of the script - a bunch of garbage code to display statistics.

# python 2/3

import os
import io
import sys
import json
import struct
import codecs
import operator
import argparse
#import maya.standalone

#os.environ["MAYA_DISABLE_ADP"] = "1"

#try:    
#    maya.standalone.initialize(name = "python")
#except:
#    sys.stderr.write("Failed in initialize Maya standalone aplication")
#    raise

import maya.mel as mel
import maya.cmds as cmds
import maya.api.OpenMaya as om

def maya_useNewAPI():
    pass

def main():

    py_ver = float(sys.winver)
    print('\n\npy_ver = ' + str(py_ver) + '\n\n')
    sl_mesh_name = 'mesh00:exemple00'
    sl_file_wire = '...//exemple00.wire'
    sl_file_maya = '...//ok.ma'

    cmds.constructionHistory(toggle=False)
    cmds.undoInfo(state=False)
    cmds.refresh(su=True)
    cmds.flushUndo

    cmds.file(sl_file_maya, open = True, force = True, options = 'v=1;', ignoreVersion = True, typ = 'mayaAscii')
    sl_input_array_0 = om.MIntArray()
    sl_input_array_1 = om.MIntArray()
    sl_edge_indx_array = om.MIntArray()
    sl_edge_array = []
    sl_set1 = set()
    sl_set2 = set()
    sl_set3 = set()
    sl_set4 = set()
    sl_set5 = set()

    sl_edge_sort1 = ()

    sel = om.MSelectionList()
    sel.add(sl_mesh_name)
    dag = sel.getDagPath(0)
    dag_shape = dag.extendToShape(0)

    vtx_iter = om.MItMeshVertex(dag)
    edg_iter = om.MItMeshEdge(dag)
    edge_count = edg_iter.count()


    sl_start = cmds.timerX()

    with open(sl_file_wire, 'r') as sl_input_f: sl_lines = sl_input_f.read().splitlines()

    sl_stop_00 = cmds.timerX(startTime=sl_start)

    for each in sl_lines[slice(0,-1,2)]: sl_input_array_0.append(int(each))
    for each in sl_lines[slice(1,-1,2)]: sl_input_array_1.append(int(each))

    sl_stop_02 = cmds.timerX(startTime=sl_start)

    for each_0, each_1 in zip(sl_input_array_0, sl_input_array_1):
        vtx_iter.setIndex(each_0)
        sl_set1 = set(vtx_iter.getConnectedEdges())
        vtx_iter.setIndex(each_1)
        sl_set2 = set(vtx_iter.getConnectedEdges())
        sl_set3.add(tuple(sl_set1 & sl_set2)[0])

    for i in range(edge_count): sl_set4.add(i)


    sl_set5 = (sl_set4 - sl_set3)

    sl_stop_04 = cmds.timerX(startTime=sl_start)

    sl_edge_sort1 = sorted(sl_set5, key = int)

    sl_stop_06 = cmds.timerX(startTime=sl_start)


    for item in  sl_edge_sort1: sl_edge_indx_array.append(item)

    sl_stop_08 = cmds.timerX(startTime=sl_start)

    sl_edges = om.MFnSingleIndexedComponent()
    sl_components = sl_edges.create(om.MFn.kMeshEdgeComponent)
    sl_edges.addElements(sl_edge_indx_array)

    sl_selection_edges = om.MSelectionList()
    sl_selection_edges.add((dag_shape, sl_components))
    om.MGlobal.setActiveSelectionList(sl_selection_edges)

    sl_stop_10 = cmds.timerX(startTime=sl_start)

    cmds.delete()

    sl_stop_12 = cmds.timerX(startTime=sl_start)

    cmds.refresh(su=False)


# print timers and stat.info

    sl_len = sl_input_array_0.__len__()

    sl_stop_00 = round((sl_stop_00), 2)
    sl_stop_03 = round((sl_stop_02-sl_stop_00), 2)
    sl_stop_05 = round((sl_stop_04-sl_stop_02), 2)
    sl_stop_07 = round((sl_stop_06-sl_stop_04), 2)
    sl_stop_09 = round((sl_stop_08-sl_stop_06), 2)
    sl_stop_11 = round((sl_stop_10-sl_stop_08), 2)
    sl_stop_13 = round((sl_stop_12-sl_stop_10), 2)

    print('\n\n' + 'Total edges on Mesh = '.rjust(35) + str(edge_count).rjust(8))
    print('Total edges from wire = '.rjust(35) + str(sl_len).rjust(8))
    print('Total edges from wire, no duble = '.rjust(35) + str(len(sl_set3)).rjust(8))
    print('For delete edges count = '.rjust(35)  + str(len(sl_set5)).rjust(8)+'\n')

    print('input lines'.ljust(19) + ' = ' + (str(sl_stop_00) + ' s.').ljust(11) + 'Total = ' + str(sl_stop_00) + ' s.')
    print('lines to two arrays'.ljust(19) + ' = ' + (str(sl_stop_03) + ' s.').ljust(11) + 'Total = ' + str(sl_stop_02) + ' s.')
    print('output set'.ljust(19) + ' = ' + (str(sl_stop_05) + ' s.').ljust(11) + 'Total = ' + str(sl_stop_04) + ' s.')
    print('set to sort list'.ljust(19) + ' = ' + (str(sl_stop_07) + ' s.').ljust(11) + 'Total = ' + str(sl_stop_06) + ' s.')
    print('edges array out'.ljust(19) + ' = ' + (str(sl_stop_09) + ' s.').ljust(11) + 'Total = ' + str(sl_stop_08) + ' s.')
    print('select edges'.ljust(19) + ' = ' + (str(sl_stop_11) + ' s.').ljust(11) + 'Total = ' + str(sl_stop_10) + ' s.')
    print('delete edges'.ljust(19) + ' = ' + (str(sl_stop_13) + ' s.').ljust(11) + 'Total = ' + str(sl_stop_12) + ' s.')

main()

#try:
#    maya.standalone.uninitialize()
#except:
#    pass

There is a triangulated mesh in the scene.
The file “sl_input_f” contains the vertex indices for all edges of the mesh with the original topology (before its triangulation).
We want to get from the triangulated mesh - a mesh with the original topology.
An algorithm like this:

  • Read the vertex indices from the file (The indices are separated by “/ n”).
  • We convert each pair of vertex indices into an edge index.
  • Remove duplicates, sort, and write the resulting indexes to the array.
  • Get the list of edge indices for the triangulated mesh.
  • Subtract from this list of indices the previously received list of indices of the edges of the original topology.
  • Select the edges with the subtracted indices.
  • Delete.
  • We get a mesh with the original topology.
    Profit!

The execution time of this script in Maya 2019.3.1, or in Maya 2020.4, or in Maya 2022.3 (Python-2) is almost the same for both Maya-UI and Maya-Standalone versions (the differences are insignificant and stochastic).
The only significant difference in Maya 2022.3 (Python-3) is a fourfold increase in speed for parallel parsing (with slicing) and writing from the “list” to two arrays “om.MIntArray ()”:

sl_input_array_0 = om.MIntArray()
sl_input_array_1 = om.MIntArray()

with open(sl_file_wire, 'r') as sl_input_f: sl_lines = sl_input_f.read().splitlines()

for each in sl_lines[slice(0,-1,2)]: sl_input_array_0.append(int(each))
for each in sl_lines[slice(1,-1,2)]: sl_input_array_1.append(int(each))

This part of the code also shows an increase in execution speed for Python-3.
Execution time is reduced by 30% (compared to Python-2):

vtx_iter = om.MItMeshVertex(dag)
edg_iter = om.MItMeshEdge(dag)
edge_count = edg_iter.count()

for each_0, each_1 in zip(sl_input_array_0, sl_input_array_1):
    vtx_iter.setIndex(each_0)
    sl_set1 = set(vtx_iter.getConnectedEdges())
    vtx_iter.setIndex(each_1)
    sl_set2 = set(vtx_iter.getConnectedEdges())
    sl_set3.add(tuple(sl_set1 & sl_set2)[0])

It is noteworthy: When you try to combine these parts of the code into more compact analogs, the script execution time increases.

The execution time for all operations is acceptable. Except the time spent on the delete operation.

Here are the stats for Maya 2019, 2020, 2022 (Python-2/3). For Maya UI and for Maya standalone:

                 Total edges on triangle Mesh =  893244
 Total edges from wire in input txt-wire-file = 1093392
              Total edges from wire, no duble =  619896
                     For delete - edges count =  273348


 MAYA 2022.3, UI,  maya.api.OpenMaya, Py-3

input lines         =  0.2 s.     Total =  0.20 s.
lines to two arrays =  0.53 s.    Total =  0.73 s.
output set          =  3.03 s.    Total =  3.76 s.
set to sort list    =  0.03 s.    Total =  3.79 s.
edges array out     =  0.02 s.    Total =  3.81 s.
select edges        =  0.01 s.    Total =  3.82 s.
delete edges        = 30.94 s.    Total = 34.76 s.


 MAYA 2022.3, UI,  maya.api.OpenMaya, Py-2

input lines         =  0.11 s.    Total =  0.11 s.
lines to two arrays =  4.06 s.    Total =  4.17 s.
output set          =  3.96 s.    Total =  8.13 s.
set to sort list    =  0.09 s.    Total =  8.22 s.
edges array out     =  0.03 s.    Total =  8.25 s.
select edges        =  0.00 s.    Total =  8.25 s.
delete edges        = 30.84 s.    Total = 39.09 s.


 MAYA 2022.3 standalone, maya.api.OpenMaya, Py-3

input lines         =  0.19 s.    Total =  0.19 s.
lines to two arrays =  0.53 s.    Total =  0.72 s.
output set          =  3.09 s.    Total =  3.81 s.
set to sort list    =  0.03 s.    Total =  3.84 s.
edges array out     =  0.02 s.    Total =  3.86 s.
select edges        =  0.01 s.    Total =  3.87 s.
delete edges        = 30.89 s.    Total = 34.76 s.


 MAYA 2022.3 standalone, maya.api.OpenMaya, Py-2

input lines         =  0.13 s.    Total =  0.13 s.
lines to two arrays =  4.22 s.    Total =  4.35 s.
output set          =  3.87 s.    Total =  8.22 s.
set to sort list    =  0.10 s.    Total =  8.32 s.
edges array out     =  0.03 s.    Total =  8.35 s.
select edges        =  0.00 s.    Total =  8.35 s.
delete edges        = 30.81 s.    Total = 39.16 s.


 MAYA 2020.4, UI,  maya.api.OpenMaya, Py-2

input lines         =  0.13 s.    Total =  0.13 s.
lines to two arrays =  4.21 s.    Total =  4.34 s.
output set          =  4.03 s.    Total =  8.37 s.
set to sort list    =  0.09 s.    Total =  8.46 s.
edges array out     =  0.02 s.    Total =  8.48 s.
select edges        =  0.01 s.    Total =  8.49 s.
delete edges        = 32.26 s.    Total = 40.75 s.


 MAYA 2020.4 standalone, maya.api.OpenMaya, Py-2

input lines         =  0.11 s.    Total =  0.11 s.
lines to two arrays =  4.29 s.    Total =  4.40 s.
output set          =  3.69 s.    Total =  8.09 s.
set to sort list    =  0.10 s.    Total =  8.19 s.
edges array out     =  0.03 s.    Total =  8.22 s.
select edges        =  0.00 s.    Total =  8.22 s.
delete edges        = 32.26 s.    Total = 40.48 s.


 MAYA 2019.3.1, UI,  maya.api.OpenMaya, Py-2

input lines         =  0.13 s.    Total =  0.13 s.
lines to two arrays =  4.24 s.    Total =  4.37 s.
output set          =  3.77 s.    Total =  8.14 s.
set to sort list    =  0.08 s.    Total =  8.22 s.
edges array out     =  0.03 s.    Total =  8.25 s.
select edges        =  0.00 s.    Total =  8.25 s.
delete edges        = 30.94 s.    Total = 39.19 s.


 MAYA 2019.3.1, standalone, maya.api.OpenMaya, Py-2

input lines         =  0.11 s.    Total =  0.11 s.
lines to two arrays =  4.40 s.    Total =  4.51 s.
output set          =  3.86 s.    Total =  8.37 s.
set to sort list    =  0.08 s.    Total =  8.45 s.
edges array out     =  0.03 s.    Total =  8.48 s.
select edges        =  0.00 s.    Total =  8.48 s.
delete edges        = 30.94 s.    Total = 39.42 s.

At this point in time, I think the best way to handle this is to do it yourself.

Use MFnMesh.getVertices to get the face data, and get each triangle as its own list
Build a dictionary of [vert pair → faces]
Build dictionaries of [Face → FaceIndex] and [FaceIndex → faces]
Loop over your “to delete” edges, get the faces that share that edge (using the first dict)
Also update the indices of faces that should now be combined (using the second and third dicts)
Finally, go over the now grouped-by-face-index triangles and build a new polygon for each of them
Build a brand new mesh that will now have those edges missing.

Then maybe do a transfer attributes to this new mesh to get UV’s or whatever else you need.

Edit: I did the fun part :slight_smile:

1 Like

At this point in time, I think the best way to handle this is to do it yourself.

I am sincerely very grateful to you for your time and valuable ideas.
Quick tests show that applying your proposed method can be very useful in many other cases.
But, there is no cardinal gain in speed … :frowning:
When removing the mesh components - “ribs”, Maya (under the hood) has to do the same job of rebuilding the mesh. Maya probably uses the most optimized algorithms for this, aimed at maximum performance.
Therefore, I wanted to find out: what caused the observed performance (when removing mesh components and rebuilding the mesh). Are these limitations fundamental limitations of Maya architecture? Or does Maya perform (not obvious) additional resource-intensive operations (superfluous/unnecessary in this context. Something like preparing data for a possible modifier node)? And, most importantly, is it possible to interfere with this behavior (using legal methods, or “dirty hacks”)?
It seems that Maya is creating a copy of the mesh for the modified mesh anyway. But since the undo stack does not exist and the build history is disabled, creating a modifier is a waste of time.
Is it possible to force Maya by means of APIs to use the modification method on the grid itself? Using the “directModifier” method?
The modifier can be applied in Directly on the mesh.
Something like “polyModifierCmd” from the Developer Kit:
https://help.autodesk.com/view/MAYAUL/2022/ENU/?guid=Maya_SDK_Polygon_API_polyModifierCmd_example_html

PS: Sorry for the possible misunderstandings, English is not my first language…

Hi, I have nearly no experience with performance issues of this kind, so maybe this is completely naive, but my first instinct would be to tweak the obj before import?

I had a similar problem when I wrote an unSubdivide algorithm. I don’t remember the performance gain, but I remember it was significant.
I make a simple temporary mesh, then MFnMesh.createInPlace to change that mesh into what I wanted.

Heads up , my code uses api 1.0

2 Likes

Question, have you at any point attempted to profile the code using the cProfile module? It could help possibly point you at which part of the script is eating the time.

2 Likes