Foreach_get for selected vertex indices?

Hi

Is there some sort of function similar to foreach_get that I can use to dump the selected and unselected vertex indices to a Numpy array?

I love how I can fill a Numpy array with the mesh vertex location, looking for a similarly optimized way of building a boolean ndarray so I can use to mask the Numpy array.

thanks

nope… the pythonic way to do it would be to use list comprehension. ie) selected_verts = [v in bm.verts if v.select]

1 Like

Thanks, that is what I have been using atm, I was just wondering if I could make it even faster.

nope, I wish there were, i found out the hard way that looping through 2 million verts in a python script is slow no matter how you try to do it.

word up, thanks for saving my time.

Unless I’m misunderstanding something:

v_sel = np.empty(len(me.vertices), dtype=bool)
me.vertices.foreach_get('select', v_sel)

Then just use np.where() to get the indices

sel_idx, = np.where(v_sel)
unsel_idx, = np.where(np.invert(v_sel))
3 Likes

thanks, I will try it. I guess that is what I was looking for.

Did you just guess that ‘select’ would be the propert to query? Because I was not able to get much info about foreach in the api document.

Any property with a basic type (int, bool, float) can be accessed with foreach, assuming there’s a foreach function for the actual collection. Bmesh sadly doesn’t - that would have been mind blowing.

For vertices you can find foreach-compatible props through the bl_rna like this:

me = bpy.context.object.data

for prop in me.vertices[0].bl_rna.properties.values():
    if prop.type in {'INT', 'FLOAT', 'BOOLEAN'}:
        print(prop.identifier)
3 Likes

Thank you, that is all I was looking for.

@kaio

Your solution is great except that I am hitting a wall here. It seems to me that the ndarray is not updated if the selection changes while in edit mode. At least that is the case when testing this stuff inside the internal text editor/

Going in an out of the edit mode resolves th eissue but not optimum. Do you know any reasonable way to update the array properly without changing modes?

No. That’s the limitation of how obj.data works. I’ve struggled with this as well.

The only way I know of to sync changes to obj.data while in edit mode, is using obj.update_from_edit_mode() but it’s not a cheap operation on dense meshes.

If you really need synced selection, bmesh.from_edit_mesh returns a live copy, but lacks foreach methods

2 Likes

Thank you, that sounds like a reasonable option for me for what I am trying to do.

@kaio I’m using the bmesh.from_edit_mesh method and I still have to exit and re-enter edit mode to get it to update. Can you give an example of how to get synced selection? I’ve been searching for two days and can’t find anything. I almost got something working using bmesh.select_history but it only “saw” and updated click selection. Box selections didn’t register at all…

Coming from MaxScript I gotta say I find the Blender API very difficult already, but the bmesh stuff is especially hard to figure out. I just wish there was a context.mesh.selected_vertices like almost everything else in Blender has since all I really want to do is track selection. :face_with_head_bandage:

Thanks for any help.

select_history isn’t actually meant to store selections, only to keep track of the last, singular element selected, which blender calls the active element. Region based selections can never add to this because only one element can be added to the history at a time. For selections in general you need to access the select property of the bmesh elements.

By synced selection I mean the selection state of elements on the bmesh mirrors what you see in the viewport.

from bmesh import from_edit_mesh
import bpy

def run():
    bm = from_edit_mesh(bpy.context.object.data)
    vsel = [v.index for v in bm.verts if v.select]
    
    if vsel:
        print("selected:", *vsel)

if __name__ == '__main__':
    run()
2 Likes

Thank you. This is the best explanation of select_history I’ve seen so far.

I swear I was doing the list comprehension just like your example but couldn’t get an updated return without leaving and re-entering Edit Mode. But it seems to be working now. So, thanks again. That was really helpful :+1:

1 Like

I have used select_history like this:

obj = bpy.context.view_layer.objects.active
bm = bmesh.from_edit_mesh(obj.data)
if len(bm.select_history) >= 3:
    v1 = bm.select_history[-3]
    v2 = bm.select_history[-2]
    v3 = bm.select_history[-1]

To get the last three selected vertices, the last one being the active vertex, seems to work well… also the last line of this:

obj = bpy.context.view_layer.objects.active
bm = bmesh.from_edit_mesh(obj.data)
# Do something here...
bmesh.update_edit_mesh(obj.data)

Updates the display in 3D window if you add new vertices, or change the selection without having to exit and re-enter Edit mode, something that you cannot do via python in Animation Nodes for instance. Hope this helps.

Cheers, Clock.

EDIT:

You might like to use a line like this:

if isinstance(v1, bmesh.types.BMVert):

To check that v1 in the first section of code is actually a vertex, not an edge, or face…

1 Like

It’s tangentially worth noting that bm.select_history only keeps references to elements that were selected using mesh.select (single click on an element). Elements selected with lasso, box, circle, etc will not show up, which is somewhat understandable given that a bulk selection can’t be enumerated per se, but unfortunate nonetheless since it requires a bunch of edge-case sniffing if you’re going to use it. It’s especially maddening that bulk selection methods don’t add to select_history even if you only select a single element.

I feel like, as a design decision- they could have just dumped the bulk selection into select_history in any order (since the user’s intent with a bulk selection was clearly not concerned with order) and it would have covered more edge cases, but that’s how it is i guess.

3 Likes

I couldn’t agree with you more, however it is possible to use something like:

verts = [v for v in bm.verts if v.select]

The annoying thing here is that you do not know the order, other than knowing that the list is in the order you placed the vertices in the blend file. So, if I want to know which is the middle vertex on this:

33

If I select them one at a time I know - it’s the white one I selected last, if I bulk select them I have no idea…

So can we have bulk selections added to the select_history please?

Cheers, Clock.

The real problem is with existing operators that rely on select_history. For example, If you try to target weld vertices with the ‘last’ option set, it just fails if you used bulk selection as your last operation. From the users perspective, that bulk selection WAS the last selection so when it fails it’s a bad experience.

These Blender design choices are making the life of an add-on dev unnecessarily complicated.

  • bmesh doesn’t have a list of which components are selected.
  • bmesh doesn’t have foreach methods to quickly build such a list.
  • object.data.vertices has both, but it doesn’t update when the selection changes in Edit mode.
  • bmesh.select_history has a list but doesn’t include box/lasso/circle/other selected vertices.

The end result is that add-on devs have to…

  • loop over the entire bmesh with a list comprehension (slow on dense meshes)
  • or do hacks like mode switching or ob.update_from_editmode() to update the object.data (extremely expensive for dense meshes which defeats the purpose of wanting to use foreach with numpy because the slow list comprehension will be faster)
  • or re-implement and replace Blender functions and tools in their own python scripts and manually handle all tracking themselves (if you’re crazy and/or a masochist).

Is there really no faster way to get the selected vertices from a bmesh than to loop the entire mesh with a list comprehension?

4 Likes