"""Two-piece SCREWLESS mount for the Arducam B0332, designed against the real STEP (arducam_b0332_mm.stl).
Coords = the STEP mesh frame: board bottom z=0, top z=1.57, lens +z, holes at (+-14.26, +-14.26) D2.06.
- camera_pin_base.stl:  plate + 4 standoff bosses (5mm, clears the 4.46mm bottom parts) + 4 pins that pass
  through the board holes and stand proud for the cover; + perpendicular foot (2x M3) as the robot interface.
- camera_pin_cover.stl: bezel that presses onto the pin tips (blind friction sockets), contacts the board
  only on 4 corner pads, 17mm square cutout drops over the 15.5mm lens holder. No screws for the camera.
PIN_CLEAR / SOCKET_CLEAR are the print-fit knobs."""
import numpy as np, trimesh
HOLES = [(sx*14.26, sy*14.26) for sx in (-1,1) for sy in (-1,1)]
HOLE_D = 2.06
PIN_D = HOLE_D - 0.26        # pin passes board hole with slip fit (print swell ~+0.15)
SOCKET_D = PIN_D + 0.25      # judgment fit: light friction across typical FDM swell, never pin-snapping
BOARD_T = 1.57
BOSS_H = 5.0                 # bottom parts reach -4.46
PIN_PROUD = 2.5
PLATE = 46.0
def box(x0,x1,y0,y1,z0,z1):
    b=trimesh.creation.box(extents=[x1-x0,y1-y0,z1-z0]); b.apply_translation([(x0+x1)/2,(y0+y1)/2,(z0+z1)/2]); return b
def cyl(x,y,r,z0,z1,sec=48):
    c=trimesh.creation.cylinder(radius=r,height=z1-z0,sections=sec); c.apply_translation([x,y,(z0+z1)/2]); return c
def U(p): return trimesh.boolean.union(p,engine="manifold")
def D(a,b): return trimesh.boolean.difference([a,b],engine="manifold")
cam = trimesh.load("arducam_b0332_mm.stl", force="mesh")
bot = cam.vertices[cam.vertices[:,2] < -0.05]
# --- base ---
plate = box(-PLATE/2,PLATE/2,-PLATE/2,PLATE/2,-BOSS_H-2.0,-BOSS_H)   # 2mm plate (print saving)
pieces=[plate]
for hx,hy in HOLES:
    boss = cyl(hx,hy,2.6,-BOSS_H,0)
    d = np.linalg.norm(bot[:,:2]-[hx,hy],axis=1)
    clash = bot[(d < 3.0)]
    if len(clash):                                        # notch the boss away from the offending part
        cx,cy = clash[:,:2].mean(0)
        vx,vy = cx-hx, cy-hy; n=np.hypot(vx,vy); vx,vy=vx/n,vy/n
        wedge = box(-3.2,3.2,-3.2,3.2,-BOSS_H-1,1)
        wedge.apply_translation([hx+vx*4.2, hy+vy*4.2, 0])
        boss = D(boss, wedge)
        print(f"boss @({hx:.1f},{hy:.1f}) notched away from part at ({cx:.1f},{cy:.1f})")
    pieces.append(boss)
    pieces.append(cyl(hx,hy,PIN_D/2,-1.0,BOARD_T+PIN_PROUD,sec=40))   # pin (rooted 1mm into boss)
    tip=trimesh.creation.icosphere(radius=PIN_D/2,subdivisions=3); tip.apply_translation([hx,hy,BOARD_T+PIN_PROUD]); pieces.append(tip)
foot = box(-PLATE/2,PLATE/2,PLATE/2-3,PLATE/2,-BOSS_H-2-20,-BOSS_H-2)
base = U(pieces+[foot])
# USB window: the bottom JST/USB socket (x -10.8..-3.2, y -15.6..-11.8, 4.5 deep) needs the plug to pass
# through the plate from below. Window is boss-limited on the west (pin boss edge at x=-11.66); generous
# everywhere else, with a flared lower half for finger/cable room.
base = D(base, box(-12.0, 2.0, -20.0, -7.5, -BOSS_H-2.0-1, -BOSS_H+1))      # through-window
base = D(base, box(-14.5, 4.5, -22.5, -5.0, -BOSS_H-2.0-1, -BOSS_H-1.5))    # entry flare (lower plate half)
for fx in (-13,13):
    fh=trimesh.creation.cylinder(radius=1.7,height=5,sections=32)
    fh.apply_transform(trimesh.transformations.rotation_matrix(np.pi/2,[1,0,0]))
    fh.apply_translation([fx,PLATE/2-1.5,-BOSS_H-2-13]); base=D(base,fh)
base.export("camera_pin_base.stl")
# --- cover ---
PAD_H=1.8; CT=2.5
cplate = box(-PLATE/2,PLATE/2,-PLATE/2,PLATE/2,BOARD_T+PAD_H,BOARD_T+PAD_H+CT)
cplate = D(cplate, box(-11.8,11.8,-8.5,8.5,BOARD_T-1,BOARD_T+PAD_H+CT+1))        # clears 15.5sq holder + x-ears to |x|=11
pads=[cyl(hx,hy,2.6,BOARD_T,BOARD_T+PAD_H+0.1) for hx,hy in HOLES]
cover=U([cplate]+pads)
for hx,hy in HOLES:
    cover=D(cover,cyl(hx,hy,SOCKET_D/2,BOARD_T-0.1,BOARD_T+PIN_PROUD+1.1,sec=40)) # blind sockets (room for the pin dome)
cover.export("camera_pin_cover.stl")
for n,mm in [("base",base),("cover",cover)]:
    b=len(mm.split(only_watertight=False))
    print(f"camera_pin_{n}: wt={mm.is_watertight} bodies={b} vol={mm.volume/1000:.1f}cm3 bbox={[round(v,1) for v in mm.extents]}mm")
