# Critique round 2 (read-only): open (boundary) edges of SM_Grouper_Hull round the collar, and the rays' entry points import bpy, bmesh, json, os, math, collections from mathutils import Vector from mathutils.bvhtree import BVHTree ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) o = bpy.data.objects["SM_Grouper_Hull"]; M = o.matrix_world bm = bmesh.new(); bm.from_mesh(o.data); bm.edges.ensure_lookup_table() cl = Vector((0.0, -0.09)) near = [] for e in bm.edges: if len(e.link_faces) != 1: continue m = M @ ((e.verts[0].co + e.verts[1].co) / 2) r = math.hypot(m.x - cl.x, m.y - cl.y) if r < 0.9 and m.z > 0.85: ang = (math.degrees(math.atan2(m.y - cl.y, m.x - cl.x)) + 360) % 360 near.append((round(ang), round(r, 3), round(m.z, 3), round(e.calc_length() * 1000, 1))) # group the open edges by angle sector and radius sec = collections.Counter((int(a // 15) * 15, round(r, 1)) for a, r, z, L in near) # walk the boundary into loops bnd = [e for e in bm.edges if len(e.link_faces) == 1] adj = collections.defaultdict(list) for e in bnd: for v in e.verts: adj[v.index].append(e) seen = set(); loops = [] for e in bnd: if e.index in seen: continue stack = [e]; seen.add(e.index); es = [] while stack: g = stack.pop(); es.append(g) for v in g.verts: for h in adj[v.index]: if h.index not in seen: seen.add(h.index); stack.append(h) pts = [M @ v.co for g in es for v in g.verts] c = sum(pts, Vector()) / len(pts) r = math.hypot(c.x - cl.x, c.y - cl.y) if r < 0.95 and c.z > 0.8: lo = [round(min(p[i] for p in pts), 3) for i in range(3)]; hi = [round(max(p[i] for p in pts), 3) for i in range(3)] loops.append(dict(edges=len(es), centre=[round(x, 3) for x in c], lo=lo, hi=hi, length_m=round(sum(g.calc_length() for g in es), 3))) res = dict(open_edges_near_collar=len(near), by_sector=sorted([[k[0], k[1], v] for k, v in sec.items()]), loops=loops) json.dump(res, open(os.path.join(ROOT, "critique", "round2", "collar_edges.json"), "w"), indent=1) print("CR2 EDGES", len(near), len(loops)) for L in loops: print(L)