30
$\begingroup$

What's the best/fastest way to construct a list of selected vertices for a mesh object in python?

I want to avoid iterating over every vertex in the mesh if possible, since that seems like a really inefficient/wasteful way to go about it.

There's a nice attribute called select_history in the bmesh module that gives you a list of vertices in the order that they were selected...but of course, it will only give you verts that have been selected manually (not by box or circle select) after the bmesh was initialized.

I've been searching through the edit mesh api for an attribute that will give me an updated list of selected verts (so that I don't have to iterate over the whole mesh). Does such a thing not exist?

$\endgroup$

9 Answers 9

30
$\begingroup$

You have to iterate over the entire mesh since blender isn't storing this data in a separate list internally.

You can use the following line of code to get a list if selected vertices from a bpy.types.Mesh object ('mesh' in the code):

selected_verts = list(filter(lambda v: v.select, mesh.vertices))

or with list comprehension:

selected_verts = [v for v in mesh.vertices if v.select]

However, in most cases, brute force looping through all the vertices of the mesh should be fine.

$\endgroup$
6
  • $\begingroup$ For this example I'd recommend using list comprehension, if you are going to use list(filter(...)), filter returns an iterator but if you only convert to a list immediately theres no reason to use it for simple checks like this. $\endgroup$
    – ideasman42
    Commented Jun 28, 2013 at 4:37
  • 2
    $\begingroup$ I'm in Edit Mode with just one vertex selected, but v.select is true for all the vertices in the mesh :( $\endgroup$
    – endavid
    Commented Mar 30, 2016 at 11:13
  • 3
    $\begingroup$ It seems you have to switch back to Object mode so the selection gets "consolidated". I'll post an answer with the trick, just in case someone else gets stuck there. $\endgroup$
    – endavid
    Commented Mar 30, 2016 at 11:30
  • 1
    $\begingroup$ This doesn't work for multi-object editing in 2.8. Any idea how to solve it? $\endgroup$ Commented Jan 1, 2020 at 20:07
  • 1
    $\begingroup$ update: I found a solution. See my answer down this page. $\endgroup$ Commented Jan 2, 2020 at 0:18
18
$\begingroup$

Just building on Gwenn's great answer,

  • If you are in Edit Mode, all the vertices may appear selected. Switch to Object Mode so the vertex selection gets updated.
  • You can directly access the vertices from active_object.data, without looking for the mesh in bpy.data.meshes

Code:

mode = bpy.context.active_object.mode
# we need to switch from Edit mode to Object mode so the selection gets updated
bpy.ops.object.mode_set(mode='OBJECT')
selectedVerts = [v for v in bpy.context.active_object.data.vertices if v.select]
for v in selectedVerts:
    print(v.co)
# back to whatever mode we were in
bpy.ops.object.mode_set(mode=mode)

Note that you can use the same logic to find selected edges,

selectedEdges = [e for e in bpy.context.active_object.data.edges if e.select]
$\endgroup$
1
  • 1
    $\begingroup$ This should be the accepted answer. $\endgroup$
    – Prasanth
    Commented Feb 7, 2019 at 15:09
11
$\begingroup$
import bpy
import numpy as np

ob = bpy.context.object
count = len(ob.data.vertices)
sel = np.zeros(count, dtype=bool)

ob.data.vertices.foreach_get('select', sel)

print(sel)

# super fast. No looping

The foreach_get and foreach_set methods are a fast way to get at certain collection properties in blender. They can be thousands of times faster than looping and hundreds of times faster than list comprehension. The code above returns a bool array. If you continue to work with numpy arrays in your code you can index subsets of your arrays with the bool array.

x = np.array([0, 0, 1, 0, 1], dtype=bool) 
y = np.array([1, 2, 3, 4, 5]) 
print(y[x]) 

>>> (numpy array([3, 4]))
$\endgroup$
4
  • $\begingroup$ the foreach_get and foreach_set methods are a fast way to get at certain collection properties in blender. They can be thousands of times faster than looping and hundreds of times faster than list comprehension. The code above returns a bool array. If you continue to work with numpy arrays in your code you can index subsets of your arrays with the bool array. x = np.array([0, 0, 1, 0, 1], dtype=np.bool) y = np.array([1, 2, 3, 4, 5]) print(y[x]) >>> (numpy array([3, 4])) $\endgroup$ Commented Sep 1, 2017 at 20:34
  • $\begingroup$ This is so fast! But what happens if the collection changes between the foreach_get and foreach_set operations? ie. bpy.context.scene.objects.foreach_get('select', s). Then two objects are deleted, or maybe added. bpy.context.scene.objects.foreach_set('select', s) will throw an array error. What might be a good way to handle situations like that? $\endgroup$
    – andyV
    Commented Jul 13, 2018 at 21:03
  • 2
    $\begingroup$ If you were keeping track of selected objects and objects might be deleted between the time you create the list and the time you retrieve the list, you have other issues. In this case you could set up a class or dictionary for each object and have a select attribute for your class. You could check to see if the object associated with your class/dictionary is still in the scene. In most cases, I don't have object or vert counts changing between foreach_get and foreach_set in the code. $\endgroup$ Commented Jul 14, 2018 at 21:46
  • $\begingroup$ what if I want to get the vertex positions or color? where can I find out what dtype that is? $\endgroup$
    – eobet
    Commented Mar 18 at 18:28
10
$\begingroup$

In newer versions of Blender you can use (while in Edit Mode):

bpy.context.active_object.data.total_vert_sel
bpy.context.active_object.data.total_edge_sel
bpy.context.active_object.data.total_face_sel

See: https://docs.blender.org/api/current/bpy.types.Mesh.html?highlight=_sel#bpy.types.Mesh.total_edge_sel

Also count_selected_items() returns the number of selected items (vert, edge, face):

bpy.context.active_object.data.count_selected_items()

To programmatically switch to Edit Mode call: bpy.ops.object.mode_set(mode="EDIT”)

$\endgroup$
2
  • $\begingroup$ Thanks @brockmann .. great tip/edit. $\endgroup$ Commented Feb 22, 2021 at 22:14
  • $\begingroup$ Simple and super useful. Great answer overall! $\endgroup$ Commented Sep 24, 2021 at 17:45
10
$\begingroup$

Edit mode bmesh & numpy

Thought I'd throw this one in the ring as an example to speed up getting selection purely from an edit mode bmesh.

For example sake, will take a test run via entering code in python console. The default cube "Cube" is active object.

Load in an edit mode bmesh from the mesh of the current edit object.

>>> import bmesh    
>>> bm = bmesh.from_edit_mesh(C.edit_object.data)

Create a numpy verts array

>>> import numpy as np
>>> verts = np.array(bm.verts)
>>> verts
array([<BMVert(0x7f8ac738e090), index=0>,
       <BMVert(0x7f8ac738e0c8), index=1>,
       <BMVert(0x7f8ac738e100), index=2>,
       <BMVert(0x7f8ac738e138), index=3>,
       <BMVert(0x7f8ac738e170), index=4>,
       <BMVert(0x7f8ac738e1a8), index=5>,
       <BMVert(0x7f8ac738e1e0), index=6>,
       <BMVert(0x7f8ac738e218), index=7>], dtype=object)

Define a "foreach get" method

Example of np.frompyfunc as suggested in this stackoverflow answer

frompyfunc iterates as the [a for a in A.flat] does, but is somewhat faster, and throws all the power of numpy broadcasting at the task.

>>> selected = np.frompyfunc(lambda a: a.select, 1, 1)

With all verts selected

>>> selected(verts)
array([True, True, True, True, True, True, True, True], dtype=object)

Change selection in UI to single vert, run again

>>> selected(verts)
array([False, False, False, False, False, True, False, False],
      dtype=object)

Cannot index with the object dtype

>>> sel = selected(verts).astype(bool)
>>> sel
array([False, False, False, False, False,  True, False, False])

>>> verts[sel]
array([<BMVert(0x7f8ac738e1a8), index=5>], dtype=object)

Above could be used to create a function, "get_where" that takes the sequence, the property name and value to check, and the property to return

selected_verts = get_where(bm.verts, "select", True, "index")

returning an array of indices of selected vertices.

$\endgroup$
2
  • 1
    $\begingroup$ Thanks a ton, I was exactly looking for functional examples like this. frompyfunc & get_where are so great. $\endgroup$
    – melMass
    Commented Aug 14, 2021 at 21:53
  • $\begingroup$ Thanks, appreciated. $\endgroup$
    – batFINGER
    Commented Aug 15, 2021 at 6:44
4
$\begingroup$

Related to the numpy approach which is super fast to get a count - the issue for me was - now that I have a count - how do I quickly get which vertex it is without loops:

    vert_count = len(obj.data.vertices)
    sel = np.zeros(vert_count, dtype=np.bool)
    obj.data.vertices.foreach_get('select', sel)
    sel_count = np.count_nonzero(sel)

    # get the actual vertices that are selected
    nv = np.array(obj.data.vertices)
    sel_vert_list = nv[sel]
$\endgroup$
4
  • 1
    $\begingroup$ Is this an answer or a question? $\endgroup$ Commented May 10, 2020 at 10:59
  • 3
    $\begingroup$ It is my solution that adds to the OP the code to get the actual list of selected vertices. $\endgroup$
    – EMCS
    Commented May 11, 2020 at 13:27
  • $\begingroup$ I did some print to understand better. very interesting. is there a kind of limit of vertices number from where this is better to use numpy? I mean for 50 vertices if you do [v for v in obj.data.vertices if v.select] is numpy slower? or not really and this is useless to do a condition to change the script? $\endgroup$ Commented Oct 13, 2021 at 18:31
  • $\begingroup$ well I found my answer as fast I asked blog.michelanders.nl/2016/02/… $\endgroup$ Commented Oct 13, 2021 at 18:38
3
$\begingroup$

Since Blender 2.8, multi-object editing makes the previously given answers insufficient, as they don't list the selected vertices of non active objects.

The following snippet solves this issue by creating a dictionary with the paths of the edited objects as keys and the indices of their respective selected vertices as key values :

import bpy
import bmesh

dic_v = {}
for o in bpy.context.objects_in_mode:
    dic_v.update( {o : []} )
    bm = bmesh.from_edit_mesh(o.data)
    for v in bm.verts:
        if v.select:
            dic_v[o].append(v.index)

print(dic_v)
$\endgroup$
1
  • $\begingroup$ best solution yet $\endgroup$ Commented Jul 18, 2021 at 17:01
1
$\begingroup$

problem concerning this subject, using blender v2.78

v.select value of v in bpy.context.active_object.data.vertices seems ALWAYS True:

  • I go in 3D View, select a mesh, go in edit mode: all vertices appear selected
  • I run below described script : all v.select values appear True
  • I manually do a deselect all vertices (pressing A in 3D View, Edit mode)
  • I run the script again : All v.select values remain True!

script does this:

for v in bpy.context.active_object.data.vertices:
    print(str(v.select))

More completely:

I'm using a custom operator to perform this, which assures Edit mode as needed state and allows a shortkey setup to call the operator :

class stitch(bpy.types.Operator):
    bl_idname = "my.printselectedstates"  
    bl_label = "printselectedstates"  

    @classmethod  
    def poll(cls, context):  
        ob = context.active_object  
        return ob is not None and ob.mode == 'EDIT' 

    def execute(self, context):  
        for v in context.active_object.data.vertices:
            print(str(v.index) + ' ' + str(v.select)) 
        return {'FINISHED'} 
$\endgroup$
2
  • $\begingroup$ solution : blenderartists.org/forum/archive/index.php/t-227726.html $\endgroup$
    – user42646
    Commented Aug 25, 2017 at 4:28
  • 1
    $\begingroup$ solution built in the code : inside that 'def execute' do a object/edit mode switch before the print loop. add these two lines bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='EDIT') $\endgroup$
    – user42646
    Commented Aug 25, 2017 at 4:28
0
$\begingroup$

print index of selected vertex with order

import bpy
import bmesh

obj = bpy.context.object

data = obj.data
bm = bmesh.from_edit_mesh(data)

v = [s.index for s in bm.select_history if isinstance(s, bmesh.types.BMVert)]
print(v)
$\endgroup$
1
  • $\begingroup$ As mentioned in question, select history only gives manually selected elements, not those selected by other means. $\endgroup$
    – batFINGER
    Commented Mar 9, 2020 at 11:50

You must log in to answer this question.

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