"""Checks the pressure door through its whole travel (the user's rule: moving parts are checked in every position, not only shut and open). For `open` from 0 to 1 in steps: * every moving part against every static mesh: triangles that intersect (BVH overlap) = a clash * the leaf's and the bolts' smallest distance to the static meshes (vertex sampling) * the leaf's lowest point above the floor Contacts a part is meant to have are not clashes: the wheel's hub sits in the leaf's boss, the bolts run through the leaf's rim (inside their bezels). Those pairs are not tested. check(steps=100) -> dict """ import bpy, math from mathutils.bvhtree import BVHTree STATIC = ["SM_PressureDoor_Frame", "SM_PressureDoor_Housing", "SM_PressureDoor_Bulkhead", "SM_PressureDoor_FloorPlate"] def _bvh(ob, dg): """in world space (BVHTree.FromObject would give object space)""" ev = ob.evaluated_get(dg) me = ev.to_mesh() M = ob.matrix_world vs = [M @ v.co for v in me.vertices] ps = [tuple(p.vertices) for p in me.polygons] ev.to_mesh_clear() return BVHTree.FromPolygons(vs, ps) def _verts_world(ob, dg, stride=1): ev = ob.evaluated_get(dg) me = ev.to_mesh() M = ob.matrix_world vs = me.vertices pts = [M @ vs[i].co for i in range(0, len(vs), stride)] ev.to_mesh_clear() return pts def check(steps=100, near_every=5, set_open=None): dg = bpy.context.evaluated_depsgraph_get() statics = {n: _bvh(bpy.data.objects[n], dg) for n in STATIC} movers = ["SM_PressureDoor_Leaf", "SM_PressureDoor_Wheel"] + sorted(o.name for o in bpy.data.objects if o.name.startswith("SM_PressureDoor_Bolt_")) clashes = {} near = {} low = 9.0 for i in range(steps + 1): s = i / steps set_open(s) dg = bpy.context.evaluated_depsgraph_get() for m in movers: ob = bpy.data.objects[m] b = _bvh(ob, dg) for n, sb in statics.items(): ov = b.overlap(sb) if ov: key = (m, n) c = clashes.setdefault(key, {"first": s, "last": s, "max_pairs": 0}) c["last"] = s c["max_pairs"] = max(c["max_pairs"], len(ov)) if i % near_every == 0 and (m == "SM_PressureDoor_Leaf" or m.startswith("SM_PressureDoor_Bolt_01")): pts = _verts_world(ob, dg, 2 if m == "SM_PressureDoor_Leaf" else 1) if m == "SM_PressureDoor_Leaf": low = min(low, min(p.z for p in pts)) for n, sb in statics.items(): best = 9.0 for p in pts: r = sb.find_nearest(p, 0.25) if r[0] is not None and r[3] < best: best = r[3] k = (m, n) if k not in near or best < near[k][0]: near[k] = (round(best, 4), s) set_open(0.0) return {"clashes": {"%s x %s" % k: v for k, v in clashes.items()}, "nearest_m_at_open": {"%s x %s" % k: v for k, v in near.items() if v[0] < 0.25}, "leaf_lowest_z": round(low, 4)}