Can I get from bpy which (if any) vertex / edge / face is currently under the mouse cursor? This would be quite useful for making more modeling tools.

Right now what I have to do is, maybe utterly stupid and wasteful on my part, use a selection list and update it when new valid edges are added to the selection. The problem with that is I loop through all edges each time, which can be a bit slower on large meshes.


1 Answer 1


You could transform the mesh to screen space (single locations: vertex coordinates, edge centers, face dots) and find the closest using (cursor-loc).length_squared, e.g. use this utility function:

from bpy_extras.view3d_utils import location_3d_to_region_2d

But it's pretty inefficient. There is no elegant way to retrieve a close-by geometry element.

You could improve the search for a close location in 3d space using the new KDTree Utilities (mathutils.kdtree), but you need to re-generate the kdtree whenever the mesh changes.

Paleajed's Preselection builds might still contain Python-exposed functions to retrieve a close element, at least older versions did (when the preselection highlighting was done in Python, and the script used a patched-in function for a more efficient test).

It depends on your goals, but you could highjack the actual operator that is used for selection making in the UI, which is definitely the most efficient way to select a close element. Here's a demo that selects the closest geometry element, and silently deselects it again:

import bpy
import bmesh

class SimpleOperator(bpy.types.Operator):
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

    def poll(cls, context):
        return (context.object is not None and
                context.object.type == 'MESH' and

    def invoke(self, context, event):
        me = context.object.data
        bm = bmesh.from_edit_mesh(me)
        verts_sel = [v.select for v in bm.verts]
        edges_sel = [e.select for e in bm.edges]
        faces_sel = [f.select for f in bm.faces]
        loc = event.mouse_region_x, event.mouse_region_y
            geom = bm.select_history[-1]
        except IndexError:
            geom = None
        ret = bpy.ops.view3d.select(extend=True, location=loc)
        if ret == {'PASS_THROUGH'}:
            self.report({'INFO'}, "no close-by geom")
            return {'CANCELLED'}
            geom2 = bm.select_history[-1]
            print("geom2 sel 1st", geom2.select)
        except IndexError:
            geom2 = None

        if geom is None:
            geom = geom2
        if isinstance(geom, bmesh.types.BMVert):
            geom_sel = verts_sel
            bm_geom = bm.verts
        elif isinstance(geom, bmesh.types.BMEdge):
            geom_sel = edges_sel
            bm_geom = bm.edges
        elif isinstance(geom, bmesh.types.BMFace):
            geom_sel = faces_sel
            bm_geom = bm.faces

        for sel, g in zip(geom_sel, bm_geom):
            if sel != g.select:
        self.report({'INFO'}, repr(geom))
        return {'FINISHED'}

def register():

def unregister():

if __name__ == "__main__":

Run the operator via spacebar menu over 3D View. It should print the bmesh element in the info header. It does not change selection, if the geom element was selected however, it will become the active element.

  • $\begingroup$ Thanks for your answer and demo, I didn't know about mathutils.kdtree or bm.select_history - both should prove useful to my script. $\endgroup$
    – zeffii
    Mar 20, 2014 at 10:13

You must log in to answer this question.

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