// =============================================================================
// SaltyBot — Parametric Chassis Frame
// Task: bd-1iy5
// Agent: sl-mechanical
// Date: 2026-02-28
//
// Self-balancing two-wheeled robot chassis
// Requirements:
//   - 600mm wheelbase
//   - 2x hoverboard hub motors (170mm OD)
//   - ESP32-S3 ESP32-S3 BALANCE FC mount (30.5x30.5mm pattern)
//   - Battery tray (24V 4Ah — ~180x70x50mm pack)
//   - Jetson Orin Nano Super B01 mount plate (100x80mm, M3 holes)
//   - Front/rear bumper brackets
// =============================================================================

// ─── RENDER QUALITY ──────────────────────────────────────────────────────────
$fn = 64;

// =============================================================================
// PARAMETERS — edit here to adjust the whole model
// =============================================================================

// ── Wheelbase / overall geometry ─────────────────────────────────────────────
WHEELBASE          = 600;   // mm, center-to-center axle distance (lateral)
FRAME_WIDTH        = 220;   // mm, front-to-back depth of main deck
DECK_THICKNESS     = 6;     // mm, main deck plate thickness
WALL_T             = 4;     // mm, general wall thickness for brackets/ribs

// ── Hub motor parameters ─────────────────────────────────────────────────────
MOTOR_OD           = 170;   // mm, motor housing outer diameter
MOTOR_AXLE_D       = 14;    // mm, axle bolt diameter
MOTOR_AXLE_FLAT    = 10;    // mm, axle flat-to-flat (for anti-rotation)
MOTOR_FORK_WIDTH   = 24;    // mm, dropout slot width (fits 10-14mm axle + spacers)
MOTOR_FORK_DEPTH   = 60;    // mm, dropout slot depth from fork tip
MOTOR_FORK_H       = 80;    // mm, total height of motor fork bracket
MOTOR_FORK_T       = 8;     // mm, fork plate thickness
AXLE_HEIGHT        = 310;   // mm, axle CL above ground (motor radius + clearance)

// ── FC mount (ESP32-S3 BALANCE — 30.5 × 30.5 mm M3 pattern) ──────────────────────
FC_MOUNT_SPACING   = 30.5;  // mm, hole pattern pitch
FC_MOUNT_HOLE_D    = 3.2;   // mm, M3 clearance
FC_STANDOFF_H      = 6;     // mm, standoff height
FC_PAD_T           = 3;     // mm, mounting pad thickness

// ── Battery tray (24V 4Ah LiPo / LiFePO4) ───────────────────────────────────
BATT_L             = 185;   // mm, cell pack length
BATT_W             = 72;    // mm, cell pack width
BATT_H             = 52;    // mm, cell pack height
BATT_WALL          = 3;     // mm, tray wall thickness
BATT_FLOOR         = 4;     // mm, tray floor thickness
BATT_STRAP_W       = 20;    // mm, Velcro strap slot width
BATT_STRAP_T       = 2;     // mm, strap slot depth

// ── Jetson Orin Nano Super B01 mount plate ──────────────────────────────────────────────
// B01 carrier board hole pattern: 58 x 58 mm M3 (inner) + corner pass-throughs
JETSON_HOLE_PITCH  = 58;    // mm, M3 mounting hole pattern
JETSON_HOLE_D      = 3.2;   // mm
JETSON_PLATE_L     = 105;   // mm, plate length
JETSON_PLATE_W     = 90;    // mm, plate width
JETSON_PLATE_T     = 4;     // mm, plate thickness
JETSON_STANDOFF_H  = 8;     // mm

// ── Bumper bracket ────────────────────────────────────────────────────────────
BUMPER_L           = WHEELBASE + 60; // mm, bumper rail length (overhangs wheel CL)
BUMPER_H           = 40;    // mm, bracket vertical height
BUMPER_T           = 5;     // mm, bracket plate thickness
BUMPER_TUBE_OD     = 22;    // mm, 3/4" EMT conduit OD for bumper rail

// ── Rib / gusset parameters ───────────────────────────────────────────────────
RIB_W              = 20;    // mm, longitudinal rib width
RIB_H              = 40;    // mm, rib height below deck
RIB_T              = 4;     // mm, rib plate thickness (laser/router cut)

// ── Fastener helpers ─────────────────────────────────────────────────────────
M3_D    = 3.2;
M4_D    = 4.3;
M5_D    = 5.3;
M6_D    = 6.5;

// =============================================================================
// MAIN ASSEMBLY — comment/uncomment parts as needed
// =============================================================================

color("Silver", 0.9) main_deck();
color("DimGray")     motor_fork(side= 1);   // right (+X)
color("DimGray")     motor_fork(side=-1);   // left  (-X)
color("SteelBlue")   battery_tray();
color("OliveDrab")   fc_mount_plate();
color("DarkOrange")  jetson_mount_plate();
color("Tomato")      bumper_bracket(front= 1);
color("Tomato")      bumper_bracket(front=-1);
color("WhiteSmoke")  longitudinal_ribs();

// =============================================================================
// MODULES
// =============================================================================

// ─── Main deck plate ─────────────────────────────────────────────────────────
module main_deck() {
    difference() {
        // Deck plate
        translate([-WHEELBASE/2 - MOTOR_FORK_T, -FRAME_WIDTH/2, 0])
            cube([WHEELBASE + 2*MOTOR_FORK_T, FRAME_WIDTH, DECK_THICKNESS]);

        // Lightening holes — 3 rows × 4 cols
        for (x = [-WHEELBASE/3, 0, WHEELBASE/3])
        for (y = [-FRAME_WIDTH/4, FRAME_WIDTH/4])
            translate([x, y, -1])
                cylinder(d=50, h=DECK_THICKNESS+2);

        // FC mount holes
        fc_mount_holes(z_offset=-1, depth=DECK_THICKNESS+2);

        // Battery tray floor cutout for wire pass-through
        translate([-BATT_L/2 + 40, -BATT_W/2 + 40, -1])
            cube([BATT_L - 80, BATT_W - 80, DECK_THICKNESS+2]);

        // Cable routing slots
        for (x = [-60, 60])
            translate([x, -FRAME_WIDTH/2 - 1, -1])
                cube([14, 18, DECK_THICKNESS+2]);
    }
}

// ─── Longitudinal ribs (×2, symmetric F/R) ───────────────────────────────────
module longitudinal_ribs() {
    for (y = [-FRAME_WIDTH/2 + RIB_W/2 + WALL_T,
               FRAME_WIDTH/2 - RIB_W/2 - WALL_T])
        translate([-WHEELBASE/2, y - RIB_T/2, -RIB_H])
            cube([WHEELBASE, RIB_T, RIB_H]);
}

// ─── Motor fork dropout bracket ──────────────────────────────────────────────
// Mounts to deck edge; provides a CNC-milled or FDM dropout slot for the axle.
// `side` = +1 (right/+X) or -1 (left/-X)
module motor_fork(side = 1) {
    x_pos = side * (WHEELBASE/2);

    translate([x_pos, 0, 0]) {
        difference() {
            union() {
                // Vertical fork body
                translate([side*(DECK_THICKNESS/2), -MOTOR_FORK_WIDTH/2 - 4, -MOTOR_FORK_H])
                    cube([side * MOTOR_FORK_T, MOTOR_FORK_WIDTH + 8, MOTOR_FORK_H + DECK_THICKNESS]);

                // Gusset triangles
                for (g = [-1, 1])
                    translate([side*(DECK_THICKNESS/2), g*(MOTOR_FORK_WIDTH/2 + 2), -MOTOR_FORK_H])
                        linear_extrude(height=MOTOR_FORK_T)
                            polygon([[0,0],[0, g*20],[side*-30, 0]]);
            }

            // Axle dropout slot (open at bottom)
            translate([side*(DECK_THICKNESS/2) - 1,
                       -MOTOR_AXLE_FLAT/2,
                       -MOTOR_FORK_DEPTH - MOTOR_AXLE_D/2])
                cube([MOTOR_FORK_T + 2, MOTOR_AXLE_FLAT, MOTOR_FORK_DEPTH + 1]);

            // Axle through-hole at slot top
            translate([side*(DECK_THICKNESS/2) - 1, 0,
                       -MOTOR_FORK_DEPTH - MOTOR_AXLE_D/2])
                rotate([0, 90, 0])
                    cylinder(d=MOTOR_AXLE_D + 1, h=MOTOR_FORK_T + 2);

            // Bolt holes for deck attachment (M5 × 4)
            for (y = [-20, 20])
            for (z = [4, 12])
                translate([side*(DECK_THICKNESS/2) - 1, y, z - MOTOR_FORK_H + MOTOR_FORK_H/2])
                    rotate([0, 90, 0])
                        cylinder(d=M5_D, h=MOTOR_FORK_T + 2);
        }
    }
}

// ─── Battery tray ─────────────────────────────────────────────────────────────
// Positioned in centre of deck, recessed 10mm below deck surface
module battery_tray() {
    z_base = -BATT_FLOOR - BATT_H - 5;  // hang below deck
    translate([-BATT_L/2 - BATT_WALL, -BATT_W/2 - BATT_WALL, z_base]) {
        difference() {
            // Outer tray body
            cube([BATT_L + 2*BATT_WALL,
                  BATT_W + 2*BATT_WALL,
                  BATT_H + BATT_FLOOR]);

            // Inner cavity
            translate([BATT_WALL, BATT_WALL, BATT_FLOOR])
                cube([BATT_L, BATT_W, BATT_H + 1]);

            // Strap slots (2× longitudinal)
            for (x = [BATT_L/2 - BATT_STRAP_W*2,
                      BATT_L/2 + BATT_STRAP_W])
                translate([x, -1, BATT_FLOOR + BATT_H/2 - BATT_STRAP_W/2])
                    cube([BATT_STRAP_W, BATT_W + 2*BATT_WALL + 2, BATT_STRAP_W]);

            // Ventilation slots bottom (3×)
            for (i = [0:2])
                translate([BATT_WALL + 20 + i*50, BATT_WALL + 10, -1])
                    cube([30, BATT_W - 20, BATT_FLOOR + 2]);

            // Mount holes to deck (M4 × 4 corners)
            for (x = [10, BATT_L + BATT_WALL - 2])
            for (y = [10, BATT_W + BATT_WALL - 2])
                translate([x, y, BATT_H + BATT_FLOOR - 1])
                    cylinder(d=M4_D, h=BATT_FLOOR + 2);
        }
    }
}

// ─── FC mount holes helper ────────────────────────────────────────────────────
module fc_mount_holes(z_offset=0, depth=10) {
    // ESP32-S3 BALANCE: 30.5×30.5 mm M3 pattern, centred at origin
    for (x = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2])
    for (y = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2])
        translate([x, y, z_offset])
            cylinder(d=FC_MOUNT_HOLE_D, h=depth);
}

// ─── FC mount plate (raised 40mm above deck centre) ──────────────────────────
module fc_mount_plate() {
    fc_x = -50;   // offset toward front from deck centre
    fc_y =  0;
    plate_z = DECK_THICKNESS + FC_STANDOFF_H;

    translate([fc_x, fc_y, 0]) {
        difference() {
            union() {
                // Mount pads with standoffs
                for (x = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2])
                for (y = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2]) {
                    translate([x, y, DECK_THICKNESS])
                        cylinder(d=8, h=FC_STANDOFF_H);  // standoff column
                    translate([x - 5, y - 5, DECK_THICKNESS])
                        cube([10, 10, FC_PAD_T]);          // pad base
                }
                // Base plate
                translate([-FC_MOUNT_SPACING/2 - 8, -FC_MOUNT_SPACING/2 - 8, DECK_THICKNESS])
                    cube([FC_MOUNT_SPACING + 16, FC_MOUNT_SPACING + 16, FC_PAD_T]);
            }
            // M3 through-holes in standoffs
            fc_mount_holes(z_offset=DECK_THICKNESS - 1, depth=FC_STANDOFF_H + FC_PAD_T + 2);

            // Deck anchor holes
            fc_mount_holes(z_offset=-1, depth=DECK_THICKNESS + 2);
        }
    }
}

// ─── Jetson Orin Nano Super B01 mount plate ─────────────────────────────────────────────
// Positioned rear of deck, elevated on standoffs
module jetson_mount_plate() {
    jet_x =  60;   // offset toward rear
    jet_y =  0;
    plate_z = DECK_THICKNESS + JETSON_STANDOFF_H;

    translate([jet_x, jet_y, 0]) {
        difference() {
            union() {
                // Mounting plate
                translate([-JETSON_PLATE_L/2, -JETSON_PLATE_W/2, DECK_THICKNESS + JETSON_STANDOFF_H])
                    cube([JETSON_PLATE_L, JETSON_PLATE_W, JETSON_PLATE_T]);

                // Four standoff columns
                for (x = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2])
                for (y = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2])
                    translate([x, y, DECK_THICKNESS])
                        cylinder(d=6, h=JETSON_STANDOFF_H);
            }

            // B01 M3 hole pattern (58×58 mm)
            for (x = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2])
            for (y = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2])
                translate([x, y, DECK_THICKNESS - 1])
                    cylinder(d=JETSON_HOLE_D, h=JETSON_STANDOFF_H + JETSON_PLATE_T + 2);

            // Ventilation / cable routing slots in plate
            for (i = [-1, 0, 1])
                translate([i*25 - 10, -JETSON_PLATE_W/2 + 10,
                           DECK_THICKNESS + JETSON_STANDOFF_H - 1])
                    cube([20, JETSON_PLATE_W - 20, JETSON_PLATE_T + 2]);

            // Deck anchor holes (M3 × 4 corners of plate)
            for (x = [-JETSON_PLATE_L/2 + 8, JETSON_PLATE_L/2 - 8])
            for (y = [-JETSON_PLATE_W/2 + 8, JETSON_PLATE_W/2 - 8])
                translate([x, y, -1])
                    cylinder(d=M3_D, h=DECK_THICKNESS + 2);
        }
    }
}

// ─── Bumper bracket ───────────────────────────────────────────────────────────
// `front` = +1 (front) or -1 (rear)
module bumper_bracket(front = 1) {
    y_pos = front * (FRAME_WIDTH/2);

    translate([0, y_pos, 0]) {
        difference() {
            union() {
                // Horizontal mounting rail plate
                translate([-BUMPER_L/2, front*(BUMPER_T/2), 0])
                    cube([BUMPER_L, BUMPER_T, BUMPER_H]);

                // Vertical gussets at ends
                for (x = [-BUMPER_L/2 + 5, BUMPER_L/2 - BUMPER_T - 5])
                    translate([x, front*(BUMPER_T), 0])
                        cube([BUMPER_T, 20, BUMPER_H]);

                // Bumper tube saddle clamps (×3 evenly spaced)
                for (x = [-BUMPER_L/3, 0, BUMPER_L/3])
                    translate([x - BUMPER_TUBE_OD/2 - 2,
                               front*(BUMPER_T + 15),
                               BUMPER_H - BUMPER_TUBE_OD/2 - 5])
                    difference() {
                        cube([BUMPER_TUBE_OD + 4, BUMPER_TUBE_OD/2 + 4, BUMPER_TUBE_OD + 4]);
                        translate([BUMPER_TUBE_OD/2 + 2, -1, BUMPER_TUBE_OD/2 + 2])
                            rotate([-90, 0, 0])
                                cylinder(d=BUMPER_TUBE_OD, h=BUMPER_TUBE_OD/2 + 6);
                    }
            }

            // Deck mounting holes (M5 × 6 along length)
            for (x = [-BUMPER_L/2 + 20, -BUMPER_L/6, BUMPER_L/6, BUMPER_L/2 - 20])
                translate([x, y_pos * 0.001, -1])
                    cylinder(d=M5_D, h=BUMPER_H + 2);
        }
    }
}

// =============================================================================
// DIMENSIONS / ANNOTATIONS  (2D cross-section reference)
// =============================================================================
// Uncomment to render a side-view dimension guide:
//
// %translate([0, FRAME_WIDTH/2 + 30, 0]) {
//     color("Blue") {
//         cube([WHEELBASE, 1, 1]);        // wheelbase span
//         translate([0,0,0]) text(str("Wheelbase: ", WHEELBASE, "mm"), size=12);
//     }
// }
