"""Fuse a seat WEDGE into cam_conn filling the gap between the flat bracket top and the (already-tilted)
camera_mount foot. No rotation — the tilt lives in the source geometry now. Wedge = hull of the mount
bottom-face verts and their projection onto the bracket top plane. Run AFTER beautify/urdf/polish."""
import numpy as np, trimesh
s = trimesh.load("gripper.glb")
def geom(node): return s.geometry[s.graph[node][1]]
mnt = geom("camera_mount"); V = mnt.vertices * 100.0
down = mnt.face_normals[:, 1] < -0.5
bot_faces = mnt.faces[down]
bv = V[np.unique(bot_faces)]
bv = bv[bv[:, 1] < np.percentile(bv[:, 1], 30)]           # the true foot face band
cc = geom("cam_conn"); Vc = cc.vertices * 100.0
top_y = float(Vc[:, 1].max())
proj = bv.copy(); proj[:, 1] = top_y - 4.0                # sink 4mm into the bracket top (weld)
wedge = trimesh.PointCloud(np.vstack([bv, proj])).convex_hull
merged = trimesh.boolean.union([trimesh.Trimesh(Vc, cc.faces), wedge], engine="manifold")
# camera relief: the wedge must not clip the camera assembly either
cpts = np.vstack([geom(n).vertices * 100.0 for n in ("camera_body", "camera_cover")])
bm = geom("camera_body")
ch = trimesh.PointCloud(cpts).convex_hull
n, a = bm.face_normals, bm.area_faces
key = np.round(n, 2); uniq, inv = np.unique(key, axis=0, return_inverse=True)
wgt = np.bincount(inv, weights=a)
axis = uniq[np.argmax(wgt)]; axis = axis / np.linalg.norm(axis)
tv = (bm.vertices * 100.0) @ axis
fd = np.isclose(np.abs(bm.face_normals @ axis), 1.0, atol=0.05)
t_board = float(np.median((bm.triangles_center[fd] * 100.0) @ axis))
optical = axis if (tv.max() - t_board) > (t_board - tv.min()) else -axis
offs = [d * s for d in np.eye(3) for s in (2.5, -2.5)]
base = np.vstack([ch.vertices + o for o in offs])                  # guaranteed >=2.5mm dilation
relief = trimesh.PointCloud(np.vstack([base, base + optical * 120.0])).convex_hull
merged = trimesh.boolean.difference([merged, relief], engine="manifold")
cc.vertices = merged.vertices / 100.0; cc.faces = merged.faces
s.export("gripper.glb")
g = trimesh.Trimesh(merged.vertices, merged.faces); g.export("gnode_cam_conn.stl")
print(f"seat wedge fused: cam_conn wt={merged.is_watertight} bodies={len(merged.split(only_watertight=False))} vol={merged.volume/1000:.1f}cm3")
