"""Join the gripper into its two printable pieces (servo excluded):
- gripper_print_base.stl  = holder + connector1 + servo_to_finger plate + fixed_conn + finger_adj (static jaw)
- gripper_print_thumb.stl = yoke + yoke_conn + fin-ray finger (moving jaw, bolts to the servo output)"""
import trimesh, trimesh.repair
def load(n):
    m = trimesh.load(f"gnode_{n}.stl", force="mesh")
    if not m.is_volume:                       # repair tessellation cracks (e.g. build123d yoke export)
        m.merge_vertices(merge_tex=True, merge_norm=True)
        m.update_faces(m.nondegenerate_faces()); m.update_faces(m.unique_faces())
        trimesh.repair.fill_holes(m); m.fix_normals()
        print(f"repaired {n}: volume_ok={m.is_volume}")
    return m
def U(names):
    m = trimesh.boolean.union([load(n) for n in names], engine="manifold")
    bodies = m.split(only_watertight=False)
    if len(bodies) > 1:                        # strip degenerate boolean slivers, keep the real part
        keep = [b for b in bodies if b.volume > 100.0]
        m = keep[0] if len(keep) == 1 else trimesh.util.concatenate(keep)
    return m
# UMI print set (Cameron 2026-07-07): handle + marker cube fused into the base, ring into the thumb.
# The 80mm aruco cube prints SHELLED (3mm walls, open toward the handle) instead of 512cm3 solid.
cube = load("aruco_cube")
lo, hi = cube.bounds
inner = trimesh.creation.box(extents=[hi[0]-lo[0]-6, hi[1]-lo[1]+2, hi[2]-lo[2]-6])
inner.apply_translation([(lo[0]+hi[0])/2, (lo[1]+hi[1])/2 - 4, (lo[2]+hi[2])/2])   # open through the bottom
cube = trimesh.boolean.difference([cube, inner], engine="manifold")
# ArUco sheet recesses (70.4 sq, 0.6 deep) on TOP (+y) and BACK (+x) faces + a corner orientation notch
# (mirror-flipped installs break PnP — the notch keys the sheet). IDs: 240 top, 241 back.
cx, cz = (lo[0]+hi[0])/2, (lo[2]+hi[2])/2
cy = (lo[1]+hi[1])/2
def rec(center, size, normal_axis, depth, face_pos):
    e = [size, size, size]; e[normal_axis] = depth * 2
    r = trimesh.creation.box(extents=e)
    p = list(center); p[normal_axis] = face_pos
    r.apply_translation(p); return r
top = rec([cx, 0, cz], 80.4, 1, 0.6, hi[1])
tn = rec([cx - 33.0, 0, cz - 33.0], 8.0, 1, 1.2, hi[1])            # notch, deeper, one corner
back = rec([0, cy, cz], 80.4, 0, 0.6, hi[0])
bn = rec([0, cy - 33.0, cz - 33.0], 8.0, 0, 1.2, hi[0])
for tool in (top, tn, back, bn):
    cube = trimesh.boolean.difference([cube, tool], engine="manifold")
cube.export("gnode_aruco_cube_shelled.stl")
base = U(["holder", "connector1", "servo_to_finger", "fixed_conn", "finger_adj", "cam_conn", "camera_mount", "handle", "aruco_cube_shelled"])
base.export("gripper_print_base.stl")
thumb = U(["yoke", "yoke_conn", "finger", "finger_ring"])
thumb.export("gripper_print_thumb.stl")
import shutil
shutil.copy("camera_pin_cover.stl", "gripper_print_cover.stl")   # front plate, authored flat = print orientation
cover = trimesh.load("gripper_print_cover.stl", force="mesh")
for n, m in [("base", base), ("thumb", thumb), ("cover", cover)]:
    bodies = len(m.split(only_watertight=False))
    print(f"gripper_print_{n}: wt={m.is_watertight} bodies={bodies} vol={m.volume/1000:.1f}cm3 bbox={[round(v) for v in m.extents]}mm{'  <-- WARNING disjoint!' if bodies>1 else ''}")
