how can i select all concave quads, is there such a tool? enter image description here

  • $\begingroup$ Not sure if this is going to get you the selection that you need, but if you select one face and then press SHIFT + G you will see a menu called "Select Similar" try with one option and see if that works for you $\endgroup$
    – Emir
    Commented May 11, 2021 at 12:54
  • $\begingroup$ @Emir nah, none of those work $\endgroup$ Commented May 11, 2021 at 13:04

3 Answers 3


This may be a naive answer, I'm no coder, but is there anything wrong with letting BMLoop.is_convex do this for you?

(Starting from Edit Mode):

import bpy
import bmesh

obj = bpy.context.edit_object
me = obj.data

bm = bmesh.from_edit_mesh(me)
bm.faces.active = None

for face in bm.faces:
    for loop in face.loops:
        if not loop.is_convex:

bmesh.update_edit_mesh(me, False)
  • $\begingroup$ Looks good to me. Could use face.select_set(not any(loop.is_convex for loop in face.loops)) as I'm led to believe it is quicker, although can't see it being any more than an bee's diaphragm. $\endgroup$
    – batFINGER
    Commented May 11, 2021 at 17:35
  • $\begingroup$ @batFINGER there ya go! I knew there would be a snappier way of putting it! I'll just leave it in your comment. $\endgroup$
    – Robin Betts
    Commented May 11, 2021 at 17:42
  • $\begingroup$ Good find, will make that edge angle answer easier too, blender.stackexchange.com/questions/203249/… in that can simply use edge.angle(otheredge) and add pi if not convex. Consider adding as answer there too. Gives me that ",,,Whale oil beef hooked how did I miss thaaaaat" feeling. $\endgroup$
    – batFINGER
    Commented May 11, 2021 at 17:50
  • $\begingroup$ @batFINGER wilco, when I have some time to grind through my incredibly slow beginner's testing-and-looking-stuff-up, unless you go in first. I can't remember when I spotted that property.. it was in a completely different context. $\endgroup$
    – Robin Betts
    Commented May 11, 2021 at 18:01
  • 1
    $\begingroup$ cool, this is kind of odd to me that although there is a parameter that straightly tells you, that the face is convex , there is no just button in GUI for common users $\endgroup$ Commented May 11, 2021 at 18:06

ok, so I made a modification of this script: Face edges angle - python

I simply putted some selection commands at the end. it will do the work (you need to select the faces you want to check)

# based on the script by batFINGER: https://blender.stackexchange.com/questions/203249/face-edges-angle-python
import bpy
from mathutils import Matrix, Vector
from bpy import context
from math import degrees, atan2, pi
import bmesh
# project into XY plane, 
up = Vector((0, 0, 1))

ob = context.object
me = ob.data
bm = bmesh.from_edit_mesh(me)
def edge_angle(e1, e2, face_normal):
    b = set(e1.verts).intersection(e2.verts).pop()
    a = e1.other_vert(b).co - b.co
    c = e2.other_vert(b).co - b.co
    axis = a.cross(c).normalized()
    if axis.length < 1e-5:
        return pi # inline vert
    if axis.dot(face_normal) < 0:
    M = axis.rotation_difference(up).to_matrix().to_4x4()  

    a = (M @ a).xy.normalized()
    c = (M @ c).xy.normalized()
    return pi - atan2(a.cross(c), a.dot(c))

selected_faces = [f for f in bm.faces if f.select]
for f in selected_faces:
    edges = f.edges[:]
    #print("Face", f.index, "Edges:", [e.index for e in edges])
    for e1, e2 in zip(edges, edges[1:]):

        angle = edge_angle(e1, e2, f.normal)
        #print("Edge Corner", e1.index, e2.index, "Angle:", degrees(angle))
        if degrees(angle) >= 180:
  • 1
    $\begingroup$ Could possibly do something like f.select_set(any(edge_angle(e1, e2, f.normal) > pi for e1, e2 in zip(edges, edges[1:])) $\endgroup$
    – batFINGER
    Commented May 11, 2021 at 16:44
  • $\begingroup$ @batFINGER thanks :) $\endgroup$ Commented May 11, 2021 at 18:11

I see I'm late, though I ended up with simpler code:

import bpy

tolerance = 0  # increase to something like .01 or .1 to ignore small concavities

bpy.ops.object.mode_set(mode = 'EDIT') 
bpy.ops.mesh.select_all(action = 'DESELECT')
bpy.ops.object.mode_set(mode = 'OBJECT')

me = bpy.context.active_object.data
poly_i_to_tris = {}
for tri in me.loop_triangles:
    poly_i_to_tris.setdefault(tri.polygon_index, []).append(tri)

for i, tris in poly_i_to_tris.items():
        t1, t2 = tris
    except ValueError:
        continue  # not a quad
    dist = (t1.center - t2.center).length
    tolerance *= dist  # makes tolerance relative
    ray_len = dist/2  # making sure to not overshoot, but I don't think it would be possible
    test1 = t1.center + t1.normal*ray_len
    test2 = t2.center + t2.normal*ray_len
    test_dist = (test1 - test2).length
    if test_dist < dist - tolerance:
        # concave
        me.polygons[i].select = True
bpy.ops.object.mode_set(mode = 'EDIT') 

  • $\begingroup$ If you decide to remove ray_len, then use dist instead of not multiplying the normals - or otherwise you will lose the relativity of tolerance. 🤔 $\endgroup$ Commented May 11, 2021 at 14:28
  • $\begingroup$ Reading through the other answer's code, I now realize I misunderstood the question. What I thought of a concave quad, is a quad triangulated in such way, that the diagonal of the quad (the edge created by tessellation) falls down along the face normal. You can clearly see it in attached .blend file. $\endgroup$ Commented May 11, 2021 at 14:36
  • $\begingroup$ Wondered about that, Would you say this is looking for non-coplanar faces? $\endgroup$
    – batFINGER
    Commented May 11, 2021 at 16:06
  • $\begingroup$ @batFINGER in short I'm checking if two segments starting at centers of triangles and going outside along normals, end up being closer than those centers. If so, those triangles look at each-other, and the quad is "concave". If the opposite is the case, the triangles are upset at each-other, and the quad is "convex". Otherwise the quad is "flat" or coplanar. (in my script I just select the "concave" because that was the question, but you could differentiate all three, and e.g. color the cube dynamically) $\endgroup$ Commented May 11, 2021 at 16:10
  • 1
    $\begingroup$ Also similarly to @RobinBetts answer above, looks like this test could be achieved very simply with BMEdge.is_convex. $\endgroup$
    – batFINGER
    Commented May 11, 2021 at 17:41

You must log in to answer this question.

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