"""Checks the sliding door through its whole travel (the user's rule: moving parts are checked in every position, not only shut and open), and the console and the camera through their swings: * open 0..1 in steps: each leaf against the frame, the wall and the other leaf (BVH triangle overlap = a clash), and the leaves' smallest distance to the frame (vertex sampling) at every fifth step; * 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"] 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=()): clashes, near = {}, {} 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 movers: 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, 2), 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} out = {} out["leaves_open_0_1"] = _sweep("open", [i / steps for i in range(steps + 1)], ["SM_PDSliding_LeafL", "SM_PDSliding_LeafR"], statics, set_prop, 5, [("SM_PDSliding_LeafL", "SM_PDSliding_LeafR")]) n = 40 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"], {"SM_PDSliding_Frame": statics["SM_PDSliding_Frame"]}, set_prop, 4) # the leaves' lowest point and where their outer ends stand when open (inside the plate?) set_prop("open", 1.0) dg = bpy.context.evaluated_depsgraph_get() ends = {} low = 9.0 for m in ("SM_PDSliding_LeafL", "SM_PDSliding_LeafR"): 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["leaves_open_extent"] = ends out["plate_extent"] = {"x": 1.52, "slot_y": [-0.055, 0.05], "slot_x": 1.49} return out