"""Grouper: clashes between attached parts (read-only). SM_Grouper_Hull is split into its connected pieces (the skin with the duct is one, every fitting, trim and bracket another); every pair of pieces whose triangles intersect is listed with where, how many pairs and the pieces' materials, so intended joints (a pylon into its lamp head, a bracket onto a knuckle, a part sunk into the skin) can be told from accidental clashes. Also the moving parts at rest against the hull. blender.exe -b Tender_Grouper.blend --python build/gr_clash_audit.py -> build/checks/clash.json""" import bpy, bmesh, os, json, math from mathutils import Vector from mathutils.bvhtree import BVHTree HERE = os.path.dirname(os.path.abspath(__file__)) os.makedirs(os.path.join(HERE, "checks"), exist_ok=True) o = bpy.data.objects["SM_Grouper_Hull"] me = o.data bm = bmesh.new() bm.from_mesh(me) bm.faces.ensure_lookup_table() # connected pieces comp = {} cid = 0 for f in bm.faces: if f.index in comp: continue stack = [f] comp[f.index] = cid while stack: g = stack.pop() for e in g.edges: for h in e.link_faces: if h.index not in comp: comp[h.index] = cid stack.append(h) cid += 1 sizes = {} for fi, c in comp.items(): sizes[c] = sizes.get(c, 0) + 1 skin = max(sizes, key=sizes.get) pieces = {} for f in bm.faces: pieces.setdefault(comp[f.index], []).append(f.index) M_ = o.matrix_world def piece_tree(fids): vs, ts, vmap = [], [], {} for fi in fids: f = bm.faces[fi] ids = [] for v in f.verts: if v.index not in vmap: vmap[v.index] = len(vs) vs.append(M_ @ v.co) ids.append(vmap[v.index]) for k in range(1, len(ids) - 1): ts.append((ids[0], ids[k], ids[k + 1])) return BVHTree.FromPolygons(vs, ts), vs, ts trees = {c: piece_tree(f) for c, f in pieces.items()} info = {} for c, fids in pieces.items(): vs = trees[c][1] cen = sum(vs, Vector()) / len(vs) mats = sorted({me.materials[bm.faces[fi].material_index].name.replace("M_Grouper_", "") for fi in fids}) info[c] = {"faces": len(fids), "centre": [round(v, 3) for v in cen], "materials": mats} pairs = [] cs = sorted(pieces) for a_i, a in enumerate(cs): ta = trees[a] for b in cs[a_i + 1:]: tb = trees[b] ov = ta[0].overlap(tb[0]) if ov: i, j = ov[0] p = (ta[1][ta[2][i][0]] + ta[1][ta[2][i][1]] + ta[1][ta[2][i][2]]) / 3 pairs.append({"a": "skin" if a == skin else a, "b": "skin" if b == skin else b, "tri_pairs": len(ov), "at": [round(v, 3) for v in p], "a_mats": info[a]["materials"], "b_mats": info[b]["materials"], "a_faces": info[a]["faces"], "b_faces": info[b]["faces"]}) with_skin = [p for p in pairs if "skin" in (p["a"], p["b"])] between = [p for p in pairs if "skin" not in (p["a"], p["b"])] out = {"pieces": len(pieces), "skin_piece_faces": sizes[skin], "pairs_with_skin": len(with_skin), "pairs_between_parts": len(between), "between_parts": sorted(between, key=lambda p: -p["tri_pairs"]), "with_skin": sorted(with_skin, key=lambda p: -p["tri_pairs"])} json.dump(out, open(os.path.join(HERE, "checks", "clash.json"), "w"), indent=1) print("CLASH pieces", len(pieces), "pairs with the skin", len(with_skin), "between parts", len(between)) for p in out["between_parts"][:60]: print("CLASH", p["tri_pairs"], p["at"], p["a_mats"], p["a_faces"], "x", p["b_mats"], p["b_faces"])