Bmesh, adding new verts

Is it possible to add new verts to a bmesh.bmvert sequence without re-indexing existing verts? In my tests, most of the time, verts created with bm.verts.new() are created at the end of the verts sequence, but sometimes they get inserted at random positions in the middle of the sequence- which causes index_update to re-order all of the indices after it. Is there any way to stop this from happening?

after adding new verts- I’m enumerating the bm.verts sequence to figure out where the new vert was inserted. Like I said, usually it’s at the end- but sometimes it looks like this:

Existing vert: 0, (after index_update: 0)
Existing vert: 1, (after index_update: 1)
Existing vert: 2, (after index_update: 2)
Existing vert: 3, (after index_update: 3)
Existing vert: 4, (after index_update: 4)
Existing vert: 5, (after index_update: 5)
-> New vert: -1, (after index_update: 6)
Existing vert: 6, (after index_update: 7)
Existing vert: 7, (after index_update: 8)
Existing vert: 8, (after index_update: 9)
Existing vert: 9, (after index_update: 10)
Existing vert: 10, (after index_update: 11)

If anyone is curious, I’m trying to create a fast way to merge one bmesh into another using vertex tags and BMLayerAccessVert to pass information around, but the existing vertices changing indices in the middle is problematic. Any clues here would be helpful!

PS: i’m more than a little surprised that we don’t already have a bmesh.ops.join operator

Looking closer, this appears to be an issue(?) with Bevel. Every other mesh operation I try preserves the bmvert sequence, but Bevel seems to break it. I simplified the issue I’m having down to this test script- is this intended behavior? Since Bevel is the outlier here I’m inclined to think it’s a bug, @Howard_Trickey do you know anything about this?

sample script- run from edit mode with a default cube (i can supply a .blend if needed)

import bpy, bmesh
from mathutils import Vector
from random import uniform as rand

# when you run this script, it should create a random triangle within a 5 unit radius of the origin. The triangle should be completely separated from the default cube.
# you can perform any mesh editing operations you like on the geometry in the scene EXCEPT for bevel and this behavior will continue each time you run the script.
# If you bevel, and then immediately run the script- a triangle will be created but one of its verts will be attached to the default cube and one vert will be floating off in space.
# If you bevel twice in a row and then run the script, it will fail on bm.faces.new() because a vert was used more than once
# All of this appears to occur because bevel is the only mesh operation that does not preserve the bm.vert sequence.

print("\n\n-------\n")
new_coords = [Vector((rand(-5, 5),rand(-5, 5),rand(-5, 5))), Vector((rand(-5, 5),rand(-5, 5),rand(-5, 5))), Vector((rand(-5, 5),rand(-5, 5),rand(-5, 5)))]

obj = bpy.context.active_object
bm = bmesh.from_edit_mesh(obj.data)

# For the sake of keeping this example simple, i'm storing the vert indices in here to illustrate the problem, I know you could just store the verts directly and it would work for this specific example.
# this is a placeholder for "more complicated stuff happening that would only make this example harder to understand"
new_face_vert_indices = []

for n, co in enumerate(new_coords):
    print(f"adding coordinate {n+1} of 3")
    this_vert = bm.verts.new(co)

    sequence_preserved = True
    for i, v in enumerate(bm.verts):
        if v.index == -1 and i != len(bm.verts)-1:
            print(f"{v.index} - New vertex was inserted out of sequence! After update_index it will be {i}, when it should be {len(bm.verts)-1}!")
            sequence_preserved = False

    if sequence_preserved:
        print(f"New vertex was correctly inserted at the end of the bmvert sequence!")
    bm.verts.index_update()
    new_face_vert_indices.append(this_vert.index) # again, I know I could store the vert for this example. That's not the point of this demonstration.
    print("\n")

bm.verts.ensure_lookup_table()

# this will assert due to duplicate verts if you bevel twice in a row.
bm.faces.new([bm.verts[i] for i in new_face_vert_indices])
bm.normal_update()
bmesh.update_edit_mesh(obj.data, loop_triangles=True, destructive = True)

Any operation that deletes or merges vertices will change the indexing. BMesh operates internally by deleting and rebuilding certain faces and if there are vertices that are at the ends of beveled edges, or are vertex-only beveled, they will be deleted too. Sorry, it is just much easier to program than to try to reuse vertices (they’d have to move to new positions, and which new position would be an arbitrary choice). I don’t regard this as a bug since there was never any guarantee about what happens to vertex indices as you do operations. (In fact Ton asked me not to make the “show indices” feature available to regular users - he wanted it only exposed to developers, partly for the reason that people shouldn’t depend on what happens to vertices. Internally, Blender calls the reindexing functions quite a lot.

That’s what I originally assumed, it was only after doing a variety of tests that I noticed the sequence is preserved most of the time. It’s only in the clarity of daylight that the results make more sense- most of the mesh editing operations are additive, so it stands to reason that the sequence wouldn’t change in those situation since none of the original sequence was removed- and bevel is a destructive operation, so it makes sense that the sequence would change. Thanks for humoring my late-night brain fart! :sweat_smile:

So one interesting thing I’ve noticed is that dropping out of edit mode and then re-entering causes the sequence to be rebuilt linearly, so new vertices appear at the end of the sequence rather than in the middle. I don’t understand enough of the underpinnings to know what’s happening when you switch from edit to object mode (and vice versa), but it almost seems like the sequence is rebuilt from scratch using mesh data, and so the “fresh” sequence is linear until the next destructive operation?

That’s a decent enough work-around for me, but out of curiosity is there any way to force that behavior without leaving edit mode? I’ve played around with bm.verts.sort() but i can’t seem to work out what it’s meant to do as index_update() will restore it to its previous order, that seems to indicate that the sequence itself hasn’t actually been sorted.

Is there any way to force that behavior without leaving edit mode?

Have you tried https://docs.blender.org/api/blender_python_api_current/bpy.ops.ed.html ?

1 Like

I have not, but will now! Thanks!