// =============================================================================
// SaltyBot — Cable Management Clips
// Agent: sl-mechanical  |  2026-03-02
//
// MODULAR SNAP-ON CABLE CLIPS with integrated adhesive base and zip-tie anchors.
// Designed to organize power cables, sensor bundles, and signal harnesses on the
// chassis. Each clip accommodates a range of cable bundle diameters via elastic
// snap jaws.
//
// HOW IT WORKS
//   1. Adhesive base (3M VHB or equivalent) adheres to chassis surface.
//   2. Cable bundle pressed upward through snap jaws until it seats with audible click.
//   3. Overhanging jaw tabs provide two zip-tie anchor points (one per side).
//   4. Vertical ear holes accept M3 threaded inserts for wire or strap attachment.
//
// CLIP FAMILY
//   • Clip 5mm:   Holds 4–6 mm bundles  (small signal cables)
//   • Clip 8mm:   Holds 6–10 mm bundles  (mixed power + signal)
//   • Clip 12mm:  Holds 10–14 mm bundles  (heavy power)
//
// PARTS (set RENDER= to export each)
//   clip_5mm      — 3D print × N   (RENDER="clip_5mm")
//   clip_8mm      — 3D print × N   (RENDER="clip_8mm")
//   clip_12mm     — 3D print × N   (RENDER="clip_12mm")
//   assembly_all  — Full preview with ghosts  (RENDER="assembly_all")
//
// MATERIALS
//   • Body: PETG or ASA (weatherproof, adhesive-friendly)
//   • Adhesive: 3M VHB 5952F (50 mm × 75 mm pads, rated 5 N/cm²)
//   • Anchors: M3 threaded inserts (optional, for high-load retention)
//   • Zip-ties: Standard nylon 3.6 mm × 150 mm (e.g., HellermannTyton)
//
// INSTALLATION
//   1. Clean chassis surface with isopropyl alcohol; let dry.
//   2. Peel 3M VHB backing; press clip firmly (30 s hold).
//   3. Wait 24 hours for full adhesive cure.
//   4. Press cable bundle upward through snap jaws until seated.
//   5. Route zip-ties through jaw anchor points; cinch at desired tension.
//   6. Optionally thread M3 bolts through ear holes for redundant retention.
// =============================================================================

$fn = 64;

// =============================================================================
// CLIP GEOMETRY — COMMON PARAMETERS
// =============================================================================

// SNAP JAW PROFILE (all clips share same jaw geometry)
JAW_THICKNESS   = 2.0;   // mm  thickness of snap arms (thin = flexible)
JAW_BEND_R      = 0.8;   // mm  radius at jaw root (stress relief)
JAW_CLOSURE     = 0.3;   // mm  interference fit depth when snapped closed
SNAP_TRAVEL     = 1.2;   // mm  vertical distance cable travels before seat
SNAP_REST_GAP   = 0.2;   // mm  gap when unloaded (keeps jaws sprung apart)

// ADHESIVE BASE
BASE_LENGTH     = 50.0;  // mm  forward-back footprint
BASE_WIDTH      = 40.0;  // mm  left-right footprint
BASE_THICKNESS  = 2.5;   // mm  base pad thickness
BASE_FILLET     = 2.0;   // mm  corner rounding (aids adhesive contact)

// ZIP-TIE ANCHOR FEATURES
ANCHOR_TAB_H    = 5.0;   // mm  height of jaw-tip anchor tab
ANCHOR_TAB_T    = 1.5;   // mm  thickness of anchor tab
ANCHOR_SLOT_W   = 4.0;   // mm  width of zip-tie slot (3.6 mm ties + 0.4 mm clearance)
ANCHOR_SLOT_H   = 1.0;   // mm  height of slot throat

// WIRE/STRAP ATTACHMENT EARS
EAR_D           = 4.2;   // mm  hole diameter (M3 clearance, 2.6 mm nominal)
EAR_WALL_T      = 3.0;   // mm  wall thickness around hole
EAR_H           = 8.0;   // mm  ear protrusion height from base

// =============================================================================
// CLIP SIZE VARIANTS
// =============================================================================

// For each clip size, define:
//   CABLE_D      — nominal cable bundle diameter
//   JAW_SPAN     — inner span of closed jaws (CABLE_D + JAW_CLOSURE)
//   CLIP_HEIGHT  — overall height of clip body
//   CLAMP_X      — width of clamp section (controls jaw lever arm)

CLIP_PARAMS = [
    // [name,      cable_d, jaw_span, height, clamp_x]
    ["5mm",        5.0,     5.3,      28.0,   18.0],
    ["8mm",        8.0,     8.3,      35.0,   22.0],
    ["12mm",      12.0,    12.3,      42.0,   26.0],
];

// =============================================================================
// RENDER CONTROL
// =============================================================================

// "assembly_all"  — all clips in array, with base ghosts
// "clip_5mm"      — single 5mm clip (ready to export STL)
// "clip_8mm"      — single 8mm clip
// "clip_12mm"     — single 12mm clip

RENDER = "assembly_all";

// Helper to fetch clip parameters by name
function get_clip_params(name) =
    (name == "5mm") ? CLIP_PARAMS[0] :
    (name == "8mm") ? CLIP_PARAMS[1] :
    (name == "12mm") ? CLIP_PARAMS[2] :
    CLIP_PARAMS[0];

// =============================================================================
// MAIN RENDER DISPATCH
// =============================================================================

if (RENDER == "assembly_all") {
    assembly_all();
} else if (RENDER == "clip_5mm") {
    clip_body(get_clip_params("5mm"));
} else if (RENDER == "clip_8mm") {
    clip_body(get_clip_params("8mm"));
} else if (RENDER == "clip_12mm") {
    clip_body(get_clip_params("12mm"));
}

// =============================================================================
// ASSEMBLY VIEW (all clips in a row, with adhesive pads ghosted)
// =============================================================================

module assembly_all() {
    for (i = [0 : len(CLIP_PARAMS) - 1]) {
        p = CLIP_PARAMS[i];
        x_offset = i * 70;  // 70 mm spacing

        translate([x_offset, 0, 0]) {
            // Clip body
            color("DodgerBlue", 0.92)
                clip_body(p);

            // Adhesive base ghost (3M VHB pad)
            %color("LimeGreen", 0.40)
                translate([0, 0, -BASE_THICKNESS])
                    rounded_rect([50, 40, 0.2], BASE_FILLET);

            // Label
            echo(str("Clip ", p[0], " — Cable dia. ", p[1], " mm"));
        }
    }
}

// =============================================================================
// CLIP BODY MODULE (parametric across all sizes)
// =============================================================================
//
// Structure:
//   • Base: rounded rectangle, adhesive-mounting surface
//   • Spine: central vertical structure, extends from base
//   • Jaws: two snap arms extending upward/outward from spine
//   • Ears: two lateral holes for M3 attachment (optional)
//   • Anchors: small tabs on jaw tips for zip-tie routing
//

module clip_body(params) {
    name        = params[0];
    cable_d     = params[1];
    jaw_span    = params[2];
    clip_h      = params[3];
    clamp_x     = params[4];

    spine_thick = 3.5;    // mm  thickness of central spine
    jaw_l       = clip_h - BASE_THICKNESS;  // jaw arm length
    jaw_root_x  = clamp_x / 2;  // X position where jaw originates from spine

    difference() {
        union() {
            // ── ADHESIVE BASE ──────────────────────────────────────────────
            translate([0, 0, -BASE_THICKNESS/2])
                rounded_rect([BASE_LENGTH, BASE_WIDTH, BASE_THICKNESS], BASE_FILLET);

            // ── CENTRAL SPINE (support structure) ───────────────────────────
            translate([-spine_thick/2, -clamp_x/2, 0])
                cube([spine_thick, clamp_x, BASE_THICKNESS + jaw_l]);

            // ── LEFT JAW (snap arm with flexible root) ──────────────────────
            jaw_body(-jaw_root_x, jaw_l, jaw_span);

            // ── RIGHT JAW (snap arm, mirror) ───────────────────────────────
            jaw_body(jaw_root_x, jaw_l, jaw_span);

            // ── LEFT EAR (M3 attachment hole) ──────────────────────────────
            translate([-clamp_x/2 - EAR_WALL_T - EAR_D/2, 0, BASE_THICKNESS])
                ear_boss();

            // ── RIGHT EAR ──────────────────────────────────────────────────
            translate([clamp_x/2 + EAR_WALL_T + EAR_D/2, 0, BASE_THICKNESS])
                ear_boss();
        }

        // ── SUBTRACT: Anchor slot hollows (zip-tie slots in jaw tips) ──────
        jaw_root_z = BASE_THICKNESS + jaw_l - ANCHOR_TAB_H;

        // Left jaw anchor slot
        translate([-jaw_root_x - 2, -ANCHOR_SLOT_W/2, jaw_root_z])
            cube([3, ANCHOR_SLOT_W, ANCHOR_SLOT_H]);

        // Right jaw anchor slot
        translate([jaw_root_x - 1, -ANCHOR_SLOT_W/2, jaw_root_z])
            cube([3, ANCHOR_SLOT_W, ANCHOR_SLOT_H]);

        // ── SUBTRACT: Ear attachment holes (M3 clearance) ────────────────
        // Left ear hole
        translate([-clamp_x/2 - EAR_WALL_T - EAR_D/2, 0, BASE_THICKNESS + EAR_H/2])
            cylinder(d=EAR_D, h=EAR_H + 1, center=true);

        // Right ear hole
        translate([clamp_x/2 + EAR_WALL_T + EAR_D/2, 0, BASE_THICKNESS + EAR_H/2])
            cylinder(d=EAR_D, h=EAR_H + 1, center=true);
    }
}

// =============================================================================
// JAW BODY (single snap arm with cable pocket)
// =============================================================================
//
// A flexible cantilever arm extending from the spine.
// Lower section: solid (load-bearing).
// Upper section: curved U-channel (grips cable).
// Jaw tips: overhanging tabs for zip-tie anchors.
//

module jaw_body(x_root, jaw_length, inner_span) {
    jaw_span_outer = inner_span + 2 * JAW_THICKNESS;

    // The jaw sweeps from x_root (spine side) along +X, curving to grip.
    // At the tip, it has a slight outward bow for snap action.

    difference() {
        union() {
            // Lower jaw arm (solid, structural)
            translate([x_root, -jaw_span_outer/2, BASE_THICKNESS])
                cube([jaw_length * 0.65, jaw_span_outer, JAW_THICKNESS * 1.5]);

            // Upper jaw arm (U-channel form)
            translate([x_root, -inner_span/2 - JAW_THICKNESS, BASE_THICKNESS])
                cube([jaw_length * 0.85, inner_span + 2*JAW_THICKNESS, JAW_THICKNESS]);

            // Jaw tip anchor tab (for zip-tie slots)
            tip_x = x_root + jaw_length * 0.8;
            translate([tip_x, -jaw_span_outer/2 - ANCHOR_TAB_T,
                       BASE_THICKNESS + JAW_THICKNESS])
                cube([ANCHOR_TAB_H, jaw_span_outer + 2*ANCHOR_TAB_T, ANCHOR_TAB_H]);
        }

        // Hollow out the U-channel (cable pocket)
        // Inner cavity: inner_span wide, runs most of jaw length
        translate([x_root + JAW_THICKNESS * 0.5, -inner_span/2, BASE_THICKNESS])
            cube([jaw_length * 0.7, inner_span, JAW_THICKNESS + 0.5]);
    }
}

// =============================================================================
// EAR BOSS (M3 attachment point)
// =============================================================================
//
// A small raised button with a through-hole, providing optional redundant
// attachment for straps or hard-wired retention.
//

module ear_boss() {
    difference() {
        cylinder(d=EAR_D + 2*EAR_WALL_T, h=EAR_H);
        translate([0, 0, -1])
            cylinder(d=EAR_D, h=EAR_H + 2);
    }
}

// =============================================================================
// UTILITY: Rounded Rectangle (for base and ghosts)
// =============================================================================
//

module rounded_rect(size, r) {
    // size = [width, length, height]
    w = size[0];
    l = size[1];
    h = size[2];

    linear_extrude(height=h)
        offset(r=r)
            offset(r=-r)
                square([w, l], center=true);
}

// =============================================================================
// EXPORT / PRINT INSTRUCTIONS
// =============================================================================
//
//  CLIP 5mm  (3D print × N):
//    openscad cable_management_clips.scad -D 'RENDER="clip_5mm"' -o clip_5mm.stl
//    Print settings: PETG/ASA, 4 perimeters, 20% infill, 0.2 mm layer
//                    Orientation: base flat on bed (smooth finish for adhesive)
//
//  CLIP 8mm  (3D print × N):
//    openscad cable_management_clips.scad -D 'RENDER="clip_8mm"' -o clip_8mm.stl
//    Print settings: Same as 5mm
//
//  CLIP 12mm  (3D print × N):
//    openscad cable_management_clips.scad -D 'RENDER="clip_12mm"' -o clip_12mm.stl
//    Print settings: Same as 5mm
//
// =============================================================================
//
// INSTALLATION GUIDE
//
//   1. SURFACE PREP
//      • Clean chassis surface with isopropyl alcohol.
//      • Let dry for 5 minutes; inspect for dust or residue.
//
//   2. ADHESIVE APPLICATION
//      • Cut 3M VHB 5952F into ~50 × 40 mm pads (one per clip).
//      • Peel foil backing from VHB pad.
//      • Center pad on clip base; press firmly for 30 seconds.
//      • Peel clear polyester liner from exposed adhesive.
//
//   3. MOUNTING
//      • Position clip on chassis surface (e.g., along frame rail).
//      • Press and hold for 30 seconds, applying full body weight if possible.
//      • Let cure for 24 hours before loading cables.
//
//   4. CABLE INSERTION
//      • Gather cable bundle (power, signal, etc.); inspect for knots/damage.
//      • Align bundle perpendicular to clip jaws.
//      • Press upward with steady pressure until jaws snap closed (audible click).
//      • Tension should hold cable 5–10 N without slip.
//
//   5. ZIP-TIE ANCHORING (optional extra security)
//      • Thread 3.6 mm nylon zip-tie through jaw anchor tabs (left and right).
//      • Route around cable bundle; cinch to desired tension (avoid crushing).
//      • Trim excess tie length.
//
//   6. THREADED INSERTION (optional M3 redundancy)
//      • Install M3 threaded insert into ear hole (using M3 insertion tool).
//      • Thread M3 × 16 mm bolt with split washer through ear.
//      • Tighten 1.5 N·m (firm but not excessive).
//
// =============================================================================
//
// CABLE ROUTING BEST PRACTICES
//
//   • Power cables (main): Use 12mm clips, spacing 150–200 mm apart.
//   • Mixed signal bundles: Use 8mm clips, spacing 100–150 mm apart.
//   • Individual sensor leads: Use 5mm clips or traditional P-clips.
//
//   • Avoid sharp bends: Route bundles with R ≥ 50 mm (cable bundle diameter).
//   • Prevent abrasion: Use snap clips where cable crosses sharp edges.
//   • Allow thermal expansion: Leave ~2–3 mm slack in long runs.
//   • Color-code bundles: Use electrical tape or heatshrink before clipping.
//
// =============================================================================
