1
$\begingroup$

I have a BMesh that I loop through and select some faces. I would now like to create a new BMesh out of all the faces I just selected. How can this be achieved in python? In addition, since faces have the tag attribute which can be used by custom scripts like mine, how can a new mesh be created using the tag status of each face to determine inclusion?

$\endgroup$
1
$\begingroup$

AFAIK there is no native bmesh function to do this. The way I have done that in the past was to construct a new mesh manually, and propagate it to a new mesh datablock using the Mesh.from_pydata() function the Mesh datablock offers.

code explanation

First, get the mesh itself from edit mode. the ensure_lookup_table() calls are mandatory, otherwise you cannot access faces or verts by index:

bm = bmesh.from_edit_mesh(bpy.context.object.data)
bm.faces.ensure_lookup_table()

next, retrieve a list containing all selected faces:

selFaces = [x for x in bm.faces if x.select]

If instead you want to work on tagged faces, use this list aggregator instead:

selFaces = [x for x in bm.faces if x.tag]

now use two more lists which will hold the output data for our new mesh:

py_verts = []
py_faces = []

the tricky part is in the coming loop. We need to pass on a list of Vectors of vertex coordinates and a list of lists of indices of the faces in question to the from_pydata function. So iterate over all selected faces, retrieve its vertices, and add the unique vertices to the new list, and the new index of that list to the new face index list.

for f in selFaces:
    # cur_face_indices holds the new indices of our verts per face
    cur_face_indices = []

    for v in f.verts:
        if v not in py_verts:
            # this vert is found for the first time, add it
            py_verts.append(v)

        # add the new index of the current vert to the current face index list
        cur_face_indices.append(py_verts.index(v))

    # face index list construction is complete, add it to the face list
    py_faces.append(cur_face_indices)

if you require a Mesh datablock:

Declare a new mesh data block, and run the from_pydata on it to create the new mesh:

me = D.meshes.new(name = 'output')
mat = C.object.matrix_world
me.from_pydata([mat @ x.co for x in py_verts], [], py_faces)

As you can see, there is an additional step in here, which gets the world_matrix and applies it using the @ operator on each vertex coordinates. This is basically applying the transforms of the source object on the new one. Alternatively you could also set the world matrix on the new object instead:

me = D.meshes.new(name = 'output')
me.from_pydata([x.co for x in py_verts], [], py_faces)
ob = D.objects.new(name='output', object_data = me)
ob.matrix_world = C.active_object.matrix_world

if you only require a new bmesh:

You still need to construct a mesh, but from there you should be able to retrieve the bmesh easily:

me = D.meshes.new(name = 'output')
me.from_pydata([x.co for x in py_verts], [], py_faces)
bm_new = bmesh.new()
bm_new.from_mesh(me)

complete script

Here is the full script, which has to be run from edit mode with the faces selected, and links the resulting object to the scene in the end:

import bpy, bmesh
C = bpy.context
D = bpy.data

bm = bmesh.from_edit_mesh(C.object.data)
bm.faces.ensure_lookup_table()
bm.verts.ensure_lookup_table()

# get all selected faces in a list
# replace x.select with x.tag in case that's what you are after
selFaces = [x for x in bm.faces if x.select]

# construct Python faces
# this is more involved, as we have to remap the new index
# to do this, we reconstruct a new vert list and only append new items to it
py_verts = []
py_faces = []

for f in selFaces:
    # cur_face_indices holds the new indices of our verts per face
    cur_face_indices = []

    for v in f.verts:
        if v not in py_verts:
            # this vert is found for the first time, add it
            py_verts.append(v)

        # add the new index of the current vert to the current face index list
        cur_face_indices.append(py_verts.index(v))

    # face index list construction is complete, add it to the face list
    py_faces.append(cur_face_indices)

print(py_verts, py_faces)

# now create a new mesh, and add the geometry as its data
me = D.meshes.new(name = 'output')

# get the world matrix of the active object and apply it to the mesh
# this ensures same orientation as the source for the new mesh, but with
# transforms applied
mat = C.object.matrix_world
me.from_pydata([mat @ x.co for x in py_verts], [], py_faces)

# create a new object, and link it to the current view layer for display
ob = D.objects.new(name='output', object_data = me)
C.view_layer.active_layer_collection.collection.objects.link(ob)
$\endgroup$
1
  • $\begingroup$ This is exactly what I was looking for. Bmesh really should have this functionality. $\endgroup$
    – user97005
    May 24 '20 at 15:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy