// ============================================================ // 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); }