// ============================================================
// sensor_head.scad — Sensor Head Platform  Rev A  2026-02-28
// Agent: sl-mechanical
// ============================================================
// Clamps to 25 mm OD vertical stem (top of mast).
// Platform hosts:
//   • RPLIDAR A1M8       — centred, topmost
//   • RealSense D435i    — front arm, 10° down tilt
//   • 4× IMX219 cameras  — radial arms, 90° apart, 10° down
//
// RENDER options:
//   "assembly"       3-D exploded-ish view (default)
//   "collar_front"   front collar half for slicing (print 2)
//   "collar_rear"    rear collar half for slicing
//   "platform_2d"    flat projection → DXF
// ============================================================

RENDER = "assembly";

// ── Stem ─────────────────────────────────────────────────────
STEM_OD   = 25.0;            // actual stem OD (mm)
STEM_BORE = 25.4;            // collar bore (+ 0.4 mm clearance for FDM)

// ── Collar ───────────────────────────────────────────────────
COL_OD     = 52.0;           // outer diameter
COL_H      = 36.0;           // height
COL_BOLT_X = 19.0;           // M4 clamping bolt CL from stem axis
COL_BOLT_D = 4.5;            // M4 clearance hole
COL_NUT_W  = 7.0;            // M4 hex nut A/F
COL_NUT_H  = 3.4;            // M4 hex nut thickness

// ── Platform ─────────────────────────────────────────────────
PLAT_OD    = 120.0;          // octagonal platform, across-flats ≈ OD
PLAT_H     =   8.0;          // plate thickness
PLAT_Z     = COL_H;          // platform bottom sits atop collar

// ── RPLIDAR bolt circle ───────────────────────────────────────
RPL_BC      = 58.0;          // M3 hole bolt circle diameter
RPL_HOLE_D  =  3.3;          // M3 clearance
RPL_RECESS_D = 72.0;         // alignment lip diameter (body + 2 mm clearance)
RPL_RECESS_H =  1.5;         // recess depth

// ── Sensor arm attachment holes ───────────────────────────────
// RealSense arm: 2× M4 holes at X = +ARM_R, Y = ±ARM_DY_RS
ARM_R       = 50.0;          // radial distance from stem CL
ARM_DY_RS   = 18.0;          // half-spacing for RealSense pair
// IMX219 arms: 4 × 2 M4 holes at each 90° cardinal, tangential offset ±ARM_DY_CAM
ARM_DY_CAM  =  9.0;          // half-spacing for IMX219 pair

ARM_HOLE_D  =  4.5;          // M4 clearance

$fn = 64;
e   = 0.01;

// ─────────────────────────────────────────────────────────────
// collar_half(side)
//   side = "front" (+Y) | "rear" (-Y)
//   Print flat-face-down (mating face on build plate).
// ─────────────────────────────────────────────────────────────
module collar_half(side = "front") {
    difference() {
        // ── Body: D-shaped half-cylinder ─────────────────────
        intersection() {
            cylinder(d = COL_OD, h = COL_H);
            translate([-COL_OD/2, (side == "front") ? 0 : -COL_OD/2, 0])
                cube([COL_OD, COL_OD/2, COL_H]);
        }

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

        // ── M4 clamping bolt clearance holes (Y direction) ───
        for (bx = [-COL_BOLT_X, COL_BOLT_X]) {
            translate([bx, (side == "front") ? COL_OD/2 : 0, COL_H/2])
                rotate([90, 0, 0])
                    cylinder(d = COL_BOLT_D, h = COL_OD/2 + e);
        }

        // ── M4 hex nut pockets (rear half only) ──────────────
        if (side == "rear") {
            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);
            }
        }

        // ── M4 set screw — height / rotation lock (front half) ─
        if (side == "front") {
            translate([0, COL_OD/2, COL_H * 0.72])
                rotate([90, 0, 0])
                    cylinder(d = COL_BOLT_D, h = COL_OD/2 - STEM_BORE/2 + e);
        }
    }
}

// ─────────────────────────────────────────────────────────────
// platform()
// ─────────────────────────────────────────────────────────────
module platform() {
    difference() {
        // ── Octagonal disc ───────────────────────────────────
        cylinder(d = PLAT_OD, h = PLAT_H, $fn = 8);

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

        // ── RPLIDAR alignment recess (top face) ──────────────
        translate([0, 0, PLAT_H - RPL_RECESS_H])
            cylinder(d = RPL_RECESS_D, h = RPL_RECESS_H + e);

        // ── RPLIDAR M3 bolt holes — 58 mm BC ─────────────────
        for (a = [45, 135, 225, 315])
            translate([RPL_BC/2 * cos(a), RPL_BC/2 * sin(a), -e])
                cylinder(d = RPL_HOLE_D, h = PLAT_H + 2*e);

        // ── RealSense arm M4 holes — front (+X), ±ARM_DY_RS ──
        for (dy = [-ARM_DY_RS, ARM_DY_RS])
            translate([ARM_R, dy, -e])
                cylinder(d = ARM_HOLE_D, h = PLAT_H + 2*e);

        // ── IMX219 arm M4 holes — 4 × cardinal, ±ARM_DY_CAM ─
        // Tangential perpendicular for angle a: (sin(a), -cos(a))
        for (a = [0, 90, 180, 270])
            for (s = [-1, 1])
                translate([
                    ARM_R * cos(a) + s * ARM_DY_CAM * sin(a),
                    ARM_R * sin(a) - s * ARM_DY_CAM * cos(a),
                    -e
                ])
                    cylinder(d = ARM_HOLE_D, h = PLAT_H + 2*e);

        // ── Lightening pockets — between arm attachment holes ─
        for (a = [45, 135, 225, 315])
            translate([(PLAT_OD/2 - 22) * cos(a),
                       (PLAT_OD/2 - 22) * sin(a), -e])
                cylinder(d = 20, h = PLAT_H + 2*e);
    }
}

// ─────────────────────────────────────────────────────────────
// Render selector
// ─────────────────────────────────────────────────────────────
if (RENDER == "assembly") {
    color("SkyBlue", 0.9)      collar_half("front");
    color("DodgerBlue", 0.9)   mirror([0, 1, 0]) collar_half("rear");
    color("Silver", 0.85)      translate([0, 0, PLAT_Z]) platform();

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

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

} else if (RENDER == "platform_2d") {
    projection(cut = true)
        translate([0, 0, -(PLAT_Z + PLAT_H / 2)])
            platform();
}
