// ============================================================
// uwb_anchor_mount.scad — Stem-Mounted UWB Anchor  Rev A
// Agent: sl-mechanical   2026-03-01
// Closes issues #57, #62
// ============================================================
// Clamp-on bracket for 2× MaUWB ESP32-S3 anchor modules on
// SaltyBot 25 mm OD vertical stem.
// Anchors spaced ANCHOR_SPACING = 250 mm apart.
//
// Features:
//   • Split D-collar with M4 clamping bolts + M4 set screw
//   • Anti-rotation flat tab that keys against a small pin
//     OR printed key tab that registers on the stem flat (if stem
//     has a ground flat) — see ANTI_ROT_MODE parameter
//   • Module bracket: faces outward, tilted 10° from vertical
//     so antenna clears stem and faces horizon
//   • USB cable channel (power from Orin via USB-A) on collar
//   • Tool-free capture: M4 thumbscrews (slot-head, hand-tighten)
//   • UWB antenna area: NO material within 10 mm of PCB top face
//
// Components per mount:
//   2× collar_half        print in PLA/PETG, flat-face-down
//   1× module_bracket     print in PLA/PETG, flat-face-down
//
// RENDER options:
//   "assembly"       single mount assembled (default)
//   "collar_front"   front collar half for slicing (×2 per mount × 2 mounts = 4)
//   "collar_rear"    rear collar half
//   "bracket"        module bracket (×2 mounts)
//   "pair"           both mounts on 350 mm stem section
// ============================================================

RENDER = "assembly";

// ── ⚠ Verify with calipers ───────────────────────────────────
MAWB_L      = 50.0;   // PCB length
MAWB_W      = 25.0;   // PCB width
MAWB_H      = 10.0;   // PCB + components
MAWB_HOLE_X = 43.0;   // M2 mounting hole X span
MAWB_HOLE_Y = 20.0;   // M2 mounting hole Y span
M2_D        =  2.2;   // M2 clearance

// ── Stem ─────────────────────────────────────────────────────
STEM_OD     = 25.0;
STEM_BORE   = 25.4;   // +0.4 clearance
WALL        =  2.0;   // wall thickness (used in thumbscrew recess)

// ── Collar ───────────────────────────────────────────────────
COL_OD      = 52.0;
COL_H       = 30.0;   // taller than sensor-head collar for rigidity
COL_BOLT_X  = 19.0;   // M4 bolt CL from stem axis
COL_BOLT_D  =  4.5;   // M4 clearance
THUMB_HEAD_D=  8.0;   // M4 thumbscrew head OD (slot for access)
COL_NUT_W   =  7.0;   // M4 hex nut A/F
COL_NUT_H   =  3.4;

// Anti-rotation flat tab: a 3 mm wall tab that protrudes radially
// and bears against the bracket arm, preventing axial rotation
// without needing a stem flat.
ANTI_ROT_T  =  3.0;   // tab thickness (radial)
ANTI_ROT_W  =  8.0;   // tab width (tangential)
ANTI_ROT_Z  =  4.0;   // distance from collar base

// USB cable channel: groove on collar outer surface, runs Z direction
// Cable routes from anchor module down to base
USB_CHAN_W  =  9.0;   // channel width (fits USB-A cable Ø6 mm)
USB_CHAN_D  =  5.0;   // channel depth

// ── Module bracket ───────────────────────────────────────────
ARM_L       = 20.0;   // arm length from collar OD to bracket face
ARM_W       = MAWB_W + 6.0; // bracket width (Y, includes side walls)
ARM_H       =  6.0;   // arm thickness (Z)
BRKT_TILT   = 10.0;   // tilt outward from vertical (antenna faces horizon)

BRKT_BACK_T =  3.0;   // bracket back wall (module sits against this)
BRKT_SIDE_T =  2.0;   // bracket side walls

M2_STNDFF   =  3.0;   // M2 standoff height
M2_STNDFF_OD=  4.5;

// USB port access notch in bracket side wall (8×5 mm)
USB_NOTCH_W = 10.0;
USB_NOTCH_H =  7.0;

// ── Spacing ───────────────────────────────────────────────────
ANCHOR_SPACING = 250.0;  // centre-to-centre Z separation

$fn = 64;
e   = 0.01;

// ─────────────────────────────────────────────────────────────
// collar_half(side)
//   split at Y=0 plane.  Bracket arm on front (+Y) half.
//   Print flat-face-down.
// ─────────────────────────────────────────────────────────────
module collar_half(side = "front") {
    y_front = (side == "front");

    difference() {
        union() {
            // D-shaped body
            intersection() {
                cylinder(d=COL_OD, h=COL_H);
                translate([-COL_OD/2, y_front ? 0 : -COL_OD/2, 0])
                    cube([COL_OD, COL_OD/2, COL_H]);
            }

            // Anti-rotation tab (front half only, at +X side)
            if (y_front) {
                translate([COL_OD/2, -ANTI_ROT_W/2, ANTI_ROT_Z])
                    cube([ANTI_ROT_T, ANTI_ROT_W,
                          COL_H - ANTI_ROT_Z - 4]);
            }

            // Bracket arm attachment boss (front half only, top centre)
            if (y_front) {
                translate([-ARM_W/2, COL_OD/2, COL_H * 0.3])
                    cube([ARM_W, ARM_L, COL_H * 0.4]);
            }
        }

        // ── Stem bore ─────────────────────────────────────────
        translate([0,0,-e])
            cylinder(d=STEM_BORE, h=COL_H + 2*e);

        // ── M4 clamping bolt holes (Y direction) ──────────────
        for (bx=[-COL_BOLT_X, COL_BOLT_X]) {
            translate([bx, y_front ? COL_OD/2 : 0, COL_H/2])
                rotate([90,0,0])
                    cylinder(d=COL_BOLT_D, h=COL_OD/2 + e);
            // Thumbscrew head recess on outer face (front only — access side)
            if (y_front) {
                translate([bx, COL_OD/2 - WALL, COL_H/2])
                    rotate([90,0,0])
                        cylinder(d=THUMB_HEAD_D, h=8 + e);
            }
        }

        // ── M4 hex nut pockets (rear half) ────────────────────
        if (!y_front) {
            for (bx=[-COL_BOLT_X, COL_BOLT_X]) {
                translate([bx, -(COL_OD/4 + e), COL_H/2])
                    rotate([90,0,0])
                        cylinder(d=COL_NUT_W/cos(30), h=COL_NUT_H + e,
                                 $fn=6);
            }
        }

        // ── Set screw (height lock, front half) ───────────────
        if (y_front) {
            translate([0, COL_OD/2, COL_H * 0.8])
                rotate([90,0,0])
                    cylinder(d=COL_BOLT_D,
                             h=COL_OD/2 - STEM_BORE/2 + e);
        }

        // ── USB cable routing channel (rear half, −X side) ────
        if (!y_front) {
            translate([-COL_OD/2, -USB_CHAN_W/2, -e])
                cube([USB_CHAN_D, USB_CHAN_W, COL_H + 2*e]);
        }

        // ── M4 hole through arm boss (Z direction, for bracket bolt) ─
        if (y_front) {
            for (dx=[-ARM_W/4, ARM_W/4])
                translate([dx, COL_OD/2 + ARM_L/2, COL_H * 0.35])
                    cylinder(d=COL_BOLT_D, h=COL_H * 0.35 + e);
        }
    }
}

// ─────────────────────────────────────────────────────────────
// module_bracket()
//   Bolts to collar arm boss. Holds MaUWB PCB facing outward.
//   Tilted BRKT_TILT° from vertical — antenna clears stem.
//   Print flat-face-down (back wall on bed).
// ─────────────────────────────────────────────────────────────
module module_bracket() {
    bk = BRKT_BACK_T;
    sd = BRKT_SIDE_T;

    difference() {
        union() {
            // ── Back wall (mounts to collar arm boss) ─────────
            cube([ARM_W, bk, MAWB_H + M2_STNDFF + 6]);

            // ── Side walls ────────────────────────────────────
            for (sx=[0, ARM_W - sd])
                translate([sx, bk, 0])
                    cube([sd, MAWB_L + 2, MAWB_H + M2_STNDFF + 6]);

            // ── M2 standoff posts (PCB mounts to these) ───────
            for (hx=[0, MAWB_HOLE_X], hy=[0, MAWB_HOLE_Y])
                translate([(ARM_W - MAWB_HOLE_X)/2 + hx,
                           bk + (MAWB_L - MAWB_HOLE_Y)/2 + hy,
                           0])
                    cylinder(d=M2_STNDFF_OD, h=M2_STNDFF);
        }

        // ── M2 bores through standoffs ────────────────────────
        for (hx=[0, MAWB_HOLE_X], hy=[0, MAWB_HOLE_Y])
            translate([(ARM_W - MAWB_HOLE_X)/2 + hx,
                       bk + (MAWB_L - MAWB_HOLE_Y)/2 + hy,
                       -e])
                cylinder(d=M2_D, h=M2_STNDFF + e);

        // ── Antenna clearance cutout in back wall ─────────────
        // Open slot near top of back wall so antenna is unobstructed
        translate([sd, -e, M2_STNDFF + 2])
            cube([ARM_W - 2*sd, bk + 2*e, MAWB_H]);

        // ── USB port access notch on one side wall ────────────
        translate([-e, bk + 2, M2_STNDFF - 1])
            cube([sd + 2*e, USB_NOTCH_W, USB_NOTCH_H]);

        // ── Mounting holes to collar arm boss (×2) ────────────
        for (dx=[-ARM_W/4, ARM_W/4])
            translate([ARM_W/2 + dx, bk + ARM_L/2, -e])
                cylinder(d=COL_BOLT_D, h=6 + e);
    }
}

// ─────────────────────────────────────────────────────────────
// single_anchor_assembly()
// ─────────────────────────────────────────────────────────────
module single_anchor_assembly(show_phantom=false) {
    // Collar
    color("SteelBlue", 0.9)   collar_half("front");
    color("CornflowerBlue", 0.9) mirror([0,1,0]) collar_half("rear");

    // Bracket tilted BRKT_TILT° outward from top of arm boss
    color("LightSteelBlue", 0.85)
        translate([0, COL_OD/2 + ARM_L, COL_H * 0.3])
            rotate([BRKT_TILT, 0, 0])
                translate([-ARM_W/2, 0, 0])
                    module_bracket();

    // Phantom UWB PCB
    if (show_phantom)
        color("ForestGreen", 0.4)
            translate([-MAWB_L/2,
                       COL_OD/2 + ARM_L + BRKT_BACK_T,
                       COL_H * 0.3 + M2_STNDFF])
                cube([MAWB_L, MAWB_W, MAWB_H]);
}

// ─────────────────────────────────────────────────────────────
// Render selector
// ─────────────────────────────────────────────────────────────
if (RENDER == "assembly") {
    single_anchor_assembly(show_phantom=true);

} else if (RENDER == "collar_front") {
    collar_half("front");

} else if (RENDER == "collar_rear") {
    collar_half("rear");

} else if (RENDER == "bracket") {
    module_bracket();

} else if (RENDER == "pair") {
    // Both anchors at 250 mm spacing on a stem stub
    color("Silver", 0.2)
        translate([0, 0, -50])
            cylinder(d=STEM_OD, h=ANCHOR_SPACING + COL_H + 100);

    // Lower anchor (Z = 0)
    single_anchor_assembly(show_phantom=true);

    // Upper anchor (Z = ANCHOR_SPACING)
    translate([0, 0, ANCHOR_SPACING])
        single_anchor_assembly(show_phantom=true);
}

