"""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 three pipes (each a fitting, a run and a ceiling coupling). The parts of one pipe meet by design (the bands clamp the film, the film slides over the spigot): those pairs are listed, not counted. Smallest gaps from the liquid to the glass, from the canisters and the pipes to the body. 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 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() names = ["SM_NutritionVat_Body", "SM_NutritionVat_Fluid", "NutritionVat_Canister_R", "NutritionVat_Canister_L"] names += sorted(o.name for o in bpy.data.objects if o.name.startswith("NutritionVat_Pipe")) obs = {n: bpy.data.objects[n] for n in names} T = {n: tree(o) for n, o in obs.items()} rep = {"overlaps": {}, "same_pipe_contacts": {}, "gaps_mm": {}, "audit": {}} pipe_of = lambda n: n.rsplit("_", 1)[1] if n.startswith("NutritionVat_Pipe") else None for i in range(len(names)): for j in range(i + 1, len(names)): a, b = names[i], names[j] n = len(T[a][0].overlap(T[b][0])) if pipe_of(a) and pipe_of(a) == pipe_of(b): rep["same_pipe_contacts"]["%s x %s" % (a, b)] = n else: rep["overlaps"]["%s x %s" % (a, b)] = n body = T["SM_NutritionVat_Body"][0] rep["gaps_mm"]["liquid to body (glass)"] = round(gap(T["SM_NutritionVat_Fluid"][1], body) * 1000, 2) for c in names[2:]: rep["gaps_mm"][c + " to body"] = round(gap(T[c][1], body) * 1000, 2) for n, o in obs.items(): if n.startswith("NutritionVat_Pipe") and not n.endswith("_A"): continue # the three pipes share their meshes 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({k: v for k, v in rep["overlaps"].items() if v}), json.dumps(rep["gaps_mm"])) main()