1
$\begingroup$

I have a function that returns several faces from a mesh using ray_cast under the mouse pointer. Some surrounding faces are collected based on the angle from the hit face and passed to another function, most of this is done in bmesh.
In this function I want to copy the passed faces to a new bmesh to create a new object from them.

The best way I've found is to pass in the original bmesh with the faces I want to copy, iterate over it and append the new faces to a list:

def make_copy(self, context, bm_orig, faces_linked):
    # this gets called with bm_orig as the original bmesh in its entirety of type BMesh,  
    # and faces_linked as the faces of that bmesh that I want to copy to a new bmesh as a list of type BMFace
    
    bm_copy = bm_orig.copy()
    faces_linked_copy = []
    for face_orig in faces_linked:
        for face_copy in bm_copy.faces:
            if face_orig.index == face_copy.index:
                faces_linked_copy.append(face_copy)
    
    # then this makes a list of all the faces, checks them against the
    # list of faces I wan to keep (faces_linked_copy) and removes the rest from the list
    delete_list = []
    for face in bm_copy.faces:
        delete_list.append(face)
    
    for face_delete in bm_copy.faces:
        for face_save in faces_linked_copy:
            if face_delete == face_save:
                delete_list.remove(face_save)
    
    # finally using delete_list to remove all the faces I don't want
    bmesh.ops.delete(bm_copy, geom=delete_list, context="FACES")

From this I can delete all other faces in bm_copy and be left with the faces I need.
This works but it's extremely slow when operating on dense meshes.

This needs to happen every mouse move in a modal so with denser meshes this becomes unusable.

I've tried using bmesh.ops.duplicate with the dest=bm_new option but this always gives keyword "dest" type 4 not working yet! which it has done forever... (Am I doing something wrong with that?? or will they simply never implement it??)

Question is:
Is there a better way to copy some faces from one bmesh to another without having to copy and iterate over the original??

EDIT @lemon since you were curious as to what this was for, it does this: enter image description here

Collects the faces, then makes the blue faces from it. But now seeing it work I probably wont use it this way 😝
But I have learned some things, so thanks again for your help

$\endgroup$
2
  • $\begingroup$ could you add more details or provide a test case? specially, if face_copy.index is lower than len(bm_place.faces), you'll find the copy with bm_plane.faces[face_orig.index]. $\endgroup$
    – lemon
    Commented Apr 3, 2023 at 13:52
  • $\begingroup$ @lemon I've updated the code in the question, hope this makes it clearer. There was a typo with bm_plane.faces as well. The main question: Is there a better way to copy some faces from one bmesh to another? A more built-in way? Or some way that doesn't require iterating over the enire mesh, as there could be 100K faces and this happens every mouse move in a modal $\endgroup$
    – Psyonic
    Commented Apr 5, 2023 at 9:01

1 Answer 1

2
$\begingroup$

Two parts in this answer but I lake of context (so please use comments if not clear or not appropriated).

Your code optimisation, which mainly avoids loops and inner loops.

def make_copy(self, context, bm_orig, faces_linked):

    # creates a copy
    bm_copy = bm_orig.copy()

    # gets a set of face indices for fast access
    to_keep_indices = set(f.index for f in faces_linked)
    
    # creates the list of the faces to delete
    delete_list = [f for f in bm_copy.faces \
        if f.index not in to_keep_indices]
    
    # deletes
    bmesh.ops.delete(bm_copy, geom=delete_list, context="FACES")
    
    return bm_copy

A proposal that avoids to create a bmesh copy of a potential big bmesh.

def from_faces_linked(self, context, bm_orig, faces_linked):
    
    # set gets unique elements, list gets ordered elements    
    vertices = list(set(v for f in faces_linked for v in f.verts))

    # establishes a map between the new mesh vertex indices and 
    # actual mesh vertex indices
    vmap = dict()
    for i, vert in enumerate(vertices):
        vmap[vert.index] = i
    
    coordinates = [v.co for v in vertices]
    
    # build faces with new indices
    faces = []
    for face in faces_linked:
        faces.append([vmap[v.index] for v in face.verts])

    # create a mesh from that
    mesh = bpy.data.meshes.new(name="copy")
    mesh.from_pydata(coordinates, [], faces)

    # and a bmesh from this mesh
    bm = bmesh.new()
    bm.from_mesh(mesh)
    
    bpy.data.meshes.remove(mesh)    

    return bm
$\endgroup$
4
  • 1
    $\begingroup$ Thanks a heap @lemon! That 2nd method is exactly what I was looking for! I'd tried to go down the from_pydata route but couldn't quite get it to work properly.... and it was far more complex than your version. You've taught me quite a lot today! $\endgroup$
    – Psyonic
    Commented Apr 6, 2023 at 8:48
  • $\begingroup$ I've added a little video to my question with your working code as you wanted a use case for it. I miss a lot of tools from the CAD world so I'm trying to recreate them $\endgroup$
    – Psyonic
    Commented Apr 7, 2023 at 12:34
  • 1
    $\begingroup$ @Psyonic, thanks a lot for this feedback. That seems to be a cool addon! $\endgroup$
    – lemon
    Commented Apr 7, 2023 at 14:02
  • $\begingroup$ Thanks mate, and thanks again for your help. It's actually just fun to use 😁 $\endgroup$
    – Psyonic
    Commented Apr 7, 2023 at 21:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .