"""Check the feeder: drivers, the moving parts through their whole travel, topology. Nothing is saved. blender.exe -b ../NutritionFeeder.blend --python nf_check.py -> build/check_report.json Runs NutritionFeeder_Root["inject"] from 0 to 1 in 101 steps and tests the nozzle and the needle against the body and against each other (triangle overlap in world space, BVH), and measures their smallest clearance to the body's bore. """ 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, "nf_lib.py")).read()) def bvh(ob, dg): ev = ob.evaluated_get(dg) me = ev.to_mesh() bm = bmesh.new() bm.from_mesh(me) bm.transform(ev.matrix_world) t = BVHTree.FromBMesh(bm) verts = [v.co.copy() for v in bm.verts] bm.free() ev.to_mesh_clear() return t, verts def main(): sc = bpy.context.scene root = bpy.data.objects["NutritionFeeder_Root"] body = bpy.data.objects["SM_NutritionFeeder_Body"] nozzle = bpy.data.objects["SM_NutritionFeeder_Nozzle"] needle = bpy.data.objects["SM_NutritionFeeder_Needle"] rep = {"drivers": {}, "travel": {}, "clashes": [], "audit": {}} for ob in (nozzle, needle): for fc in ob.animation_data.drivers: d = fc.driver rep["drivers"][ob.name] = dict(path=fc.data_path, index=fc.array_index, valid=d.is_valid, simple=d.is_simple_expression, expression=d.expression) if root.animation_data: root.animation_data.action = None dg = bpy.context.evaluated_depsgraph_get() tb, _ = bvh(body, dg) min_gap = {nozzle.name: 1e9, needle.name: 1e9} for k in range(101): root["inject"] = k / 100.0 root.update_tag() # a property set from Python is not tagged by itself sc.frame_set(1 + (k % 2)) dg = bpy.context.evaluated_depsgraph_get() dg.update() tn, vn = bvh(nozzle, dg) td, vd = bvh(needle, dg) for name, t in ((nozzle.name, tn), (needle.name, td)): n = len(t.overlap(tb)) if n: rep["clashes"].append(dict(inject=k / 100.0, part=name, against=body.name, pairs=n)) n = len(tn.overlap(td)) if n: rep["clashes"].append(dict(inject=k / 100.0, part=nozzle.name, against=needle.name, pairs=n)) for name, verts in ((nozzle.name, vn), (needle.name, vd)): for v in verts[::3]: hit = tb.find_nearest(v) if hit[0] is not None: min_gap[name] = min(min_gap[name], hit[3]) if k in (0, 55, 100): ev = lambda o: o.evaluated_get(dg).matrix_world.translation rep["travel"]["inject %.2f" % (k / 100)] = {o.name: [round(c, 5) for c in ev(o)] for o in (nozzle, needle)} rep["smallest_gap_to_body_mm"] = {k: round(v * 1000, 2) for k, v in min_gap.items()} for ob in (body, nozzle, needle): rep["audit"][ob.name] = audit(ob) with open(os.path.join(HERE, "check_report.json"), "w") as fh: json.dump(rep, fh, indent=1) print("CHECK OK" if not rep["clashes"] else "CHECK CLASH", json.dumps(rep)) main()