# How to select concave quads

how can i select all concave quads, is there such a tool?

• 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 – Emir May 11 at 12:54
• @Emir nah, none of those work – Timur Nizamiev May 11 at 13:04

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:
face.select_set(False)
for loop in face.loops:
if not loop.is_convex:
face.select_set(True)
break

bmesh.update_edit_mesh(me, False)

• 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. – batFINGER May 11 at 17:35
• @batFINGER there ya go! I knew there would be a snappier way of putting it! I'll just leave it in your comment. – Robin Betts May 11 at 17:42
• 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. – batFINGER May 11 at 17:50
• @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. – Robin Betts May 11 at 18:01
• 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 – Timur Nizamiev May 11 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
a.negate()
axis = a.cross(c).normalized()
if axis.length < 1e-5:
return pi # inline vert

if axis.dot(face_normal) < 0:
axis.negate()
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]
bpy.ops.mesh.select_all(action='TOGGLE')
for f in selected_faces:
edges = f.edges[:]
#print("Face", f.index, "Edges:", [e.index for e in edges])
edges.append(f.edges[0])

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:
f.select_set(True)

• Could possibly do something like f.select_set(any(edge_angle(e1, e2, f.normal) > pi for e1, e2 in zip(edges, edges[1:])) – batFINGER May 11 at 16:44
• @batFINGER thanks :) – Timur Nizamiev May 11 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_mode(type="FACE")
bpy.ops.mesh.select_all(action = 'DESELECT')
bpy.ops.object.mode_set(mode = 'OBJECT')

me = bpy.context.active_object.data
me.calc_loop_triangles()
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():
try:
t1, t2 = tris
except ValueError:
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')


• 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. 🤔 – Markus von Broady May 11 at 14:28
• 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. – Markus von Broady May 11 at 14:36
• Wondered about that, Would you say this is looking for non-coplanar faces? – batFINGER May 11 at 16:06
• @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) – Markus von Broady May 11 at 16:10
• Also similarly to @RobinBetts answer above, looks like this test could be achieved very simply with BMEdge.is_convex. – batFINGER May 11 at 17:41