"""Door robot arm: the centre of each moon pool door's inner face (the fixtures' new attach point, 2026-10-18). The user: "attach the main plate to the doors center". The centre is taken as the middle of the door's length (its local x) and the middle of its curved section by arc length, on the inner face (the face toward the pocket and the arm). The door's cross-section at that x is cut from the mesh; its inner curve is the run of the section whose faces look up / inboard (the normal of the old attach point, design.json, picks the side). Writes build/door_centre.json: per door the attach point and the face normal in the door's local axes (as design.json's attach_local / normal_local), the curve's arc length, and the sag of the curve under a flat plate of a given size. blender -b --factory-startup --python build/dra_door_centre.py -- """ import os import sys import json import math import bmesh import bpy from mathutils import Vector HERE = os.path.dirname(os.path.abspath(__file__)) ARGS = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] path = ARGS[0] if ARGS else os.path.join(os.path.dirname(HERE), "DoorRobotArm.blend") bpy.ops.wm.open_mainfile(filepath=path) D = json.load(open(os.path.join(os.path.dirname(HERE), "design.json"))) def section(me, x): """the cross-section of the mesh at local x: list of (p0, p1, face normal) segments in (y, z)""" bm = bmesh.new() bm.from_mesh(me) segs = [] for f in bm.faces: vs = [v.co for v in f.verts] pts = [] n = len(vs) for i in range(n): a, b = vs[i], vs[(i + 1) % n] if (a.x - x) * (b.x - x) < 0: t = (x - a.x) / (b.x - a.x) p = a.lerp(b, t) pts.append(Vector((p.y, p.z))) if len(pts) == 2: segs.append((pts[0], pts[1], Vector((f.normal.y, f.normal.z)))) bm.free() return segs def chain_curve(segs, pick): """the inner curve: segments whose face normal agrees with 'pick', joined end to end into one polyline""" S = [(a, b) for a, b, n in segs if n.length > 0.5 and n.normalized().dot(pick) > 0.5] if not S: return [] S = [list(s) for s in S] line = S.pop(0) changed = True while changed and S: changed = False for i, (a, b) in enumerate(S): if (a - line[-1]).length < 1e-4: line.append(b) elif (b - line[-1]).length < 1e-4: line.append(a) elif (b - line[0]).length < 1e-4: line.insert(0, a) elif (a - line[0]).length < 1e-4: line.insert(0, b) else: continue S.pop(i) changed = True break return line out = {} for key in ("AFT", "FORE"): dd = D["doors"][key] ob = bpy.data.objects[dd["door"]] me = ob.data xs = [v.co.x for v in me.vertices] x0, x1 = min(xs), max(xs) xm = (x0 + x1) / 2 pick = Vector((dd["normal_local"][1], dd["normal_local"][2])).normalized() curve = chain_curve(section(me, xm), pick) L = [0.0] for i in range(1, len(curve)): L.append(L[-1] + (curve[i] - curve[i - 1]).length) tot = L[-1] half = tot / 2 for i in range(1, len(curve)): if L[i] >= half: f = (half - L[i - 1]) / max(1e-9, L[i] - L[i - 1]) mid = curve[i - 1].lerp(curve[i], f) tang = (curve[i] - curve[i - 1]).normalized() break nrm = Vector((-tang.y, tang.x)) if nrm.dot(pick) < 0: nrm = -nrm # the sag of the inner face under a flat plate (square, side s) centred on the attach point: the largest distance # of the face from the plate's plane, sampled over the plate (rays along -normal) from mathutils.bvhtree import BVHTree bvh = BVHTree.FromPolygons([v.co for v in me.vertices], [tuple(p.vertices) for p in me.polygons]) att = Vector((xm, mid.x, mid.y)) n3 = Vector((0.0, nrm.x, nrm.y)) u3 = Vector((1.0, 0.0, 0.0)) v3 = n3.cross(u3).normalized() sag = {} for s in (0.6, 0.8, 1.0, 1.1, 1.2, 1.3): lo, hi = 9.0, -9.0 for i in range(13): for j in range(13): p = att + u3 * (-s / 2 + s * i / 12) + v3 * (-s / 2 + s * j / 12) hit = bvh.ray_cast(p + n3 * 0.5, -n3, 2.0) if hit[0] is None: continue h = (hit[0] - att).dot(n3) lo, hi = min(lo, h), max(hi, h) sag[str(s)] = [round(lo, 4), round(hi, 4)] out[key] = dict(door=dd["door"], length_x=[round(x0, 4), round(x1, 4)], x_mid=round(xm, 4), arc_length=round(tot, 4), curve_ends=[[round(c, 4) for c in curve[0]], [round(c, 4) for c in curve[-1]]], attach_local=[round(xm, 5), round(mid.x, 5), round(mid.y, 5)], normal_local=[0.0, round(nrm.x, 9), round(nrm.y, 9)], old_attach_local=dd["attach_local"], closed_location=dd["closed_location"], sag_under_plate_m=sag, curve_points=len(curve), inner_curve_yz=[[round(c.x, 5), round(c.y, 5)] for c in curve]) print("[dc]", key, json.dumps(out[key]), flush=True) with open(os.path.join(HERE, "door_centre.json"), "w") as fh: json.dump(out, fh, indent=1) print("[dc] written", flush=True)