"""Mako: the moving parts through their travel against the static exterior and each other (blender -b Tender_Mako.blend --python build/motion_check.py, or exec'd in Blender). Writes checks/motion.json; leaves the rig as found. * hatch: 0 .. 90 deg in 2.5 deg steps (its rig stops at 70; the table shows where it would first touch), against the hull (clips, coaming, saddle) - its hinge knuckles turn on the fixed one; * stern arms: 0 .. 90 deg in 3 deg steps, against the hull (housing, duct) and against each other; * rotor: one blade pitch in 4 deg steps, against the hull (duct, stator) - 10 mm tip gap by design.""" import bpy, os, json from mathutils import Vector from mathutils.bvhtree import BVHTree HERE = r"D:\HADAL - The Descent\models\tender-mako\build" root = bpy.data.objects["Mako_Root"] for ob in bpy.data.objects: if ob.animation_data: for fc in ob.animation_data.drivers: fc.driver.expression = fc.driver.expression fc.driver.is_valid = True def posed(o, skip=None): dg = bpy.context.evaluated_depsgraph_get() ev = o.evaluated_get(dg) me = ev.to_mesh() M = ev.matrix_world vs = [M @ v.co for v in me.vertices] ps = [tuple(p.vertices) for p in me.polygons if skip is None or not skip(sum((vs[i] for i in p.vertices), Vector()) / len(p.vertices))] ev.to_mesh_clear() return vs, ps def tree(o, skip=None): vs, ps = posed(o, skip) used = sorted({i for p in ps for i in p}) return BVHTree.FromPolygons(vs, ps), [vs[i] for i in used] def probe(o, other_tree, skip=None): t, vs = tree(o, skip) hits = len(t.overlap(other_tree)) gap = min(other_tree.find_nearest(p)[3] for p in vs[::2]) return hits, round(gap, 4) def set_(k, v): root[k] = float(v); root.update_tag() for ob in root.children: ob.update_tag() bpy.context.view_layer.update() keep = {k: root.get(k, 0.0) for k in ("hatch_open_deg", "arms_out_deg", "rotor_turns")} for k in keep: set_(k, 0.0) hull_t, _ = tree(bpy.data.objects["SM_Mako_Hull"]) H = bpy.data.objects["SM_Mako_Hatch"] hinge = H.matrix_world.translation.copy() out = {"hatch": [], "arms": [], "rotor": []} for j in range(37): deg = 2.5 * j set_("hatch_open_deg", deg) # the lid's knuckles turn round the fixed knuckle on the same axis: faces within 3 cm of the axis are the hinge itself ax = H.get("hinge_axis", "X") # v04: the Mako's hatch hinges along Y h, g = probe(H, hull_t, skip=(lambda c: Vector((c.x - hinge.x, 0, c.z - hinge.z)).length < 0.03) if ax == "Y" else (lambda c: Vector((0, c.y - hinge.y, c.z - hinge.z)).length < 0.03)) out["hatch"].append([deg, h, g]) set_("hatch_open_deg", 0.0) AL, AR = bpy.data.objects["SM_Mako_Arm_L"], bpy.data.objects["SM_Mako_Arm_R"] for j in range(31): deg = 3.0 * j set_("arms_out_deg", deg) hl, gl = probe(AL, hull_t, skip=lambda c: (Vector((c.x - AL.matrix_world.translation.x, c.y - AL.matrix_world.translation.y, 0))).length < 0.065) hr, gr = probe(AR, hull_t, skip=lambda c: (Vector((c.x - AR.matrix_world.translation.x, c.y - AR.matrix_world.translation.y, 0))).length < 0.065) tr_, _ = tree(AR) hlr, glr = probe(AL, tr_) out["arms"].append([deg, hl, gl, hr, gr, hlr, glr]) set_("arms_out_deg", 0.0) R = bpy.data.objects["SM_Mako_Rotor"] for j in range(19): set_("rotor_turns", j * 4.0 / 360.0) h, g = probe(R, hull_t) out["rotor"].append([round(j * 4.0, 1), h, g]) for k, v in keep.items(): set_(k, v) HMAX = bpy.data.objects["Mako_Root"].id_properties_ui("hatch_open_deg").as_dict()["max"] summary = {"hatch_range_deg": [0, HMAX], "hatch_hits_in_range": sum(r[1] for r in out["hatch"] if r[0] <= HMAX), "hatch_first_touch_deg": next((r[0] for r in out["hatch"] if r[1]), None), "hatch_hits_shut": out["hatch"][0][1], "hatch_least_gap_m": min(r[2] for r in out["hatch"][1:] if r[0] <= HMAX), "arm_hits": sum(r[1] + r[3] + r[5] for r in out["arms"]), "arm_least_gap_m": min(min(r[2], r[4]) for r in out["arms"]), "arms_least_gap_to_each_other_m": min(r[6] for r in out["arms"]), "rotor_hits": sum(r[1] for r in out["rotor"]), "rotor_least_gap_m": min(r[2] for r in out["rotor"])} out["summary"] = summary json.dump(out, open(os.path.join(HERE, "checks", "motion.json"), "w"), indent=1) print("MOTION", json.dumps(summary)) result = summary