// ============================================================
// payload_bay_modules.scad — Payload Bay Module Template + Examples
// Issue: #170  Agent: sl-mechanical  Date: 2026-03-01
// ============================================================
//
// ── HOW TO CREATE A NEW MODULE ──────────────────────────────────────────────
//
//   1. Copy the "MODULE TEMPLATE" section at the bottom of this file.
//   2. Set MODULE_L to your module's Y length (min 60 mm).
//   3. Add your payload geometry on top of the _module_base() call.
//   4. The _module_base() provides:
//        • Male dovetail tongue (slides into rail slot)
//        • Spring detent bore(s) (for Ø6 mm ball + spring plunger)
//        • Connector target pads (4× Ø4 mm brass, matching rail pogo pins)
//        • Safety-lock M4 thumbscrew bore (side of tongue)
//        • Bottom-face flush with rail top (Z = 0 at rail top / module base)
//   5. Your payload geometry sits at Z ≥ 0 (above the rail top face).
//   6. Add a RENDER dispatch entry and export command.
//
// ── DOVETAIL TONGUE GEOMETRY ────────────────────────────────────────────────
//
//   TONGUE_BOT = DOVE_SLOT_BOT - 2*DOVE_CLEAR  = 28.0 - 0.6 = 27.4 mm
//   TONGUE_TOP = DOVE_SLOT_TOP + 2*DOVE_CLEAR  = 37.2 + 0.6 = 37.8 mm
//   TONGUE_H   = DOVE_H + 0.2  (slight extra depth, no binding at corners)
//
//   Tongue runs full module length (-Y to +Y in module coords).
//   Module body sits on top of tongue at Z = 0.
//
// ── CONNECTOR PADS ──────────────────────────────────────────────────────────
//   4× Ø4 mm brass discs press-fit into tongue bottom face.
//   Pad positions: must align with rail connector at CONN_Y when module
//   is slid to its intended position (any detent step).
//   Default: pads centred in module length → module must be placed so its
//   centre aligns with CONN_Y on rail.  Or: set MODULE_CONN_OFFSET to shift.
//
// ── PAYLOAD RATING ──────────────────────────────────────────────────────────
//   2 kg rated payload when safety-lock thumbscrew is tightened.
//   Detent-only (no thumbscrew): ~0.5 kg (impact / vibration condition).
//
// ── RENDERED EXAMPLES ────────────────────────────────────────────────────────
//   Part 1 — cargo_tray()     200×100 mm cargo tray, 30 mm walls
//   Part 2 — camera_boom()    L-arm with sensor_rail-compatible head
//   Part 3 — cup_holder()     Ø80 mm tapered cup cradle
//
// RENDER options:
//   "assembly"          all 3 modules on ghost rail
//   "base_stl"          module base / tongue only (universal)
//   "cargo_tray_stl"    cargo tray module
//   "camera_boom_stl"   camera boom module
//   "cup_holder_stl"    cup holder module
//   "target_pad_2d"     DXF — Ø4 mm brass target pad profile
//
// Export:
//   openscad payload_bay_modules.scad -D 'RENDER="base_stl"'        -o payload_base.stl
//   openscad payload_bay_modules.scad -D 'RENDER="cargo_tray_stl"'  -o payload_cargo_tray.stl
//   openscad payload_bay_modules.scad -D 'RENDER="camera_boom_stl"' -o payload_camera_boom.stl
//   openscad payload_bay_modules.scad -D 'RENDER="cup_holder_stl"'  -o payload_cup_holder.stl
// ============================================================

$fn = 64;
e   = 0.01;

// ── Rail geometry constants (must match payload_bay_rail.scad) ────────────────
RAIL_W         = 50.0;
RAIL_T         = 12.0;
DOVE_ANGLE     = 30.0;
DOVE_H         =  8.0;
DOVE_SLOT_BOT  = 28.0;
DOVE_SLOT_TOP  = DOVE_SLOT_BOT + 2 * DOVE_H * tan(DOVE_ANGLE);
DOVE_CLEAR     =  0.3;
DETENT_PITCH   = 50.0;
DETENT_BALL_D  =  6.0;
DETENT_SPG_OD  =  6.2;
DETENT_SPG_L   = 16.0;
CONN_PIN_SPC   =  5.0;
CONN_N_PINS    =  4;
CONN_HOUSING_D =  8.0;

// ── Module tongue (male dovetail) geometry ────────────────────────────────────
TONGUE_BOT     = DOVE_SLOT_BOT - 2*DOVE_CLEAR;  // 27.4 mm
TONGUE_TOP     = DOVE_SLOT_TOP + 2*DOVE_CLEAR;  // 37.8 mm
TONGUE_H       = DOVE_H + 0.2;                  // 8.2 mm (slight extra depth)

// ── Connector target pad ──────────────────────────────────────────────────────
TARGET_PAD_D   =  4.0;    // brass pad OD (slightly larger than pogo Ø2 mm)
TARGET_PAD_T   =  1.5;    // brass pad thickness
TARGET_PAD_RECESS = 1.3;  // press-fit recess depth (pad is 0.2 mm proud)
// 4 pads at CONN_PIN_SPC pitch, centred in module tongue
CONN_SPAN      = (CONN_N_PINS - 1) * CONN_PIN_SPC;  // 15 mm

// ── Module safety lock ────────────────────────────────────────────────────────
LOCK_BOLT_D    =  4.3;    // M4 thumbscrew bore through tongue side
LOCK_BOLT_Z    =  TONGUE_H/2; // Z of thumbscrew CL from tongue bottom
// Thumbscrew tightens against continuous groove in rail side.

// Fasteners
M3_D = 3.3;
M4_D = 4.3;

// ============================================================
// RENDER DISPATCH
// ============================================================
RENDER = "assembly";

if      (RENDER == "assembly")        assembly();
else if (RENDER == "base_stl")        _module_base(80);
else if (RENDER == "cargo_tray_stl")  cargo_tray();
else if (RENDER == "camera_boom_stl") camera_boom();
else if (RENDER == "cup_holder_stl")  cup_holder();
else if (RENDER == "target_pad_2d") {
    projection(cut = true) translate([0, 0, -0.5])
        linear_extrude(1) circle(d = TARGET_PAD_D);
}

// ============================================================
// ASSEMBLY PREVIEW
// ============================================================
module assembly() {
    // Ghost rail
    %color("Silver", 0.25)
        translate([0, 0, -RAIL_T])
            cube([RAIL_W, 600, RAIL_T], center = true);

    // Cargo tray at Y=50 (first detent position)
    color("OliveDrab", 0.85)
        translate([0, 50, 0])
            cargo_tray();

    // Cup holder at Y=300
    color("RoyalBlue", 0.85)
        translate([0, 300, 0])
            cup_holder();

    // Camera boom at Y=500
    color("DarkSlateGray", 0.85)
        translate([0, 500, 0])
            camera_boom();
}

// ============================================================
// UNIVERSAL MODULE BASE  (_module_base)
// ============================================================
// All modules use this as their foundation.
// Provides: male dovetail tongue, detent bore, connector pads, lock bore.
//
// module_len : module length in Y (≥ 60 mm)
// n_detents  : how many ball detent bores to include (1 or 2)
// conn_offset: Y offset of connector pads from module centre (default 0)
//
// After calling _module_base(), add payload geometry at Z = 0 and above.
// The tongue occupies Z = -(TONGUE_H) to Z = 0.
// Rail top face is at Z = 0.
module _module_base(module_len, n_detents = 1, conn_offset = 0) {
    ml  = module_len;

    difference() {
        // ── Dovetail tongue (male) ────────────────────────────────────
        translate([0, ml/2, -TONGUE_H/2])
            linear_extrude(TONGUE_H, center = true, twist = 0,
                           convexity = 2)
                _tongue_profile_2d();

        // ── Spring detent bore(s) (up through tongue top face) ────────
        // 1 detent: at module centre; 2 detents: at ±25 mm from centre
        for (i = [0 : n_detents - 1]) {
            dy = (n_detents == 1)
                    ? ml/2
                    : ml/2 + (i - (n_detents-1)/2) * DETENT_PITCH;
            translate([0, dy, -(TONGUE_H/2) - DETENT_SPG_L/2])
                cylinder(d = DETENT_SPG_OD,
                         h = DETENT_SPG_L + TONGUE_H/2 + e);
        }

        // ── Connector target pad recesses (tongue bottom face) ────────
        for (i = [0 : CONN_N_PINS - 1]) {
            px = (i - (CONN_N_PINS-1)/2) * CONN_PIN_SPC;
            translate([px,
                       ml/2 + conn_offset,
                       -TONGUE_H + e])
                cylinder(d = TARGET_PAD_D + 0.1,
                         h = TARGET_PAD_RECESS + e);
        }

        // ── Safety-lock M4 thumbscrew bore (right side of tongue) ─────
        // Bore exits tongue right face, tip bears on rail side groove
        translate([TONGUE_TOP/2 + e, ml/2, LOCK_BOLT_Z - TONGUE_H])
            rotate([0, 90, 0])
                cylinder(d = LOCK_BOLT_D,
                         h = (TONGUE_TOP - TONGUE_BOT)/2 + 6 + e);

        // ── Lead-in chamfer on entry end of tongue ────────────────────
        // 2 mm chamfer on bottom corners of tongue at Y=0 end
        translate([0, 0, -TONGUE_H])
            rotate([45, 0, 0])
                cube([TONGUE_TOP + 2*e, 4, 4], center = true);
    }
}

// ── Tongue 2D cross-section (male dovetail, trapezoid wider at bottom) ────────
// Module tongue: narrower at top (entering slot), wider at bottom (interlocking).
// Note orientation: tongue points DOWN (-Z), so wider face is at bottom (-Z).
module _tongue_profile_2d() {
    polygon([
        [-TONGUE_TOP/2,  0],         // top-left  (at Z = 0, flush with rail top)
        [ TONGUE_TOP/2,  0],         // top-right
        [ TONGUE_BOT/2, -TONGUE_H],  // bottom-right (interlocking face)
        [-TONGUE_BOT/2, -TONGUE_H],  // bottom-left
    ]);
}

// ============================================================
// PART 1 — CARGO TRAY
// ============================================================
// 200×100 mm open tray for transporting small items.
// 30 mm walls, chamfered rim, 4× drainage holes.
// Two Velcro strip slots on tray floor for cargo retention.
// Module length: 200 mm (4 detent positions).
// Weight budget: tray printed ~180 g; payload up to 2 kg.
TRAY_L       = 200.0;   // module Y length
TRAY_W       = 100.0;   // tray interior width (X)
TRAY_WALL    =   3.0;   // tray wall thickness
TRAY_H       =  30.0;   // tray wall height above module base
TRAY_FLOOR_T =   3.0;   // tray floor thickness

module cargo_tray() {
    // Mount base on rail (2 detents at ±50 mm from module centre)
    _module_base(TRAY_L, n_detents = 2);

    difference() {
        union() {
            // ── Tray body on top of base ────────────────────────────
            translate([-TRAY_W/2 - TRAY_WALL, 0,
                       0])
                cube([TRAY_W + 2*TRAY_WALL,
                      TRAY_L,
                      TRAY_FLOOR_T + TRAY_H]);

            // ── Corner gussets (stiffening for 2 kg load) ───────────
            for (cx = [-1, 1]) for (cy = [0, 1])
                hull() {
                    translate([cx * TRAY_W/2,
                               cy * (TRAY_L - TRAY_WALL),
                               0])
                        cube([TRAY_WALL + e, TRAY_WALL + e,
                              TRAY_FLOOR_T + TRAY_H * 0.6], center = true);
                    translate([cx * (TRAY_W/2 - 10),
                               cy * (TRAY_L - TRAY_WALL),
                               0])
                        cylinder(d = TRAY_WALL * 2, h = e);
                }
        }

        // ── Tray interior cavity ─────────────────────────────────────
        translate([-TRAY_W/2, TRAY_WALL,
                   TRAY_FLOOR_T])
            cube([TRAY_W, TRAY_L - 2*TRAY_WALL,
                  TRAY_H + e]);

        // ── Drainage holes (4× Ø8 mm in floor) ──────────────────────
        for (dx = [-TRAY_W/4, TRAY_W/4])
        for (dy = [TRAY_L/4, 3*TRAY_L/4])
            translate([dx, dy, -e])
                cylinder(d = 8, h = TRAY_FLOOR_T + 2*e);

        // ── Velcro slot × 2 (25 mm wide grooves in floor) ────────────
        for (dy = [TRAY_L/3, 2*TRAY_L/3])
            translate([0, dy, TRAY_FLOOR_T - 1.5])
                cube([TRAY_W - 10, 25, 2 + e], center = true);

        // ── Rim chamfer (top inner edge, ergonomic) ───────────────────
        translate([0, TRAY_L/2, TRAY_FLOOR_T + TRAY_H + e])
            cube([TRAY_W + 2*TRAY_WALL + 2*e,
                  TRAY_L + 2*e, 4], center = true);

        // ── Side slots for bungee cord retention (3× each long side) ─
        for (sx = [-1, 1])
        for (sy = [TRAY_L/4, TRAY_L/2, 3*TRAY_L/4])
            translate([sx * (TRAY_W/2 + TRAY_WALL/2),
                       sy, TRAY_FLOOR_T + TRAY_H/2])
                cube([TRAY_WALL + 2*e, 8, 6], center = true);
    }
}

// ============================================================
// PART 2 — CAMERA BOOM
// ============================================================
// L-shaped arm: vertical mast + horizontal boom.
// Boom head accepts sensor_rail 2020 T-slot (RAIL_W/2 bolt pattern).
// Sensor head can be rotated 0/90/180° and locked with M4 bolt.
// Module length: 80 mm; arm rises 120 mm, boom extends 80 mm forward.
BOOM_MODULE_L  =  80.0;
BOOM_MAST_H    = 120.0;   // mast height above rail top (Z)
BOOM_ARM_L     =  80.0;   // horizontal boom length (+Y forward)
BOOM_ARM_W     =  20.0;   // arm cross-section width
BOOM_ARM_T     =  20.0;   // arm cross-section height
BOOM_HEAD_W    =  50.0;   // sensor head width (matches 2020 rail flange)
BOOM_HEAD_H    =  20.0;   // sensor head plate height
BOOM_HEAD_T    =   5.0;   // sensor head plate thickness

module camera_boom() {
    _module_base(BOOM_MODULE_L, n_detents = 1);

    difference() {
        union() {
            // ── Mast (vertical column from module body) ──────────────
            translate([-BOOM_ARM_W/2, BOOM_MODULE_L/2 - BOOM_ARM_T/2, 0])
                cube([BOOM_ARM_W, BOOM_ARM_T, BOOM_MAST_H]);

            // ── Horizontal boom (from mast top, extends +Y) ──────────
            translate([-BOOM_ARM_W/2,
                       BOOM_MODULE_L/2 - BOOM_ARM_T/2,
                       BOOM_MAST_H - BOOM_ARM_W])
                cube([BOOM_ARM_W, BOOM_ARM_L, BOOM_ARM_W]);

            // ── Sensor head plate (at boom tip) ──────────────────────
            translate([-BOOM_HEAD_W/2,
                       BOOM_MODULE_L/2 - BOOM_ARM_T/2 + BOOM_ARM_L,
                       BOOM_MAST_H - BOOM_ARM_W - BOOM_HEAD_H/2 + BOOM_ARM_W/2])
                cube([BOOM_HEAD_W, BOOM_HEAD_T, BOOM_HEAD_H]);

            // ── Junction gussets (mast + horizontal boom) ────────────
            translate([-BOOM_ARM_W/2,
                       BOOM_MODULE_L/2 - BOOM_ARM_T/2,
                       BOOM_MAST_H - BOOM_ARM_W])
                rotate([45, 0, 0])
                    cube([BOOM_ARM_W, BOOM_ARM_W * 0.7, BOOM_ARM_W * 0.7]);
        }

        // ── 2020 sensor-rail bolt pattern in head plate ───────────────
        // 2× M5 slots (matches sensor_rail.scad tank_clamp slot geometry)
        for (sz = [-BOOM_HEAD_H/4, BOOM_HEAD_H/4])
            translate([0,
                       BOOM_MODULE_L/2 - BOOM_ARM_T/2 + BOOM_ARM_L + BOOM_HEAD_T + e,
                       BOOM_MAST_H - BOOM_ARM_W/2 + sz])
                rotate([90, 0, 0])
                    hull() {
                        translate([-6, 0, 0])
                            cylinder(d = 5.3, h = BOOM_HEAD_T + 2*e);
                        translate([+6, 0, 0])
                            cylinder(d = 5.3, h = BOOM_HEAD_T + 2*e);
                    }

        // ── Tilt angle slots (3 positions: 0°, ±15°) ─────────────────
        for (ta = [-15, 0, 15]) {
            translate([0,
                       BOOM_MODULE_L/2 - BOOM_ARM_T/2 + BOOM_ARM_L/2,
                       BOOM_MAST_H - BOOM_ARM_W/2])
                rotate([ta, 0, 0])
                    translate([0, BOOM_ARM_L/2, 0])
                        cylinder(d = 4.3, h = BOOM_ARM_W + 2*e,
                                 center = true);
        }

        // ── Cable tie slots in mast ───────────────────────────────────
        for (cz = [BOOM_MAST_H * 0.3, BOOM_MAST_H * 0.6])
            translate([0, BOOM_MODULE_L/2, cz])
                cube([BOOM_ARM_W + 2*e, 4, 4], center = true);

        // ── Lightening pockets in mast ────────────────────────────────
        translate([0, BOOM_MODULE_L/2, BOOM_MAST_H * 0.4])
            cube([BOOM_ARM_W - 6, BOOM_ARM_T - 6,
                  BOOM_MAST_H * 0.4], center = true);
    }
}

// ============================================================
// PART 3 — CUP HOLDER
// ============================================================
// Tapered cup cradle for standard travel mugs / water bottles.
// Inner diameter: 80 mm at top, 68 mm at bottom (matches Ø70 mm typical mug).
// Flexible gripper ribs (cut slots) provide spring retention.
// Drain hole at bottom for condensation.
// Module length: 80 mm.
CUP_MODULE_L  =  80.0;
CUP_INNER_TOP = 80.0;    // inner bore OD at top
CUP_INNER_BOT = 68.0;    // inner bore OD at bottom (taper for grip)
CUP_OUTER_T   =  4.0;    // wall thickness
CUP_H         = 80.0;    // cup holder height
CUP_GRIP_SLOTS = 8;      // number of flex slots (spring grip)
CUP_SLOT_W    =  2.5;    // flex slot width
CUP_SLOT_H    = 40.0;    // flex slot height (from top)

module cup_holder() {
    _module_base(CUP_MODULE_L, n_detents = 1);

    difference() {
        union() {
            // ── Outer shell (tapered cylinder) ───────────────────────
            cylinder(d1 = CUP_INNER_BOT + 2*CUP_OUTER_T,
                     d2 = CUP_INNER_TOP + 2*CUP_OUTER_T,
                     h  = CUP_H);

            // ── Base flange (connects to module body footprint) ───────
            hull() {
                cylinder(d = CUP_INNER_BOT + 2*CUP_OUTER_T + 4,
                         h = 4);
                translate([0, CUP_MODULE_L/2, 0])
                    cube([RAIL_W, CUP_MODULE_L, 4], center = true);
            }
        }

        // ── Inner bore (tapered cup cavity) ──────────────────────────
        translate([0, 0, CUP_OUTER_T])
            cylinder(d1 = CUP_INNER_BOT, d2 = CUP_INNER_TOP,
                     h  = CUP_H + e);

        // ── Base drain hole ──────────────────────────────────────────
        translate([0, 0, -e])
            cylinder(d = 12, h = CUP_OUTER_T + 2*e);

        // ── Flex grip slots (from top down) ──────────────────────────
        // Slots allow upper rim to flex inward and grip cup body
        for (i = [0 : CUP_GRIP_SLOTS - 1]) {
            angle = i * 360 / CUP_GRIP_SLOTS;
            rotate([0, 0, angle])
                translate([CUP_INNER_TOP/2 + CUP_OUTER_T/2, 0, CUP_H - CUP_SLOT_H])
                    cube([CUP_OUTER_T + 2*e, CUP_SLOT_W, CUP_SLOT_H + e],
                         center = true);
        }

        // ── Exterior branding recess (optional label area) ────────────
        translate([CUP_INNER_BOT/2 + CUP_OUTER_T/2 - 0.5, 0, CUP_H/2])
            rotate([0, 90, 0])
                cube([25, 40, 1 + e], center = true);
    }
}

// ============================================================
// ══ MODULE TEMPLATE ═══════════════════════════════════════════
// ══ Copy this block to create a new payload module ════════════
// ============================================================
//
// module my_new_module() {
//     MY_LEN = 120.0;   // module length — must be multiple of DETENT_PITCH (50 mm)
//
//     // Always start with the base (provides tongue, pads, detent bore)
//     _module_base(MY_LEN, n_detents = 2);
//
//     // Add your payload geometry here.
//     // Z = 0 is the rail top face / module mounting face.
//     // Build upward from Z = 0.
//
//     difference() {
//         union() {
//             // Example: a simple platform
//             translate([-(RAIL_W + 10)/2, 0, 0])
//                 cube([RAIL_W + 10, MY_LEN, 10]);
//
//             // Add your geometry...
//         }
//
//         // Add your cutouts...
//     }
// }
//
// ── Don't forget to: ──────────────────────────────────────────
//   1. Add  else if (RENDER == "my_module_stl")  my_new_module();
//      in the RENDER DISPATCH block above.
//   2. Add export command in BOM / README.
//   3. Test: verify tongue fits rail slot (should slide with 0.3 mm clearance).
//   4. Verify connector pad positions align with CONN_Y on rail.
// ============================================================
