"""Checks the sliding door through its whole travel (the user's rule: moving parts are checked in every position, not only shut and open), the handle through its turn, and the console and the camera through their swings: * open 0..1 in steps: each leaf and the handle (which rides on the left leaf) against the frame and the wall (BVH triangle overlap = a clash), the leaves against each other, the handle against both leaves; smallest distances at every fifth step (vertex sampling); * handle_turn -1..1: the handle disc against the frame and both leaves (it sits in a well cut through both); * arm_swing -1..1: the console against the frame and the wall; cam_yaw -1..1: the camera against the frame. check(steps=100, set_open=..., set_prop=...) -> dict (written to build/check_report.json by sd_run.py) """ import bpy, math from mathutils.bvhtree import BVHTree STATIC = ["SM_PDSliding_Frame", "SM_PDSliding_Wall"] LEAVES = ["SM_PDSliding_LeafL", "SM_PDSliding_LeafR"] HANDLE = "SM_PDSliding_Handle" 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 _nearest(pts, tree, lim=0.25): best = 9.0 for p in pts: r = tree.find_nearest(p, lim) if r[0] is not None and r[3] < best: best = r[3] return best def _sweep(prop, values, movers, statics, set_prop, near_every=5, extra_pairs=(), test_static=None): """movers: objects rebuilt every step; statics: name -> tree. extra_pairs: (mover, mover) pairs tested too. test_static: the movers tested against the statics (default: all movers).""" clashes, near = {}, {} test_static = movers if test_static is None else test_static for i, s in enumerate(values): set_prop(prop, s) dg = bpy.context.evaluated_depsgraph_get() trees = {m: _bvh(bpy.data.objects[m], dg) for m in movers} for m in test_static: for n, sb in statics.items(): ov = trees[m].overlap(sb) if ov: c = clashes.setdefault("%s x %s" % (m, n), {"first": s, "last": s, "max_pairs": 0}) c["last"] = s c["max_pairs"] = max(c["max_pairs"], len(ov)) if i % near_every == 0: pts = _verts_world(bpy.data.objects[m], dg, 2) for n, sb in statics.items(): d = _nearest(pts, sb) k = "%s x %s" % (m, n) if k not in near or d < near[k][0]: near[k] = (round(d, 4), s) for a, b in extra_pairs: ov = trees[a].overlap(trees[b]) if ov: c = clashes.setdefault("%s x %s" % (a, b), {"first": s, "last": s, "max_pairs": 0}) c["last"] = s c["max_pairs"] = max(c["max_pairs"], len(ov)) if i % near_every == 0: d = _nearest(_verts_world(bpy.data.objects[a], dg, 1), trees[b]) k = "%s x %s" % (a, b) if k not in near or d < near[k][0]: near[k] = (round(d, 4), s) set_prop(prop, 0.0) return {"clashes": clashes, "nearest_m": {k: v for k, v in near.items() if v[0] < 0.25}} def check(steps=100, set_open=None, set_prop=None): dg = bpy.context.evaluated_depsgraph_get() statics = {n: _bvh(bpy.data.objects[n], dg) for n in STATIC} frame_only = {"SM_PDSliding_Frame": statics["SM_PDSliding_Frame"]} out = {} pairs = [(LEAVES[0], LEAVES[1]), (HANDLE, LEAVES[1]), (HANDLE, LEAVES[0])] out["leaves_open_0_1"] = _sweep("open", [i / steps for i in range(steps + 1)], LEAVES + [HANDLE], statics, set_prop, 5, pairs) n = 40 out["handle_turn"] = _sweep("handle_turn", [-1 + 2 * i / n for i in range(n + 1)], LEAVES + [HANDLE], frame_only, set_prop, 4, [(HANDLE, LEAVES[0]), (HANDLE, LEAVES[1])], test_static=[HANDLE]) out["console_swing"] = _sweep("arm_swing", [-1 + 2 * i / n for i in range(n + 1)], ["SM_PDSliding_Monitor"], statics, set_prop, 4) out["camera_yaw"] = _sweep("cam_yaw", [-1 + 2 * i / n for i in range(n + 1)], ["SM_PDSliding_Camera"], frame_only, set_prop, 4) # the leaves' lowest point and where they and the handle stand when open (inside the plate's slot?) set_prop("open", 1.0) dg = bpy.context.evaluated_depsgraph_get() ends = {} low = 9.0 for m in LEAVES + [HANDLE]: pts = _verts_world(bpy.data.objects[m], dg) low = min(low, min(p.z for p in pts)) ends[m] = {"x_min": round(min(p.x for p in pts), 4), "x_max": round(max(p.x for p in pts), 4), "y_min": round(min(p.y for p in pts), 4), "y_max": round(max(p.y for p in pts), 4)} set_prop("open", 0.0) out["leaf_lowest_z"] = round(low, 4) out["open_extent"] = ends return out