"""Check the vat: separate parts against each other, topology. Nothing is saved. blender.exe -b ../NutritionVat.blend --python nv_check.py -> build/check_report.json Triangle overlap in world space (BVH) between every pair of separate meshes: the body, the liquid, the two canisters and the hoses; the hoses also tube against tube. Smallest gaps from the liquid to the glass and from the canisters to their clamp rings. Topology of each mesh. """ import bpy, bmesh, json, os, math from mathutils.bvhtree import BVHTree HERE = os.path.dirname(os.path.abspath(__file__)) exec(open(os.path.join(HERE, "nv_lib.py")).read()) def world_bm(ob): bm = bmesh.new() bm.from_mesh(ob.data) bm.transform(ob.matrix_world) return bm def tree(ob): bm = world_bm(ob) t = BVHTree.FromBMesh(bm) vs = [v.co.copy() for v in bm.verts] bm.free() return t, vs def parts_of(ob): """one BVH per connected piece (the hoses' tubes)""" bm = world_bm(ob) bm.verts.ensure_lookup_table() seen, out = set(), [] for v0 in bm.verts: if v0.index in seen: continue comp, stack = [], [v0] seen.add(v0.index) while stack: v = stack.pop() comp.append(v) for e in v.link_edges: w = e.other_vert(v) if w.index not in seen: seen.add(w.index) stack.append(w) ids = {v.index for v in comp} faces = [f for f in bm.faces if f.verts[0].index in ids] out.append(BVHTree.FromPolygons([v.co.copy() for v in bm.verts], [[v.index for v in f.verts] for f in faces])) bm.free() return out def gap(verts, t): best = 1e9 for v in verts: hit = t.find_nearest(v) if hit[0] is not None: best = min(best, hit[3]) return best def main(): bpy.context.view_layer.update() obs = {n: bpy.data.objects[n] for n in ("SM_NutritionVat_Body", "SM_NutritionVat_Fluid", "NutritionVat_Canister_R", "NutritionVat_Canister_L", "SM_NutritionVat_Hoses")} T = {n: tree(o) for n, o in obs.items()} rep = {"overlaps": {}, "gaps_mm": {}, "audit": {}} names = list(obs) for i in range(len(names)): for j in range(i + 1, len(names)): a, b = names[i], names[j] rep["overlaps"]["%s x %s" % (a, b)] = len(T[a][0].overlap(T[b][0])) tubes = parts_of(obs["SM_NutritionVat_Hoses"]) hh = 0 pairs = [] for i in range(len(tubes)): for j in range(i + 1, len(tubes)): n = len(tubes[i].overlap(tubes[j])) if n: hh += n pairs.append((i, j, n)) rep["overlaps"]["hose x hose"] = hh rep["hose_pairs_touching"] = pairs rep["hoses"] = len(tubes) rep["gaps_mm"]["liquid to body (glass)"] = round(gap(T["SM_NutritionVat_Fluid"][1], T["SM_NutritionVat_Body"][0]) * 1000, 2) for c in ("NutritionVat_Canister_R", "NutritionVat_Canister_L"): rep["gaps_mm"][c + " to body"] = round(gap(T[c][1], T["SM_NutritionVat_Body"][0]) * 1000, 2) rep["gaps_mm"]["hoses to body"] = round(gap(T["SM_NutritionVat_Hoses"][1][::4], T["SM_NutritionVat_Body"][0]) * 1000, 2) for n, o in obs.items(): rep["audit"][n] = audit(o) clash = sum(v for v in rep["overlaps"].values()) rep["clash_pairs_total"] = clash with open(os.path.join(HERE, "check_report.json"), "w") as fh: json.dump(rep, fh, indent=1) print("CHECK OK" if not clash else "CHECK CLASH", json.dumps(rep)) main()