// ============================================================
// rover_electronics_bay.scad — SaltyRover Electronics Bay
// Issue: #109  Agent: sl-mechanical  Date: 2026-03-01
// ============================================================
//
// Enclosed electronics housing sitting on the rover deck plate.
// Houses:  • Flight Controller (FC) — 30.5×30.5 mm M3 standoffs
//          • Jetson Orin NX / Nano  — 58×49 mm M3 standoffs
//          • Battery access slot    — left side slide-in
//          • RPLIDAR A1M8 tower     — integrated on lid top
//          • Ventilation slots      — all 4 walls + lid
//
// Shared mounting patterns (swappable with SaltyLab):
//   FC    : 30.5 × 30.5 mm M3 (MAMBA F722S / Pixhawk)
//   Jetson: 58 × 49 mm M3    (Orin NX / Nano Devkit carrier)
//
// Coordinate: bay centred at origin; Z=0 = deck top face.
// Bay body rests directly on deck top (no additional standoffs).
//
// Print:
//   Material : PETG (bay body + lid)
//   Settings : 4 perimeters, 30% gyroid infill
//   Orientation: open top face up for body; lid printed flat.
//   Note: Bay is too large to print as one piece on most 220mm beds.
//         Use RENDER="front_half" and RENDER="rear_half" for split,
//         joined with 3× M3 bolts and alignment pins.
//
// Export commands:
//   Bay body (full, for large-bed printers):
//     openscad rover_electronics_bay.scad -D 'RENDER="bay_stl"' -o rover_elec_bay.stl
//   Front half:
//     openscad rover_electronics_bay.scad -D 'RENDER="front_half"' -o rover_elec_bay_front.stl
//   Rear half:
//     openscad rover_electronics_bay.scad -D 'RENDER="rear_half"' -o rover_elec_bay_rear.stl
//   Lid (with RPLIDAR tower):
//     openscad rover_electronics_bay.scad -D 'RENDER="lid_stl"' -o rover_elec_bay_lid.stl
//   Assembly preview:
//     openscad rover_electronics_bay.scad -D 'RENDER="assembly"'
// ============================================================

$fn = 64;
e   = 0.01;

// ── Bay exterior dimensions ──────────────────────────────────────────────────
BAY_L        = 240.0;   // length left-right (X in rover coords = Y here)
BAY_W        = 200.0;   // width fore-aft    (Y in rover = X here)
BAY_H        =  80.0;   // interior height
BAY_WALL     =   3.0;   // wall thickness (all sides)
BAY_FLOOR    =   4.0;   // floor thickness (rests on deck)
BAY_R        =   8.0;   // exterior corner radius

// ── Ventilation slots ────────────────────────────────────────────────────────
VENT_W       =  20.0;   // slot width
VENT_H       =   6.0;   // slot height
VENT_PITCH   =  28.0;   // slot centre-to-centre pitch
VENT_FROM_BOT = 12.0;   // lowest vent row height above floor exterior

// ── Lid ─────────────────────────────────────────────────────────────────────
LID_T        =   4.0;   // lid plate thickness
LID_RIM_H    =   8.0;   // lip that drops inside bay walls (retention)
LID_RIM_GAP  =   0.4;   // clearance between lid rim and bay inner wall

// ── FC mount — 30.5×30.5 mm M3 (shared SaltyLab) ────────────────────────────
FC_PITCH     = 30.5;
FC_HOLE_D    =  3.2;
FC_STANDOFF_H = 8.0;
FC_STANDOFF_OD = 7.0;
// FC positioned toward front-left inside bay (offset from centre)
FC_OFF_X     = -BAY_L/2 + 60.0;   // left side (left = cable/ESC side)
FC_OFF_Y     = -BAY_W/2 + 50.0;   // front side

// ── Jetson Orin mount — 58×49 mm M3 (shared SaltyLab) ───────────────────────
ORIN_HOLE_X  = 58.0;
ORIN_HOLE_Y  = 49.0;
ORIN_HOLE_D  =  3.2;
ORIN_STANDOFF_H  = 10.0;
ORIN_STANDOFF_OD =  7.0;
// Jetson positioned toward rear-right (toward robot rear, USB/HDMI accessible)
ORIN_OFF_X   =  BAY_L/2 - 70.0;   // right side
ORIN_OFF_Y   =  BAY_W/2 - 55.0;   // rear side

// ── Battery access slot (left wall slide-in) ──────────────────────────────────
// The under-deck battery tray is separate (rover_battery_tray.scad).
// A slot in the bay left wall allows BMS cable + main power harness.
BATT_SLOT_W  =  30.0;   // harness slot width
BATT_SLOT_H  =  20.0;   // harness slot height
BATT_SLOT_Z  =  20.0;   // slot bottom above floor interior

// ── RPLIDAR A1M8 tower (on lid, top centre) ───────────────────────────────────
RPL_TOWER_OD =  28.0;   // tower OD (hollow column)
RPL_TOWER_ID =  16.0;   // hollow core ID (cable routing)
RPL_TOWER_H  = 100.0;   // tower height above lid top face
// RPLIDAR A1M8 bolt circle: 58 mm dia, 4× M3 at 45°/135°/225°/315°
RPL_BC       =  58.0;
RPL_HOLE_D   =   3.3;   // M3 clearance
RPL_PLATFORM_D =  90.0; // platform disk at tower top

// ── Bay-to-deck attachment ────────────────────────────────────────────────────
// 8× M3 SHCS through bay floor flanges into deck (matching saltyrover_chassis_r2.scad)
DECK_BOLT_D  =  3.3;
DECK_BOLT_INSET = 8.0;  // bolt CL from exterior corner

// ── Lid retention (M3 corner bolts) ──────────────────────────────────────────
LID_BOLT_D   =  3.3;
LID_BOLT_POS =  8.0;   // bolt CL from exterior wall

M3_D = 3.3;
M4_D = 4.3;

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

if (RENDER == "assembly") {
    assembly();
} else if (RENDER == "bay_stl") {
    bay_body();
} else if (RENDER == "front_half") {
    // Split along XZ plane (Y=0) — front half
    intersection() {
        bay_body();
        translate([0, -BAY_W/2 - BAY_WALL, 0])
            cube([BAY_L + 2*BAY_WALL + 2, BAY_W/2 + BAY_WALL + 1,
                  BAY_H + BAY_FLOOR + LID_T + 2]);
    }
} else if (RENDER == "rear_half") {
    // Split along XZ plane (Y=0) — rear half
    intersection() {
        bay_body();
        translate([0, 0, 0])
            cube([BAY_L + 2*BAY_WALL + 2, BAY_W/2 + BAY_WALL + 1,
                  BAY_H + BAY_FLOOR + LID_T + 2]);
    }
} else if (RENDER == "lid_stl") {
    bay_lid();
}

// ============================================================
// ASSEMBLY PREVIEW
// ============================================================
module assembly() {
    color("OliveDrab",    0.80) bay_body();
    color("DarkOliveGreen", 0.70)
        translate([0, 0, BAY_FLOOR + BAY_H + 1])
            bay_lid();

    // FC standoffs + ghost board
    color("LightGray", 0.60) fc_standoffs();
    %color("DarkGreen", 0.30)
        translate([FC_OFF_X, FC_OFF_Y, BAY_FLOOR + FC_STANDOFF_H])
            cube([76, 42, 3], center = true);

    // Jetson standoffs + ghost board
    color("LightGray", 0.60) jetson_standoffs();
    %color("DarkBlue", 0.25)
        translate([ORIN_OFF_X, ORIN_OFF_Y,
                   BAY_FLOOR + ORIN_STANDOFF_H])
            cube([100, 80, 5], center = true);
}

// ============================================================
// BAY BODY  (open-top box with ventilation + mounts)
// ============================================================
module bay_body() {
    outer_x = BAY_L + 2*BAY_WALL;
    outer_y = BAY_W + 2*BAY_WALL;
    outer_z = BAY_FLOOR + BAY_H;

    difference() {
        // ── Outer shell (rounded rectangle) ────────────────────────────
        linear_extrude(outer_z)
            minkowski() {
                square([outer_x - 2*BAY_R, outer_y - 2*BAY_R], center = true);
                circle(r = BAY_R);
            }

        // ── Inner cavity ───────────────────────────────────────────────
        translate([-BAY_L/2, -BAY_W/2, BAY_FLOOR])
            cube([BAY_L, BAY_W, BAY_H + e]);

        // ── Ventilation slots — left wall (−X) ────────────────────────
        for (i = [-2:2])
            translate([-(BAY_L/2 + BAY_WALL + e),
                       i * VENT_PITCH - VENT_W/2,
                       VENT_FROM_BOT])
                cube([BAY_WALL + 2*e, VENT_W, VENT_H]);

        // ── Ventilation slots — right wall (+X) ───────────────────────
        for (i = [-2:2])
            translate([BAY_L/2 - e,
                       i * VENT_PITCH - VENT_W/2,
                       VENT_FROM_BOT])
                cube([BAY_WALL + 2*e, VENT_W, VENT_H]);

        // ── Ventilation slots — front wall (−Y) ───────────────────────
        for (i = [-2:2])
            translate([i * VENT_PITCH - VENT_W/2,
                       -(BAY_W/2 + BAY_WALL + e),
                       VENT_FROM_BOT])
                cube([VENT_W, BAY_WALL + 2*e, VENT_H]);

        // ── Ventilation slots — rear wall (+Y) ────────────────────────
        for (i = [-2:2])
            translate([i * VENT_PITCH - VENT_W/2,
                       BAY_W/2 - e,
                       VENT_FROM_BOT])
                cube([VENT_W, BAY_WALL + 2*e, VENT_H]);

        // ── Battery / harness slot (left wall) ────────────────────────
        translate([-(BAY_L/2 + BAY_WALL + e),
                   -BATT_SLOT_W/2,
                   BAY_FLOOR + BATT_SLOT_Z])
            cube([BAY_WALL + 2*e, BATT_SLOT_W, BATT_SLOT_H]);

        // ── FC mount holes through floor ──────────────────────────────
        for (dx = [-FC_PITCH/2, FC_PITCH/2])
        for (dy = [-FC_PITCH/2, FC_PITCH/2])
            translate([FC_OFF_X + dx, FC_OFF_Y + dy, -e])
                cylinder(d = FC_HOLE_D, h = BAY_FLOOR + 2*e);

        // ── Jetson mount holes through floor ──────────────────────────
        for (dx = [-ORIN_HOLE_X/2, ORIN_HOLE_X/2])
        for (dy = [-ORIN_HOLE_Y/2, ORIN_HOLE_Y/2])
            translate([ORIN_OFF_X + dx, ORIN_OFF_Y + dy, -e])
                cylinder(d = ORIN_HOLE_D, h = BAY_FLOOR + 2*e);

        // ── Bay-to-deck M3 bolt holes (8× corners, through floor flange)
        for (sx = [-1, 1])
        for (sy = [-1, 1]) {
            bx = sx * (BAY_L/2 + BAY_WALL - DECK_BOLT_INSET);
            by = sy * (BAY_W/2 + BAY_WALL - DECK_BOLT_INSET);
            translate([bx, by, -e])
                cylinder(d = DECK_BOLT_D, h = BAY_FLOOR + 2*e);
        }
        // Extra 2 bolts per long wall (centre)
        for (sy = [-1, 1])
            translate([0, sy * (BAY_W/2 + BAY_WALL - DECK_BOLT_INSET), -e])
                cylinder(d = DECK_BOLT_D, h = BAY_FLOOR + 2*e);

        // ── Lid retention M3 threaded bosses cut (4× top rim corners) ──
        for (sx = [-1, 1])
        for (sy = [-1, 1]) {
            lx = sx * (BAY_L/2 + BAY_WALL - LID_BOLT_POS);
            ly = sy * (BAY_W/2 + BAY_WALL - LID_BOLT_POS);
            translate([lx, ly, outer_z - 12])
                cylinder(d = LID_BOLT_D - 0.3, h = 14);   // M3 self-tap bore
        }

        // ── Cable pass-through grommets slots (bottom, 2× for deck slots)
        for (sy = [-1, 1])
            hull() {
                translate([-15, sy * (BAY_W/2 - 6), -e])
                    cylinder(d = 12, h = BAY_FLOOR + 2*e);
                translate([ 15, sy * (BAY_W/2 - 6), -e])
                    cylinder(d = 12, h = BAY_FLOOR + 2*e);
            }
    }

    // ── FC standoffs ─────────────────────────────────────────────────────────
    fc_standoffs();

    // ── Jetson standoffs ─────────────────────────────────────────────────────
    jetson_standoffs();
}

// ── FC standoffs (inside bay, above floor) ───────────────────────────────────
module fc_standoffs() {
    for (dx = [-FC_PITCH/2, FC_PITCH/2])
    for (dy = [-FC_PITCH/2, FC_PITCH/2])
        translate([FC_OFF_X + dx, FC_OFF_Y + dy, BAY_FLOOR])
            difference() {
                cylinder(d = FC_STANDOFF_OD, h = FC_STANDOFF_H);
                // Threaded bore (M3 screw from above)
                translate([0, 0, FC_STANDOFF_H - 6])
                    cylinder(d = 2.5, h = 7);   // M3 tap drill (Ø2.5)
                // Through clearance from floor (to match deck FC holes)
                cylinder(d = FC_HOLE_D, h = FC_STANDOFF_H - 6);
            }
}

// ── Jetson Orin standoffs (inside bay, above floor) ──────────────────────────
module jetson_standoffs() {
    for (dx = [-ORIN_HOLE_X/2, ORIN_HOLE_X/2])
    for (dy = [-ORIN_HOLE_Y/2, ORIN_HOLE_Y/2])
        translate([ORIN_OFF_X + dx, ORIN_OFF_Y + dy, BAY_FLOOR])
            difference() {
                cylinder(d = ORIN_STANDOFF_OD, h = ORIN_STANDOFF_H);
                // M3 tap bore (top 8mm)
                translate([0, 0, ORIN_STANDOFF_H - 8])
                    cylinder(d = 2.5, h = 9);
                // Clearance from floor
                cylinder(d = ORIN_HOLE_D, h = ORIN_STANDOFF_H - 8);
            }
}

// ============================================================
// BAY LID  (with RPLIDAR A1M8 tower and ventilation)
// ============================================================
// Lid drops over bay walls (retention lip) and is held with 4× M3 screws.
// RPLIDAR A1M8 tower rises from lid centre.
// Lid ventilation slots allow convective air circulation.
// ============================================================
module bay_lid() {
    outer_x = BAY_L + 2*BAY_WALL;
    outer_y = BAY_W + 2*BAY_WALL;

    difference() {
        union() {
            // ── Lid plate ─────────────────────────────────────────────
            linear_extrude(LID_T)
                minkowski() {
                    square([outer_x - 2*BAY_R, outer_y - 2*BAY_R],
                            center = true);
                    circle(r = BAY_R);
                }

            // ── Retention rim (drops inside bay walls) ─────────────────
            rim_x = BAY_L - 2*LID_RIM_GAP;
            rim_y = BAY_W - 2*LID_RIM_GAP;
            translate([0, 0, -LID_RIM_H + e])
                linear_extrude(LID_RIM_H)
                    difference() {
                        minkowski() {
                            square([rim_x - 2*BAY_R, rim_y - 2*BAY_R],
                                    center = true);
                            circle(r = BAY_R);
                        }
                        // Hollow interior
                        offset(r = -BAY_WALL)
                            minkowski() {
                                square([rim_x - 2*BAY_R, rim_y - 2*BAY_R],
                                        center = true);
                                circle(r = BAY_R);
                            }
                    }

            // ── RPLIDAR tower (centred on lid) ─────────────────────────
            translate([0, 0, LID_T])
                rplidar_tower();
        }

        // ── Lid ventilation slots (3× rows, 5 slots each) ─────────────
        for (i = [-2:2]) {
            translate([i * VENT_PITCH - VENT_W/2, -outer_y/2 + 20, -e])
                cube([VENT_W, outer_y - 40, LID_T + 2*e]);
        }

        // ── 4× M3 lid retention bolt holes ────────────────────────────
        for (sx = [-1, 1])
        for (sy = [-1, 1]) {
            lx = sx * (outer_x/2 - LID_BOLT_POS);
            ly = sy * (outer_y/2 - LID_BOLT_POS);
            translate([lx, ly, -e])
                cylinder(d = M3_D, h = LID_T + 2*e);
        }
    }
}

// ── RPLIDAR A1M8 tower (on lid) ──────────────────────────────────────────────
// Hollow column provides height above bay for RPLIDAR 360° scan clearance.
// Anti-vibration ring (rplidar_mount.scad) sits atop the platform.
// Tower height: 100 mm above lid = ~185 mm total above deck.
module rplidar_tower() {
    difference() {
        union() {
            // Hollow column
            cylinder(d = RPL_TOWER_OD, h = RPL_TOWER_H);

            // Flared base (distributes load, improves print adhesion)
            cylinder(d = RPL_TOWER_OD + 16, h = 8);

            // Top platform disk
            translate([0, 0, RPL_TOWER_H])
                cylinder(d = RPL_PLATFORM_D, h = 8);
        }

        // Hollow core (cable routing for RPLIDAR USB)
        translate([0, 0, -e])
            cylinder(d = RPL_TOWER_ID, h = RPL_TOWER_H + 9);

        // 4× base-to-lid M3 attachment holes (through flared base)
        for (a = [0, 90, 180, 270])
            rotate([0, 0, a])
                translate([(RPL_TOWER_OD + 12) / 2, 0, -e])
                    cylinder(d = M3_D, h = 10);

        // RPLIDAR A1M8 mounting holes (4× M3, 58 mm BC, 45° offset)
        // Matches rplidar_mount.scad / sensor_head.scad RPL_BC pattern
        for (a = [45, 135, 225, 315])
            translate([RPL_BC/2 * cos(a),
                       RPL_BC/2 * sin(a),
                       RPL_TOWER_H - e])
                cylinder(d = RPL_HOLE_D, h = 10);

        // Rotation alignment slot (sets RPLIDAR scan start angle)
        translate([RPL_BC/2 - 3, -2, RPL_TOWER_H - e])
            cube([8, 4, 10]);
    }
}
