# Grouper (copied from the Critique's round 2 cr2_decal_warp.py, output to build/checks) (read-only): how far each decal quad is from planar (the gap between its two diagonals), and whether a ray # from outside along the decal's normal meets the decal first or the hull first, on a 1 cm grid over each marking. import bpy, json, os, math, collections from mathutils import Vector from mathutils.bvhtree import BVHTree ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) dg = bpy.context.evaluated_depsgraph_get() def tris(o): ev = o.evaluated_get(dg); me = ev.to_mesh(); me.calc_loop_triangles() M = ev.matrix_world vs = [M @ v.co for v in me.vertices]; ts = [tuple(t.vertices) for t in me.loop_triangles] polys = [[M @ me.vertices[i].co for i in p.vertices] for p in me.polygons] ev.to_mesh_clear(); return vs, ts, polys dv, dt, dp = tris(bpy.data.objects["SM_Grouper_Decals"]) hv, ht, hp = tris(bpy.data.objects["SM_Grouper_Hull"]) warp = [] for q in dp: if len(q) != 4: continue a, b, c, d = q # distance between the diagonal lines a-c and b-d u, v, w = c - a, d - b, a - b n = u.cross(v) warp.append(abs(w.dot(n)) / n.length if n.length > 1e-12 else 0.0) warp.sort() B_dec = BVHTree.FromPolygons(dv, dt); B_hull = BVHTree.FromPolygons(hv, ht) side = {} for name, sx in (("R", -1.0), ("L", 1.0)): lost = 0; tot = 0; where = collections.Counter() for y in [i * 0.01 for i in range(-260, 360)]: for z in [k * 0.01 for k in range(-20, 110)]: o = Vector((sx * 3.0, y, z)); d = Vector((-sx, 0, 0)) hd = B_dec.ray_cast(o, d) if hd[0] is None: continue hh = B_hull.ray_cast(o, d) tot += 1 if hh[0] is not None and hh[3] < hd[3] - 1e-5: lost += 1; where[(round(y, 1), round(z, 1))] += 1 side[name] = dict(decal_samples=tot, hull_in_front=lost, pct=round(100 * lost / max(1, tot), 2), at=where.most_common(8)) res = dict(quads=len(warp), triangles=sum(1 for q in dp if len(q) == 3), # (round 2: the decal quads are split on their ridge) warp_mm_max=round(1000 * warp[-1], 2) if warp else 0.0, warp_mm_p90=round(1000 * warp[int(0.9 * len(warp))], 2) if warp else 0.0, over_1_5mm=sum(1 for w in warp if w > 0.0015), lift_mm="1.5 (3.0 where markings overlap)", side=side) json.dump(res, open(os.path.join(ROOT, "build", "checks", "decal_warp.json"), "w"), indent=1) print("CR2 DECAL", json.dumps(res))