// ============================================================
// base_bumper.scad — Base Plate Impact Bumper  Rev A  2026-03-01
// Agent: sl-mechanical
// ============================================================
// Clip-on bumper system for the 270×240 mm SaltyBot base plate.
// Protects wheels, FC board, and wiring when robot tips.
//
// System overview:
//   corner_bracket()     — L-clip, grips plate corner, print 4×
//   side_bracket()       — straight clip for mid-sides, print 4×
//   tpu_edge_cap()       — soft snap-on edge cap, print in TPU
//                          (same profile fits corner and side)
//   bumper_rail_2d()     — 2-D cross-section DXF for straight rails
//
// Assembly: 4 corner brackets + 4 side brackets clip onto the
// plate bottom edge without drilling.  Rails (printed or cut from
// 25×8 mm aluminium bar) bridge between clips.  TPU edge caps
// snap onto rail outer face.
//
// FC status LEDs: FC is front-centre of plate.  Bumper is below
// and outside the plate footprint — LEDs remain visible.
//
// RENDER options:
//   "assembly"      3-D view with phantom plate (default)
//   "corner"        one corner bracket (print 4×)
//   "side"          one side bracket (print 4×)
//   "tpu_cap"       TPU edge cap cross-section extruded (print N×)
//   "rail_2d"       flat rail profile → DXF
// ============================================================

RENDER = "assembly";

// ── Base plate (reference only — not printed) ─────────────────
PLATE_W     = 270.0;         // width (motor axle direction, X)
PLATE_D     = 240.0;         // depth (fore-aft, Y)
PLATE_H     =   6.0;         // plate thickness
PLATE_R     =  10.0;         // plate corner radius

// ── Bumper geometry ──────────────────────────────────────────
BUMP_H      =  50.0;         // bumper height below plate bottom face
BUMP_T      =   8.0;         // rail/bracket wall thickness
BUMP_CR     =  22.0;         // outer corner radius of bumper frame
BUMP_EXTEND =  15.0;         // how far bumper extends beyond plate edge
                             // (absorbs impact before plate corner hits)

// ── Clip channel (grips plate edge) ──────────────────────────
CLIP_SLOT_W =  PLATE_H + 0.6;  // slot width (6 mm plate + 0.6 clearance)
CLIP_SLOT_D =  12.0;         // how deep clip grips plate edge
CLIP_GRIP_T =   4.0;         // clip wall above plate (keeps clip aligned)

// ── Corner bracket ────────────────────────────────────────────
CORNER_ARM  =  45.0;         // length of each L-arm along plate edge
CORNER_H    =  BUMP_H + CLIP_GRIP_T + CLIP_SLOT_W;

// ── Side bracket ─────────────────────────────────────────────
SIDE_ARM    =  30.0;         // side bracket total width along plate edge
SIDE_H      =  CORNER_H;

// ── Rail (reference dims for procurement / DXF) ───────────────
RAIL_W      =  BUMP_T;       // 8 mm
RAIL_H      =  BUMP_H;       // 50 mm

// ── TPU edge cap ─────────────────────────────────────────────
CAP_W       =  BUMP_T + 4.0; // wraps around rail outer face
CAP_H       =  14.0;         // cap height (bottom of bumper profile)
CAP_R       =   8.0;         // outer impact radius
CAP_CLIP_W  =  BUMP_T + 0.6; // inner clip width (snaps on rail)
CAP_CLIP_D  =   5.0;         // clip depth

// ── Fasteners ─────────────────────────────────────────────────
M4_D        =  4.5;          // M4 clearance hole (rail → bracket)
M3_D        =  3.3;          // M3 set screw (lock bracket on plate)

$fn = 48;
e   = 0.01;

// ─────────────────────────────────────────────────────────────
// clip_slot_2d()
//   2-D profile of the plate-edge gripping channel.
//   Origin at plate bottom face, +Y toward plate interior.
// ─────────────────────────────────────────────────────────────
module clip_slot_2d() {
    // Slot opens from outside (-Y), grips plate edge
    translate([0, -(BUMP_EXTEND + BUMP_T)])
        square([CLIP_SLOT_W, CLIP_SLOT_D + BUMP_EXTEND + BUMP_T]);
}

// ─────────────────────────────────────────────────────────────
// corner_bracket()
//   L-shaped clip bracket.  Internal coordinate:
//     Corner at origin, arms along +X and +Y.
//     Plate sits at Z = BUMP_H (above bracket base).
// ─────────────────────────────────────────────────────────────
module corner_bracket() {
    arm_w = BUMP_T;           // wall thickness
    arm_h = CORNER_H;         // full height

    difference() {
        union() {
            // X arm (along plate width edge)
            translate([-BUMP_EXTEND, -BUMP_EXTEND, 0])
                hull() {
                    cube([CORNER_ARM + BUMP_EXTEND, arm_w, arm_h]);
                    translate([0, arm_w, 0])
                        cylinder(r = BUMP_CR, h = arm_h);
                    translate([CORNER_ARM + BUMP_EXTEND, arm_w, 0])
                        cylinder(r = BUMP_CR, h = arm_h);
                }

            // Y arm (along plate depth edge)
            translate([-BUMP_EXTEND, -BUMP_EXTEND, 0])
                hull() {
                    cube([arm_w, CORNER_ARM + BUMP_EXTEND, arm_h]);
                    translate([arm_w, 0, 0])
                        cylinder(r = BUMP_CR, h = arm_h);
                    translate([arm_w, CORNER_ARM + BUMP_EXTEND, 0])
                        cylinder(r = BUMP_CR, h = arm_h);
                }
        }

        // Clip slot in X arm (grips plate edge)
        // Slot at Z = BUMP_H (plate bottom face height)
        translate([CORNER_ARM - CLIP_SLOT_D, -(BUMP_EXTEND + arm_w + e), BUMP_H])
            cube([CLIP_SLOT_D + BUMP_EXTEND + e,
                  arm_w + 2*e, CLIP_SLOT_W]);

        // Clip slot in Y arm
        translate([-(BUMP_EXTEND + arm_w + e), CORNER_ARM - CLIP_SLOT_D, BUMP_H])
            cube([arm_w + 2*e,
                  CLIP_SLOT_D + BUMP_EXTEND + e, CLIP_SLOT_W]);

        // M3 set screw through clip above slot (X arm, 2 per arm)
        for (xoff = [CORNER_ARM * 0.3, CORNER_ARM * 0.7]) {
            translate([xoff - BUMP_EXTEND, -(BUMP_EXTEND + arm_w/2), BUMP_H + CLIP_SLOT_W + CLIP_GRIP_T/2])
                rotate([90, 0, 0])
                    cylinder(d = M3_D, h = arm_w + 2*e);
        }
        for (yoff = [CORNER_ARM * 0.3, CORNER_ARM * 0.7]) {
            translate([-(BUMP_EXTEND + arm_w/2), yoff - BUMP_EXTEND, BUMP_H + CLIP_SLOT_W + CLIP_GRIP_T/2])
                rotate([0, 90, 0])
                    cylinder(d = M3_D, h = arm_w + 2*e);
        }

        // M4 holes for rail attachment (outer face, pairs per arm)
        for (xoff = [15, CORNER_ARM - 10]) {
            translate([xoff - BUMP_EXTEND, -(BUMP_EXTEND + arm_w + e), BUMP_H * 0.3])
                rotate([90, 0, 0])
                    cylinder(d = M4_D, h = arm_w + 2*e);
            translate([xoff - BUMP_EXTEND, -(BUMP_EXTEND + arm_w + e), BUMP_H * 0.7])
                rotate([90, 0, 0])
                    cylinder(d = M4_D, h = arm_w + 2*e);
        }
    }
}

// ─────────────────────────────────────────────────────────────
// side_bracket()
//   Straight clip for mid-side.  Grips plate edge, M4 holes
//   for rail, M3 set screws.  Placed between corner brackets.
// ─────────────────────────────────────────────────────────────
module side_bracket() {
    arm_w = BUMP_T;
    arm_h = SIDE_H;

    difference() {
        // Outer body
        translate([-SIDE_ARM/2, -(BUMP_EXTEND + arm_w), 0])
            hull() {
                cube([SIDE_ARM, arm_w, arm_h]);
                translate([0, arm_w, 0])
                    cylinder(r = BUMP_CR * 0.6, h = arm_h);
                translate([SIDE_ARM, arm_w, 0])
                    cylinder(r = BUMP_CR * 0.6, h = arm_h);
            }

        // Clip slot
        translate([-SIDE_ARM/2 + 5, -(BUMP_EXTEND + arm_w + e), BUMP_H])
            cube([SIDE_ARM - 10, arm_w + 2*e, CLIP_SLOT_W]);

        // M3 set screws (2×)
        for (xoff = [-SIDE_ARM/4, SIDE_ARM/4]) {
            translate([xoff, -(BUMP_EXTEND + arm_w/2), BUMP_H + CLIP_SLOT_W + CLIP_GRIP_T/2])
                rotate([90, 0, 0])
                    cylinder(d = M3_D, h = arm_w + 2*e);
        }

        // M4 rail holes (2 heights × 1 column)
        for (zh = [BUMP_H * 0.3, BUMP_H * 0.7]) {
            translate([0, -(BUMP_EXTEND + arm_w + e), zh])
                rotate([90, 0, 0])
                    cylinder(d = M4_D, h = arm_w + 2*e);
        }
    }
}

// ─────────────────────────────────────────────────────────────
// tpu_edge_cap()
//   Snap-on soft edge cap — print in TPU.
//   Snaps onto outer face of rail (or corner bracket outer face).
//   Orient: cap opening faces -Y (toward bracket), print flat.
// ─────────────────────────────────────────────────────────────
module tpu_edge_cap() {
    // Length — 40 mm segment; print multiple, install end-to-end
    cap_len = 40.0;

    difference() {
        union() {
            // Rounded outer impact face
            hull() {
                translate([-CAP_W/2, 0, 0])
                    cylinder(r = CAP_R, h = cap_len);
                translate([ CAP_W/2, 0, 0])
                    cylinder(r = CAP_R, h = cap_len);
                translate([-CAP_W/2, CAP_H - CAP_R, 0])
                    cylinder(r = CAP_R, h = cap_len);
                translate([ CAP_W/2, CAP_H - CAP_R, 0])
                    cylinder(r = CAP_R, h = cap_len);
            }
        }

        // Inner clip slot (snaps onto rail outer face)
        translate([-CAP_CLIP_W/2, -e, -e])
            cube([CAP_CLIP_W, CAP_CLIP_D + e, cap_len + 2*e]);
    }
}

// ─────────────────────────────────────────────────────────────
// bumper_rail_2d()
//   2-D profile for straight bumper rail sections.
//   Extrude to length per side: front/rear = PLATE_W, sides = PLATE_D.
//   Use "rail_2d" RENDER to export DXF.
// ─────────────────────────────────────────────────────────────
module bumper_rail_2d() {
    // Simple rectangular profile 8 × 50 mm
    square([RAIL_W, RAIL_H]);
}

// ─────────────────────────────────────────────────────────────
// Phantom plate (reference, assembly view only)
// ─────────────────────────────────────────────────────────────
module phantom_plate() {
    color("Gray", 0.25)
        linear_extrude(PLATE_H)
            offset(r = PLATE_R, $fn = 32)
                offset(-PLATE_R)
                    square([PLATE_W, PLATE_D], center = true);
}

// ─────────────────────────────────────────────────────────────
// Render selector
// ─────────────────────────────────────────────────────────────
if (RENDER == "assembly") {
    // Phantom plate
    translate([0, 0, BUMP_H]) phantom_plate();

    // 4 corner brackets (NW / NE / SW / SE)
    half_w = PLATE_W/2;
    half_d = PLATE_D/2;
    color("SteelBlue", 0.9) {
        // NW corner (X−, Y+) → rotate 90°
        translate([-half_w, half_d, 0])  rotate([0,0,180]) corner_bracket();
        // NE corner (X+, Y+) → rotate 90°
        translate([ half_w, half_d, 0])  rotate([0,0,270]) corner_bracket();
        // SW corner (X−, Y−) → no rotation
        translate([-half_w,-half_d, 0])  rotate([0,0, 90]) corner_bracket();
        // SE corner (X+, Y−)
        translate([ half_w,-half_d, 0])  corner_bracket();
    }

    // 4 side brackets (front, rear, left, right midpoints)
    color("CornflowerBlue", 0.9) {
        translate([0,  half_d, 0]) rotate([0,0,180]) side_bracket();  // front
        translate([0, -half_d, 0])                   side_bracket();  // rear
        translate([-half_w, 0, 0]) rotate([0,0, 90]) side_bracket();  // left
        translate([ half_w, 0, 0]) rotate([0,0,270]) side_bracket();  // right
    }

} else if (RENDER == "corner") {
    corner_bracket();

} else if (RENDER == "side") {
    side_bracket();

} else if (RENDER == "tpu_cap") {
    tpu_edge_cap();

} else if (RENDER == "rail_2d") {
    projection(cut = false)
        linear_extrude(1)
            bumper_rail_2d();
}
