"""Grouper: twisted and folded quads in SM_Grouper_Hull (read-only): every quad whose two triangles (either diagonal) differ by more than 25 deg, by material and place; over 90 deg = a fold. -> printed (TWIST ...) blender.exe -b Tender_Grouper.blend --python build/gr_fold_audit.py""" import bpy, bmesh, math from mathutils import Vector o = bpy.data.objects["SM_Grouper_Hull"] bm = bmesh.new(); bm.from_mesh(o.data) tw = [] for f in bm.faces: if len(f.verts) != 4: continue a, b, c, d = [v.co for v in f.verts] n1 = (b - a).cross(c - a); n2 = (c - a).cross(d - a) n3 = (b - a).cross(d - a); n4 = (c - b).cross(d - b) if n1.length < 1e-12 or n2.length < 1e-12: continue ang = math.degrees(n1.angle(n2)) ang2 = math.degrees(n3.angle(n4)) if n3.length > 1e-12 and n4.length > 1e-12 else 0 if max(ang, ang2) > 25: tw.append((max(ang, ang2), tuple(round(v, 3) for v in f.calc_center_median()), o.data.materials[f.material_index].name)) tw.sort(reverse=True) print("TWIST faces >25deg:", len(tw)) for t in tw[:25]: print("TWIST", t) import collections c = collections.Counter() for ang, p, m in tw: c[(m, round(p[1] * 2) / 2)] += 1 print("TWIST by material/y:", sorted(c.items(), key=lambda kv: -kv[1])[:30]) big = [t for t in tw if t[0] > 90] c2 = collections.Counter((m, round(p[1] * 4) / 4, round(p[2] * 4) / 4) for a, p, m in big) print("TWIST >90:", len(big), sorted(c2.items(), key=lambda kv: -kv[1])[:20]) for t in big[:30]: print("BIG", round(t[0]), t[1], t[2])