// ============================================================
// gimbal_camera_mount.scad — Pan/Tilt Gimbal Mount for RealSense D435i
// Issue: #552  Agent: sl-mechanical  Date: 2026-03-14
// ============================================================
//
// Parametric gimbal bracket system mounting an Intel RealSense D435i
// (or similar box camera) on a 2-axis pan/tilt gimbal driven by
// ST3215 serial bus servos (25T spline, Feetech/Waveshare).
//
// Architecture:
//   Pan axis  — base T-nut clamps to 2020 rail; pan servo rotates yoke
//   Tilt axis — tilt servo horn plate bolts to ST3215 horn; camera cradle
//               rocks on tilt axis
//   Camera    — D435i captured via 1/4-20 UNC hex nut in cradle floor
//   Damping   — PETG flexure ribs on camera contact faces (or TPU pads)
//   Wiring    — USB-C cable routed through channel in cradle arm
//
// Part catalogue:
//   Part 1 — tnut_rail_base()       2020 rail T-nut base + pan servo seat
//   Part 2 — pan_yoke()             U-yoke connecting pan servo to tilt axis
//   Part 3 — tilt_horn_plate()      Plate bolting to ST3215 tilt servo horn
//   Part 4 — camera_cradle()        D435i cradle with 1/4-20 captured nut
//   Part 5 — vibe_pad()             PETG flexure vibration-damping pad (×2)
//   Part 6 — assembly_preview()     Full assembly preview
//
// Hardware BOM (per gimbal):
//   2× ST3215 serial bus servo (pan + tilt)
//   2× servo horn (25T spline, ≥Ø36 mm, 4× M3 bolt holes on Ø24 mm BC)
//   2× M3 × 8 mm SHCS          horn-to-plate bolts (×4 each horn = 8 total)
//   1× M3 × 16 mm SHCS + nut   T-nut rail clamp thumbscrew
//   1× 1/4-20 UNC × 8 mm SHCS  camera retention bolt (or existing tripod screw)
//   1× 1/4-20 UNC hex nut       captured in cradle floor
//   4× M3 × 12 mm SHCS          yoke-to-tilt-plate pivot axle bolts
//   2× M3 × 25 mm SHCS          pan yoke attachment to servo body
//   (optional) 2× vibe_pad printed in TPU 95A
//
// ST3215 servo interface (caliper-verified Feetech ST3215):
//   Body footprint : 40.0 × 20.0 mm (W × D), 36.5 mm tall
//   Shaft centre H : 28.5 mm from mounting face
//   Shaft spline   : 25T, centre Ø5.8 mm, D-cut
//   Mount holes    : 4× M3 on 32 × 10 mm rectangular pattern (18 mm offset)
//   Horn bolt circle: Ø24 mm, 4× M3
//   Horn OD        : ~36 mm
//
// D435i camera interface (caliper-verified):
//   Body           : 90 × 25 × 25 mm (W × D × H)
//   Tripod thread  : 1/4-20 UNC, centred bottom face, 9 mm from front
//   USB-C connector: right rear, 8 × 5 mm opening, 4 mm from edge
//
// Parametric camera size (override to adapt to other cameras):
//   CAM_W, CAM_D, CAM_H  — body envelope
//   CAM_MOUNT_X           — tripod hole X offset from camera centre
//   CAM_MOUNT_Y           — tripod hole Y offset from front face
//
// Coordinate convention:
//   Camera looks in +Y direction (forward)
//   Pan axis is Z (vertical); tilt axis is X (lateral)
//   Rail runs along Z; T-nut base at Z=0
//   All parts at assembly origin; translate for assembly_preview
//
// RENDER options:
//   "assembly"            full assembly preview (default)
//   "tnut_rail_base_stl"  Part 1
//   "pan_yoke_stl"        Part 2
//   "tilt_horn_plate_stl" Part 3
//   "camera_cradle_stl"   Part 4
//   "vibe_pad_stl"        Part 5
//
// Export commands:
//   openscad gimbal_camera_mount.scad -D 'RENDER="tnut_rail_base_stl"'  -o gcm_tnut_base.stl
//   openscad gimbal_camera_mount.scad -D 'RENDER="pan_yoke_stl"'        -o gcm_pan_yoke.stl
//   openscad gimbal_camera_mount.scad -D 'RENDER="tilt_horn_plate_stl"' -o gcm_tilt_horn_plate.stl
//   openscad gimbal_camera_mount.scad -D 'RENDER="camera_cradle_stl"'   -o gcm_camera_cradle.stl
//   openscad gimbal_camera_mount.scad -D 'RENDER="vibe_pad_stl"'        -o gcm_vibe_pad.stl
// ============================================================

$fn = 64;
e   = 0.01;   // epsilon for boolean clearance

// ── Parametric camera envelope ────────────────────────────────────────────────
// Override these for cameras other than D435i
CAM_W          = 90.0;   // camera body width (X)
CAM_D          = 25.0;   // camera body depth (Y)
CAM_H          = 25.0;   // camera body height (Z)
CAM_MOUNT_X    =  0.0;   // tripod hole X offset from camera body centre
CAM_MOUNT_Y    =  9.0;   // tripod hole from front face (Y)  [D435i: 9 mm]
CAM_USBC_X     = CAM_W/2 - 4;   // USB-C connector X (right side)
CAM_USBC_Z     = CAM_H/2;       // USB-C connector Z (mid-height rear)
CAM_USBC_W     =  9.0;   // USB-C opening width (X)
CAM_USBC_H     =  5.0;   // USB-C opening height (Z)

// ── Rail geometry (matches sensor_rail.scad / sensor_rail_brackets.scad) ─────
RAIL_W         = 20.0;
SLOT_OPEN      =  6.0;
SLOT_INNER_W   = 10.2;
SLOT_INNER_H   =  5.8;
SLOT_NECK_H    =  3.2;

// ── T-nut geometry (matches sensor_rail_brackets.scad) ───────────────────────
TNUT_W         =  9.8;
TNUT_H         =  5.5;
TNUT_L         = 12.0;
TNUT_M3_NUT_AF =  5.5;
TNUT_M3_NUT_H  =  2.5;
TNUT_BOLT_D    =  3.3;   // M3 clearance

// ── T-nut base plate geometry ─────────────────────────────────────────────────
BASE_W         = 44.0;   // wide enough for pan servo body (40 mm)
BASE_H         = 40.0;   // height along rail (Z)
BASE_T         = SLOT_NECK_H + 2.0;   // plate depth (Y), rail-face side

// ── ST3215 servo geometry ─────────────────────────────────────────────────────
SERVO_W        = 40.0;   // servo body width (X)
SERVO_D        = 20.0;   // servo body depth (Y)
SERVO_H        = 36.5;   // servo body height (Z)
SERVO_SHAFT_Z  = 28.5;   // shaft centre height from mounting face
SERVO_HOLE_X   = 16.0;   // mount hole half-span X (32 mm span)
SERVO_HOLE_Y   =  5.0;   // mount hole half-span Y (10 mm span)
SERVO_M3_D     =  3.3;   // M3 clearance

// ── Servo horn geometry ───────────────────────────────────────────────────────
HORN_OD        = 36.0;   // horn outer diameter
HORN_SPLINE_D  =  5.9;   // 25T spline bore clearance (5.8 + 0.1)
HORN_BC_D      = 24.0;   // bolt circle diameter (4× M3)
HORN_BOLT_D    =  3.3;   // M3 clearance through horn plate
HORN_PLATE_T   =  5.0;   // tilt horn plate thickness

// ── Yoke geometry ─────────────────────────────────────────────────────────────
YOKE_WALL_T    =  5.0;   // yoke arm wall thickness
YOKE_ARM_H     = 50.0;   // yoke arm height (Z) — clears servo body + camera
YOKE_INNER_W   = CAM_W + 8.0;  // yoke inner span (camera + pad clearance)
YOKE_BASE_T    =  8.0;   // yoke base plate thickness

// ── Tilt pivot ────────────────────────────────────────────────────────────────
PIVOT_D        =  4.3;   // M4 pivot axle bore
PIVOT_BOSS_D   = 10.0;   // boss OD around pivot bore
PIVOT_BOSS_L   =  6.0;   // boss protrusion from yoke wall

// ── Camera cradle geometry ────────────────────────────────────────────────────
CRADLE_WALL_T  =  4.0;   // cradle side wall thickness
CRADLE_FLOOR_T =  5.0;   // cradle floor thickness (holds 1/4-20 nut)
CRADLE_LIP_T   =  3.0;   // front retaining lip thickness
CRADLE_LIP_H   =  8.0;   // front lip height
CABLE_CH_W     = 12.0;   // USB-C cable channel width
CABLE_CH_H     =  8.0;   // USB-C cable channel height

// ── 1/4-20 UNC tripod thread ──────────────────────────────────────────────────
QTR20_D        =  6.6;   // 1/4-20 clearance bore
QTR20_NUT_AF   = 11.1;   // 1/4-20 hex nut across-flats (standard)
QTR20_NUT_H    =  5.5;   // 1/4-20 hex nut height

// ── Vibration-damping pad geometry ────────────────────────────────────────────
PAD_W          = CAM_W - 2*CRADLE_WALL_T - 2;
PAD_H          = CAM_H + 4;
PAD_T          =  2.5;   // pad body thickness
RIB_H          =  1.5;   // flexure rib height
RIB_W          =  1.2;   // rib width
RIB_PITCH      =  5.0;   // rib pitch

// ── Fastener sizes ────────────────────────────────────────────────────────────
M3_D = 3.3;
M4_D = 4.3;
M3_NUT_AF = 5.5;
M3_NUT_H  = 2.4;

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

if      (RENDER == "assembly")            assembly_preview();
else if (RENDER == "tnut_rail_base_stl")  tnut_rail_base();
else if (RENDER == "pan_yoke_stl")        pan_yoke();
else if (RENDER == "tilt_horn_plate_stl") tilt_horn_plate();
else if (RENDER == "camera_cradle_stl")   camera_cradle();
else if (RENDER == "vibe_pad_stl")        vibe_pad();

// ============================================================
// ASSEMBLY PREVIEW
// ============================================================
module assembly_preview() {
    asm_rail_z = 0;
    // Rail section ghost (200 mm)
    %color("Silver", 0.25)
        translate([-RAIL_W/2, -RAIL_W/2, asm_rail_z])
            cube([RAIL_W, RAIL_W, 200]);

    // T-nut rail base
    color("OliveDrab", 0.85)
        translate([0, 0, asm_rail_z + 80])
            tnut_rail_base();

    // Pan servo ghost (sitting in base seat)
    %color("DimGray", 0.4)
        translate([-SERVO_W/2, BASE_T, asm_rail_z + 80 + (BASE_H - SERVO_H)/2])
            cube([SERVO_W, SERVO_D, SERVO_H]);

    // Pan yoke rising from servo shaft
    color("SteelBlue", 0.85)
        translate([0, BASE_T + SERVO_D, asm_rail_z + 80 + BASE_H/2])
            pan_yoke();

    // Tilt horn plate (tilt axis — left yoke wall)
    color("DarkOrange", 0.85)
        translate([-YOKE_INNER_W/2 - YOKE_WALL_T - HORN_PLATE_T,
                   BASE_T + SERVO_D + YOKE_BASE_T,
                   asm_rail_z + 80 + BASE_H/2 + YOKE_ARM_H/2])
            rotate([0, 90, 0])
                tilt_horn_plate();

    // Camera cradle (centred in yoke)
    color("DarkSlateGray", 0.85)
        translate([0, BASE_T + SERVO_D + YOKE_BASE_T + CRADLE_FLOOR_T,
                   asm_rail_z + 80 + BASE_H/2 + YOKE_ARM_H/2 - CAM_H/2])
            camera_cradle();

    // D435i ghost
    %color("Black", 0.4)
        translate([-CAM_W/2,
                   BASE_T + SERVO_D + YOKE_BASE_T + CRADLE_FLOOR_T + PAD_T,
                   asm_rail_z + 80 + Base_H_mid() - CAM_H/2])
            cube([CAM_W, CAM_D, CAM_H]);

    // Vibe pads (front + rear camera face)
    color("DimGray", 0.80) {
        translate([-CAM_W/2 + CRADLE_WALL_T + 1,
                   Base_T + SERVO_D + YOKE_BASE_T + CRADLE_FLOOR_T,
                   asm_rail_z + 80 + Base_H_mid() - PAD_H/2])
            rotate([90, 0, 0])
                vibe_pad();
    }
}

// helper (avoids recomputing same expression)
function Base_T() = BASE_T;
function Base_H_mid() = BASE_H/2 + YOKE_ARM_H/2;

// ============================================================
// PART 1 — T-NUT RAIL BASE  (pan servo seat + rail clamp)
// ============================================================
// Mounts to 2020 rail via standard T-nut tongue.
// Front face (+Y side) provides flat seat for pan ST3215 servo body.
// Servo body recessed 1 mm into seat for positive lateral registration.
// Pan servo shaft axis = Z (vertical) → pan rotation about Z.
//
// Print: PETG, 5 perims, 50 % gyroid. Orient face-plate down (flat).
module tnut_rail_base() {
    difference() {
        union() {
            // ── Face plate (against rail outer face, -Y side) ────────────
            translate([-BASE_W/2, -BASE_T, 0])
                cube([BASE_W, BASE_T, BASE_H]);

            // ── T-nut neck (enters rail slot, +Y side of face plate) ─────
            translate([-TNUT_W/2, 0, (BASE_H - TNUT_L)/2])
                cube([TNUT_W, SLOT_NECK_H + e, TNUT_L]);

            // ── T-nut inner body (wider, locks inside T-groove) ──────────
            translate([-TNUT_W/2, SLOT_NECK_H - e, (BASE_H - TNUT_L)/2])
                cube([TNUT_W, TNUT_H - SLOT_NECK_H + e, TNUT_L]);

            // ── Pan servo seat boss (front face, +Y side) ────────────────
            // Proud pad that servo body sits on; 1 mm registration recess
            translate([-BASE_W/2, -BASE_T, 0])
                cube([BASE_W, BASE_T + 6, BASE_H]);
        }

        // ── Rail clamp bolt bore (M3 through face plate) ─────────────────
        translate([0, -BASE_T - e, BASE_H/2])
            rotate([-90, 0, 0])
                cylinder(d = TNUT_BOLT_D, h = BASE_T + TNUT_H + 2*e);

        // ── M3 hex nut pocket (inside T-nut body) ────────────────────────
        translate([0, SLOT_NECK_H + 0.3, BASE_H/2])
            rotate([-90, 0, 0])
                cylinder(d = TNUT_M3_NUT_AF / cos(30),
                         h = TNUT_M3_NUT_H + 0.3, $fn = 6);

        // ── Servo body recess (1 mm registration pocket in seat face) ────
        translate([-SERVO_W/2 - 0.3, -BASE_T + 6 - 1.0,
                   (BASE_H - SERVO_H)/2 - 0.3])
            cube([SERVO_W + 0.6, 1.2, SERVO_H + 0.6]);

        // ── Pan servo mount holes (4× M3 in rectangular pattern) ─────────
        for (sx = [-SERVO_HOLE_X, SERVO_HOLE_X])
        for (sy = [-SERVO_HOLE_Y, SERVO_HOLE_Y])
            translate([sx, -BASE_T + 6 + e, BASE_H/2 + sy])
                rotate([90, 0, 0])
                    cylinder(d = SERVO_M3_D, h = BASE_T + 2*e);

        // ── Pan servo shaft bore (passes shaft through base if needed) ────
        // Centre of shaft at Z = BASE_H/2, no bore needed (shaft exits top)

        // ── Lightening pockets ────────────────────────────────────────────
        translate([0, -BASE_T/2 + 3, BASE_H/2])
            cube([BASE_W - 14, BASE_T - 4, BASE_H - 14], center = true);
    }
}

// ============================================================
// PART 2 — PAN YOKE
// ============================================================
// U-shaped yoke that attaches to pan servo horn (below) and carries
// the tilt axis (above).  Two vertical arms straddle the camera cradle.
// Tilt servo sits on top of one arm; tilt pivot boss on the other.
//
// Yoke base bolts to pan servo horn (4× M3 on HORN_BC_D bolt circle).
// Pan servo horn spline bore passes through yoke base centre.
// Tilt axis: M4 pivot axle through boss on each arm (X-axis rotation).
//
// Print: upright (yoke in final orientation), PETG, 5 perims, 40% gyroid.
module pan_yoke() {
    arm_z_total = YOKE_ARM_H + YOKE_BASE_T;
    inner_w     = YOKE_INNER_W;

    difference() {
        union() {
            // ── Yoke base plate (bolts to pan servo horn) ─────────────────
            translate([-inner_w/2 - YOKE_WALL_T, 0, 0])
                cube([inner_w + 2*YOKE_WALL_T, YOKE_BASE_T, YOKE_BASE_T]);

            // ── Left arm ──────────────────────────────────────────────────
            translate([-inner_w/2 - YOKE_WALL_T, 0, 0])
                cube([YOKE_WALL_T, YOKE_BASE_T, arm_z_total]);

            // ── Right arm (tilt servo side) ───────────────────────────────
            translate([inner_w/2, 0, 0])
                cube([YOKE_WALL_T, YOKE_BASE_T, arm_z_total]);

            // ── Tilt pivot bosses (both arms, X-axis) ─────────────────────
            // Left pivot boss (plain pivot — M4 bolt)
            translate([-inner_w/2 - YOKE_WALL_T - PIVOT_BOSS_L,
                       YOKE_BASE_T/2,
                       YOKE_BASE_T + YOKE_ARM_H/2])
                rotate([0, 90, 0])
                    cylinder(d = PIVOT_BOSS_D, h = PIVOT_BOSS_L + YOKE_WALL_T);

            // Right pivot boss (tilt servo horn seat)
            translate([inner_w/2,
                       YOKE_BASE_T/2,
                       YOKE_BASE_T + YOKE_ARM_H/2])
                rotate([0, 90, 0])
                    cylinder(d = PIVOT_BOSS_D + 4, h = PIVOT_BOSS_L + YOKE_WALL_T);

            // ── Tilt servo body seat on right arm top ─────────────────────
            translate([inner_w/2, 0, arm_z_total - SERVO_H - 4])
                cube([YOKE_WALL_T + SERVO_D + 2, YOKE_BASE_T, SERVO_H + 4]);
        }

        // ── Pan horn spline bore (centre of yoke base) ────────────────────
        translate([0, YOKE_BASE_T/2, YOKE_BASE_T/2])
            rotate([90, 0, 0])
                cylinder(d = HORN_SPLINE_D, h = YOKE_BASE_T + 2*e,
                         center = true);

        // ── Pan horn bolt holes (4× M3 on HORN_BC_D) ─────────────────────
        for (a = [45, 135, 225, 315])
            translate([HORN_BC_D/2 * cos(a),
                       YOKE_BASE_T/2,
                       HORN_BC_D/2 * sin(a) + YOKE_BASE_T/2])
                rotate([90, 0, 0])
                    cylinder(d = HORN_BOLT_D, h = YOKE_BASE_T + 2*e,
                             center = true);

        // ── Left tilt pivot bore (M4 clearance) ───────────────────────────
        translate([-inner_w/2 - YOKE_WALL_T - PIVOT_BOSS_L - e,
                   YOKE_BASE_T/2,
                   YOKE_BASE_T + YOKE_ARM_H/2])
            rotate([0, 90, 0])
                cylinder(d = PIVOT_D, h = PIVOT_BOSS_L + YOKE_WALL_T + 2*e);

        // ── Right tilt pivot bore (larger — tilt horn plate seats here) ───
        translate([inner_w/2 - e,
                   YOKE_BASE_T/2,
                   YOKE_BASE_T + YOKE_ARM_H/2])
            rotate([0, 90, 0])
                cylinder(d = HORN_SPLINE_D,
                         h = PIVOT_BOSS_L + YOKE_WALL_T + 2*e);

        // ── Tilt servo mount holes in right arm seat ──────────────────────
        for (sz = [-SERVO_HOLE_Y, SERVO_HOLE_Y])
            translate([inner_w/2 + YOKE_WALL_T + SERVO_D/2,
                       YOKE_BASE_T/2,
                       arm_z_total - SERVO_H/2 + sz])
                rotate([90, 0, 0])
                    cylinder(d = SERVO_M3_D, h = YOKE_BASE_T + 2*e,
                             center = true);

        // ── M3 nut pockets (tilt servo mount, rear of arm seat) ──────────
        for (sz = [-SERVO_HOLE_Y, SERVO_HOLE_Y])
            translate([inner_w/2 + YOKE_WALL_T + SERVO_D/2,
                       YOKE_BASE_T - M3_NUT_H - 0.5,
                       arm_z_total - SERVO_H/2 + sz])
                rotate([90, 0, 0])
                    cylinder(d = M3_NUT_AF / cos(30), h = M3_NUT_H + 0.5,
                             $fn = 6);

        // ── Lightening slots in yoke arms ─────────────────────────────────
        translate([-inner_w/2 - YOKE_WALL_T/2,
                   YOKE_BASE_T/2,
                   YOKE_BASE_T + YOKE_ARM_H/2 - 10])
            cube([YOKE_WALL_T - 2, YOKE_BASE_T - 2, YOKE_ARM_H - 24],
                 center = true);
        translate([inner_w/2 + YOKE_WALL_T/2,
                   YOKE_BASE_T/2,
                   YOKE_BASE_T + YOKE_ARM_H/2 - 10])
            cube([YOKE_WALL_T - 2, YOKE_BASE_T - 2, YOKE_ARM_H - 30],
                 center = true);
    }
}

// ============================================================
// PART 3 — TILT HORN PLATE
// ============================================================
// Disc plate bolting to tilt ST3215 servo horn on the right yoke arm.
// Servo horn spline centres into disc bore (captured, no free rotation).
// Camera cradle attaches to opposite face via 2× M3 bolts.
//
// Tilt range: ±45° limited by yoke arm geometry.
// Plate thickness HORN_PLATE_T provides stiffness for cantilevered cradle.
//
// Print: flat (disc face down), PETG, 5 perims, 50 % infill.
module tilt_horn_plate() {
    plate_od = HORN_OD + 8;   // plate OD (4 mm rim outside horn BC)

    difference() {
        union() {
            // ── Main disc ─────────────────────────────────────────────────
            cylinder(d = plate_od, h = HORN_PLATE_T);

            // ── Cradle attachment arm (extends to camera cradle) ──────────
            // Rectangular boss on top of disc toward camera
            translate([-CAM_W/2, HORN_PLATE_T - e, -CAM_H/2])
                cube([CAM_W, HORN_PLATE_T + 4, CAM_H]);
        }

        // ── Servo horn spline bore (centre) ───────────────────────────────
        translate([0, 0, -e])
            cylinder(d = HORN_SPLINE_D, h = HORN_PLATE_T + 2*e);

        // ── Horn bolt holes (4× M3 on HORN_BC_D) ─────────────────────────
        for (a = [45, 135, 225, 315])
            translate([HORN_BC_D/2 * cos(a),
                       HORN_BC_D/2 * sin(a), -e])
                cylinder(d = HORN_BOLT_D, h = HORN_PLATE_T + 2*e);

        // ── Pivot axle bore (M4, coaxial with horn centre) ────────────────
        translate([0, 0, -e])
            cylinder(d = PIVOT_D, h = HORN_PLATE_T + 2*e);

        // ── Cradle attachment bolts (2× M3 in arm boss) ──────────────────
        for (cz = [-CAM_H/2 + 6, CAM_H/2 - 6])
            translate([0, HORN_PLATE_T + 2, cz])
                rotate([90, 0, 0])
                    cylinder(d = M3_D, h = HORN_PLATE_T + 6 + 2*e);

        // ── M3 hex nut pockets (rear of disc face) ────────────────────────
        for (cz = [-CAM_H/2 + 6, CAM_H/2 - 6])
            translate([0, M3_NUT_H + 0.5, cz])
                rotate([90, 0, 0])
                    cylinder(d = M3_NUT_AF / cos(30),
                             h = M3_NUT_H + 0.5, $fn = 6);

        // ── Weight-relief arcs (between horn bolt holes) ──────────────────
        for (a = [0, 90, 180, 270])
            translate([(plate_od/2 - 5) * cos(a),
                       (plate_od/2 - 5) * sin(a), -e])
                cylinder(d = 6, h = HORN_PLATE_T + 2*e);
    }
}

// ============================================================
// PART 4 — CAMERA CRADLE
// ============================================================
// Open-front U-cradle holding D435i via captured 1/4-20 hex nut.
// Front lip retains camera from sliding forward (+Y).
// Vibration-damping pads seat in recessed pockets on inner faces.
// USB-C cable routing channel exits cradle right rear wall.
//
// 1/4-20 captured nut in cradle floor — tighten with standard
// tripod screw or M6→1/4-20 adapter from camera bottom.
//
// Print: cradle-floor-down (flat), PETG, 5 perims, 40 % gyroid.
// No supports needed (overhangs < 45°).
module camera_cradle() {
    outer_w = CAM_W + 2*CRADLE_WALL_T;
    outer_h = CAM_H + CRADLE_FLOOR_T;

    difference() {
        union() {
            // ── Cradle body ───────────────────────────────────────────────
            translate([-outer_w/2, 0, 0])
                cube([outer_w, CAM_D + CRADLE_WALL_T, outer_h]);

            // ── Front retaining lip ───────────────────────────────────────
            translate([-outer_w/2, CAM_D + CRADLE_WALL_T - CRADLE_LIP_T, 0])
                cube([outer_w, CRADLE_LIP_T, CRADLE_LIP_H]);

            // ── Cable channel boss (right rear, exits +X side) ────────────
            translate([CAM_W/2 + CRADLE_WALL_T - e,
                       0,
                       CRADLE_FLOOR_T + CAM_H/2 - CABLE_CH_H/2])
                cube([CABLE_CH_W + CRADLE_WALL_T, CAM_D * 0.6, CABLE_CH_H]);

            // ── Tilt horn attachment tabs (left + right, bolt to horn plate)─
            for (sx = [-outer_w/2 - 4, outer_w/2])
                translate([sx, CAM_D/2, CRADLE_FLOOR_T + CAM_H/2 - 6])
                    cube([4, 12, 12]);
        }

        // ── Camera pocket (hollow interior) ──────────────────────────────
        translate([-CAM_W/2, 0, CRADLE_FLOOR_T])
            cube([CAM_W, CAM_D + CRADLE_WALL_T + e, CAM_H + e]);

        // ── 1/4-20 UNC clearance bore (camera tripod thread, bottom) ─────
        translate([CAM_MOUNT_X, CAM_MOUNT_Y, -e])
            cylinder(d = QTR20_D, h = CRADLE_FLOOR_T + 2*e);

        // ── 1/4-20 hex nut pocket (captured in cradle floor) ─────────────
        translate([CAM_MOUNT_X, CAM_MOUNT_Y, CRADLE_FLOOR_T - QTR20_NUT_H - 0.5])
            cylinder(d = QTR20_NUT_AF / cos(30),
                     h = QTR20_NUT_H + 0.6, $fn = 6);

        // ── USB-C cable channel (exit through right rear wall) ────────────
        translate([CAM_W/2 - e,
                   0,
                   CRADLE_FLOOR_T + CAM_H/2 - CABLE_CH_H/2])
            cube([CABLE_CH_W + CRADLE_WALL_T + 2*e,
                  CAM_D * 0.6 + e, CABLE_CH_H]);

        // ── Vibe pad recesses on inner camera-contact faces ───────────────
        // Rear wall recess (camera front face → +Y side of rear wall)
        translate([-CAM_W/2 + CRADLE_WALL_T, CRADLE_WALL_T, CRADLE_FLOOR_T])
            cube([CAM_W, PAD_T, CAM_H]);

        // ── Tilt horn bolt holes in attachment tabs ───────────────────────
        for (sx = [-outer_w/2 - 4 - e, outer_w/2 - e])
            translate([sx, CAM_D/2 + 6, CRADLE_FLOOR_T + CAM_H/2])
                rotate([0, 90, 0])
                    cylinder(d = M3_D, h = 6 + 2*e);

        // ── M3 nut pockets in attachment tabs ─────────────────────────────
        translate([outer_w/2 + 4 - M3_NUT_H - 0.4,
                   CAM_D/2 + 6,
                   CRADLE_FLOOR_T + CAM_H/2])
            rotate([0, 90, 0])
                cylinder(d = M3_NUT_AF / cos(30),
                         h = M3_NUT_H + 0.4, $fn = 6);
        translate([-outer_w/2 - 4 - e,
                   CAM_D/2 + 6,
                   CRADLE_FLOOR_T + CAM_H/2])
            rotate([0, 90, 0])
                cylinder(d = M3_NUT_AF / cos(30),
                         h = M3_NUT_H + 0.4, $fn = 6);

        // ── Lightening pockets in cradle walls ────────────────────────────
        for (face_x = [-CAM_W/2 - CRADLE_WALL_T - e, CAM_W/2 - e])
            translate([face_x, CAM_D * 0.2, CRADLE_FLOOR_T + 3])
                cube([CRADLE_WALL_T + 2*e, CAM_D * 0.55, CAM_H - 6]);
    }
}

// ============================================================
// PART 5 — VIBRATION-DAMPING PAD
// ============================================================
// Flat pad with transverse PETG flexure ribs pressing against camera body.
// Rib geometry (thin fins ~1.5 mm tall) deflects under camera vibration,
// attenuating high-frequency input from motor/drive-train.
// For superior damping: print in TPU 95A (no infill changes needed).
// Pads seat in recessed pockets in camera cradle inner wall.
// Optional M2 bolt-through at corners or adhesive-back foam tape.
//
// Print: pad-back-face-down, PETG or TPU 95A, 3 perims, 20 % infill.
module vibe_pad() {
    rib_count = floor((PAD_W - RIB_W) / RIB_PITCH);

    union() {
        // ── Base plate ────────────────────────────────────────────────────
        translate([-PAD_W/2, -PAD_T, -PAD_H/2])
            cube([PAD_W, PAD_T, PAD_H]);

        // ── Flexure ribs (parallel to Z, spaced RIB_PITCH apart) ─────────
        for (i = [0 : rib_count - 1]) {
            rx = -PAD_W/2 + RIB_PITCH/2 + i * RIB_PITCH + RIB_W/2;
            if (rx <= PAD_W/2 - RIB_W/2)
                translate([rx, 0, 0])
                    cube([RIB_W, RIB_H, PAD_H - 6], center = true);
        }

        // ── Corner nubs (M2 bolt-through retention, optional) ─────────────
        for (px = [-PAD_W/2 + 5, PAD_W/2 - 5])
        for (pz = [-PAD_H/2 + 5, PAD_H/2 - 5])
            translate([px, -PAD_T/2, pz])
                difference() {
                    cylinder(d = 5, h = PAD_T, center = true);
                    cylinder(d = 2.4, h = PAD_T + 2*e, center = true);
                }
    }
}
