"""Moving parts of the Manta through their travel against everything that does not move (read only, never saved): rotor 0..72 deg (one blade pitch, 3 deg), tool boom swing -45..+45 deg (5), drill bit 0..180 deg (10), also at the boom's ends. Runs headless: blender -b Tender_Manta.blend --python build/motion_check.py""" import bpy, math, json, os from mathutils import Vector from mathutils.bvhtree import BVHTree HERE = r"D:\HADAL - The Descent\models\tender-manta\build" O = bpy.data.objects def tree(o): bpy.context.view_layer.update() dg = bpy.context.evaluated_depsgraph_get() ev = o.evaluated_get(dg) me = ev.to_mesh(); me.calc_loop_triangles() M = ev.matrix_world vs = [M @ v.co for v in me.vertices] t = BVHTree.FromPolygons(vs, [tuple(x.vertices) for x in me.loop_triangles]) ev.to_mesh_clear() return t, vs root = O["Manta_Root"] for o in O: # drivers off: the check sets angles itself if o.animation_data: for d in o.animation_data.drivers: d.mute = True ST = [tree(O["SM_Manta_Hull"]), tree(O["SM_Manta_Glass"])] def least(vs, step=2): best = 9.0 for t, _ in ST: for p in vs[::step]: h = t.find_nearest(p) if h[0] is not None: best = min(best, h[3]) return best def sweep(objs, setter, angles): rows = [] for a in angles: setter(a) hits, gap = 0, 9.0 for o in objs: t, vs = tree(o) hits += sum(len(t.overlap(s)) for s, _ in ST) gap = min(gap, least(vs)) rows.append({"deg": a, "intersections": hits, "least_gap_m": round(gap, 4)}) setter(0) return {"worst_gap_m": min(r["least_gap_m"] for r in rows), "angles_with_intersections": [r["deg"] for r in rows if r["intersections"]], "rows": rows} rot, boom, bit = O["SM_Manta_Rotor"], O["SM_Manta_ToolBoom"], O["SM_Manta_ToolBit"] def set_rot(a): rot.rotation_euler = (0, math.radians(a), 0) def set_boom(a): boom.rotation_euler = (0, 0, math.radians(a)) def set_bit(a): bit.rotation_euler = (0, math.radians(a), 0) out = {"rotor": sweep([rot], set_rot, list(range(0, 73, 3))), "boom": sweep([boom, bit], set_boom, list(range(-45, 46, 5)))} clear = [r["deg"] for r in out["boom"]["rows"] if not r["intersections"] and r["least_gap_m"] >= 0.005] # the hinge runs 1 cm clear by design out["boom_clear_range_deg"] = [min(clear), max(clear)] if clear else None for end in (out["boom_clear_range_deg"] or [0]): set_boom(end) out["bit_at_%d" % end] = sweep([bit], set_bit, list(range(0, 181, 10))) set_boom(0) movers = [rot, boom, bit] mt = {o.name: tree(o)[0] for o in movers} out["movers_touching_each_other"] = [(a.name, b.name) for i, a in enumerate(movers) for b in movers[i + 1:] if b.parent is not a and mt[a.name].overlap(mt[b.name])] json.dump(out, open(os.path.join(HERE, "checks", "motion.json"), "w"), indent=1) print("MOTION", json.dumps({k: ({kk: vv for kk, vv in v.items() if kk != "rows"} if isinstance(v, dict) else v) for k, v in out.items()}))