4
$\begingroup$

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.

$\endgroup$
3
$\begingroup$

You could transform the mesh to screen space (single locations: vertex coords, 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 UI, which is definately 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):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

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

    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

        try:
            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'}

        try:
            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:
                g.select_set(False)
                bm.select_history.remove(g)
                bm.select_flush_mode()
                break


        self.report({'INFO'}, repr(geom))
        return {'FINISHED'}


def register():
    bpy.utils.register_class(SimpleOperator)


def unregister():
    bpy.utils.unregister_class(SimpleOperator)


if __name__ == "__main__":
    register()

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.

$\endgroup$
  • $\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 '14 at 10:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.