Page MenuHome

Crash when passing geom and context to bmesh.ops.delete when all is selected
Closed, ResolvedPublic

Description

This started from a question at BSE. The example file can be found here (google drive 500k)

The file contains a single mesh object made of two cubes and a plane and has the following script -

import bpy, bmesh

mesh = bpy.context.edit_object.data
bm = bmesh.from_edit_mesh(mesh)

delete_first = []
for f in bm.faces:
    if f.calc_center_bounds().y < 0:
        delete_first.append(f)

bmesh.ops.delete(bm, geom=delete_first, context=5)
#Note: if the above line is commented out, there is no error.

#bmesh.update_edit_mesh(mesh, True)
#bpy.ops.object.editmode_toggle()
#bpy.ops.object.editmode_toggle()
#bm = bmesh.from_edit_mesh(mesh)

geom = [e for e in bm.verts[:]+bm.edges[:]+bm.faces[:] if e.select]

for i in range(10):
    vn, en, fn = len(bm.verts), len(bm.edges), len(bm.faces)
    ret = bmesh.ops.bisect_plane(bm, geom=geom, plane_co=(0,i,0), plane_no=(0,1,0))
    geom += bm.verts[vn:] + bm.edges[en:] + bm.faces[fn:]
    
    #Note: ret["geom"] unfortunately does not provide all the geometry I need for the next cut, so I have to collect the new geometry this way.
    #Note: Also it is required that ONLY the selected geometry is cut, nothing else.

bmesh.update_edit_mesh(mesh)

The script expects to start with the object in edit mode and the sample file has all mesh components selected. When the script is run the error ValueError: geom: found the same (BMVert/BMEdge/BMFace) used multiple times is given and then when the 3d view is updated blender crashes.

The problem comes from the call to bmesh.ops.delete and would appear to be caused by both geom and context parameters being passed, while all components are selected.

If context is removed or changed (values of 1,2 and 4 works) or if everything is deselected before running the script there is no problem. I think the enum for the context value should be defined in python and to me it looks like it should match the enum in bmesh_operator_api.h.

Also having only the listed items selected works, so you can also change the first loop to -

delete_first = []
for f in bm.faces:
    f.select = False
    if f.calc_center_bounds().y < 0:
        delete_first.append(f)
        f.select = True

Related to this - the python docs for bmesh.ops.delete could be improved. Some clarification for the context and/or geom parameter could be added, is context meant to delete selected items or does it have to match (or subset of) the type of items in the geom list?

Event Timeline

Germano Cavalcante (mano-wii) closed this task as Invalid.

Actually the crash occurs because the execution of the script is being interrupted before the bmesh.update_edit_mesh(mesh).

Remember that the new elements created are not necessarily at the end of the array. So your code is actually repeating elements in the list.

Unfortunately the return of the bmesh.ops.bisect_plane does not inform the new index of the cut edges :\
This return is a matter for discussion... In another report.

I posted the question on BSE.

I've seen the editmode_toggle trick used in other addons as a flush to ensure everything is as it seems. But without it, it seems bmesh.ops.bisect_plane is filling gaps in bm.verts/edges/faces that resulted from the deletion op.
Thus:

vn, en, fn = len(bm.verts), len(bm.edges), len(bm.faces)
op()
geom += bm.verts[vn:] + bm.edges[en:] + bm.faces[fn:]

Will catch some repeated elements due to the initial deletion making Swiss cheese of my geometry.

Shouldn't there be an additional op that ensures that new geometry is appended?
(along with documentation and something in the Gotchas section)

Surely it's just a matter of removing *secret* gaps in the BMElemSeqs and updating indices.

Unless I'm terribly mistaken, could someone explain why this wouldn't be a welcome addition to the bmesh module?

Thus:

vn, en, fn = len(bm.verts), len(bm.edges), len(bm.faces)
op()
geom += bm.verts[vn:] + bm.edges[en:] + bm.faces[fn:]

This takes repetitive elements because the new elements are added in the middle of the list (not at the end).

Unfortunately the return of the bmesh.ops.bisect_plane operator is currently limited. So to get around this limitation in python is necessary to make a hack that unfortunately makes your code less efficient :\

Here is the hack:

import bpy, bmesh

mesh = bpy.context.edit_object.data
bm = bmesh.from_edit_mesh(mesh)

delete_first = []
for f in bm.faces:
    if f.calc_center_bounds().y < 0:
        delete_first.append(f)

bmesh.ops.delete(bm, geom=delete_first, context=5)

geom = [e for e in bm.verts[:]+bm.edges[:]+bm.faces[:] if e.select]

edges = set(bm.edges)

for i in range(10):
    ret = bmesh.ops.bisect_plane(bm, geom=geom, plane_co=(0,i,0), plane_no=(0,1,0))
    
    if ret["geom_cut"]: # Had cut
        geom = ret["geom"]
        new_edges = edges.symmetric_difference(bm.edges)
        edges.update(new_edges)
        geom.extend(e for e in new_edges if e not in ret["geom_cut"])

bmesh.update_edit_mesh(mesh)