3
$\begingroup$

[Looking for answers possible via python]

Hi everyone!

I'm trying to select a face/vertex/edge based on location rather than selecting it based on its specific index. After the specific face/vertex/edge is selected, I have a code that I've found online that can then increase the size of the selected face by a radial distance, which is working the way I want it to.

I've found out that the index number changes when I run the script to generate everything in my model, so I can't (or don't know how to) select the specific face that exists in the location that i need to generate the radius of selected faces at, based on indexing. I thought a simple solution would be to select based on location instead (ie., 0,0,1), but after searching online for a solution I haven't been able to get any closer to a resolution.

If anyone has ideas or links to help, I'd appreciate your help. Please note though, I am a beginner in python and Blender so descriptive explanations would be appreciated.

Attached are images and code to better describe my problem:

This is an image of the working script that creates a radius of selection around a manually PRE-SELECTED FACE. It works great, but I need the face to be selected via python (not manually) and the face that is selected can't change location because the face index changed when the script is run a second time, etc: enter image description here

Here's the script that creates the above radius around a manually pre-selected face:

# This example assumes we have a mesh object in edit-mode

import bpy
import bmesh
# from mathutils import Vector

# Get the active mesh
ob = bpy.context.object # get the active object for example
mesh = ob.data

#45438 face number to start at. <---this changes when rerunning script
#length should equal radius of circle *1.1666

# Get a BMesh representation
bm = bmesh.from_edit_mesh(mesh)

# notice in Bmesh polygons are called faces
#bm.faces[45438].select = True  # select index 4  #this line doesn't work

# this is a coordinate like Vector((0,0,0))
active_median = bm.faces.active.calc_center_median()

# Modify the BMesh, can do anything here...
for f in bm.faces:
    f.select = False
    if (f.calc_center_median()-active_median).length <= 0.035:
        f.select = True

# Show the updates in the viewport
# and recalculate n-gon tessellation.
bmesh.update_edit_mesh(mesh, True)
$\endgroup$

2 Answers 2

4
$\begingroup$

Some methods to do this, in my preferred order. All Scripts below will select the closest face to the scene cursor.

BVH Tree

The module mathutils.bvhtree

BVH tree structures for proximity searches and ray casts on geometry.

Make a lookup tree directly from the bmesh and use its find_nearest(location) method to return index of element closest to that location.

Test script run in edit mode

import bpy
import bmesh
from mathutils.bvhtree import BVHTree
context = bpy.context
ob = context.object
scene = context.scene
mwi = ob.matrix_world.inverted()

loc = mwi @ scene.cursor.location

me = ob.data
bm = bmesh.from_edit_mesh(me)
bvhtree = BVHTree.FromBMesh(bm)

pos, norm, idx, d = bvhtree.find_nearest(loc)
if pos is not None: # is zero vector boolean False?
    for f in bm.faces:
        f.select = f.index == idx

    bmesh.update_edit_mesh(me)

Sort the faces

If the mesh is pretty small then sorting the faces based on distance from location to face centre, could suffice.

faces = sorted(bm.faces, 
        key=lambda f: (f.calc_center_median() - loc).length)
closest = faces[0]

Closest point on mesh.

Object.closest_point_on_mesh(...) Uses object or local coordinates

returns valid, location of closest point, face normal, face index.

The closest point on mesh must be run in object mode. Remember that switching modes will most likely trash any existing edit mode bmesh.

mesh version

Toggle to object mode, select then switch to edit mode,

import bpy
import bmesh
# from mathutils import Vector
bpy.ops.object.mode_set()
context = bpy.context
ob = context.object
scene = context.scene
mwi = ob.matrix_world.inverted()

loc = mwi @ scene.cursor.location

hit, loc, norm, idx = ob.closest_point_on_mesh(loc)

if hit:
    me = ob.data
    me.polygons.foreach_set("select", 
            [p.index == idx for p in me.polygons])
    bpy.ops.object.mode_set(mode='EDIT')

bmesh version

import bpy
import bmesh
# from mathutils import Vector
bpy.ops.object.mode_set()
context = bpy.context
ob = context.object
scene = context.scene
mwi = ob.matrix_world.inverted()

loc = mwi @ scene.cursor.location

hit, loc, norm, idx = ob.closest_point_on_mesh(loc)

if hit:
    bpy.ops.object.mode_set(mode='EDIT')
    me = ob.data
    bm = bmesh.from_edit_mesh(me)
    for f in bm.faces:
        f.select = f.index == idx

    bmesh.update_edit_mesh(me, True)

Note: v2.80 Test Script(s) Substitute loc = mwi * scene.cursor_location to make them 2.7x compato

$\endgroup$
3
  • 1
    $\begingroup$ Hi batFINGER. First I'd just like to say that I've read your answers on the forums for a variety of other problems that I've had to sort out and you've been a great help to me already. $\endgroup$
    – user70711
    Mar 21, 2019 at 8:40
  • $\begingroup$ I've looked at the python api reference for this function and I've been able to run it and print out the face index. I haven't been able to get the index output to be "selected" yet though: CenterFace = obj.closest_point_on_mesh(xyz) #xyz is vector initiated at right loc obj.data.polygons[CenterFace[3]].select = True #this doesn't seem to select the face I've got a few tasks to do at work, but I'll continue working for a solution later today. If you have time for any more comments I'd appreciate it. Thanks! $\endgroup$
    – user70711
    Mar 21, 2019 at 8:47
  • $\begingroup$ Thanks for your detailed response @batFINGER. I've been working on different aspects of the project and have delayed work on this specific issue. I'll begin working on it again soon and try out your suggestions and then update again. $\endgroup$
    – user70711
    Apr 23, 2019 at 12:46
1
$\begingroup$

this is an update for blender 2.80+ (tested with blender 3.0)

## script for blender 2.80+ (tested with 3.0)
##
## place 3d cursor on the object (shift+right_mouse), 
## run the script (in object mode) 
## it will select the face under the 3D cursor 
##
import bpy
import bmesh
#from mathutils import Vector as Vec


context = bpy.context
ob = context.object
scene = context.scene
mwi = ob.matrix_world.inverted()
#
loc = mwi @ scene.cursor.location

#objB = scene.objects['ball']
##############



hit, loc, norm, idx = ob.closest_point_on_mesh(loc)

if hit:
    bpy.ops.object.mode_set(mode='EDIT')
    me = ob.data
    bm = bmesh.from_edit_mesh(me)
    #
    for f in bm.faces:
        #
        if f.index == idx:
            f.select_set( True )
        #
    #
    #objB.location = loc
    bmesh.update_edit_mesh(me)
$\endgroup$

You must log in to answer this question.

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