From 479a33a6fac243c6f4cca41a74f6f0524066ac73 Mon Sep 17 00:00:00 2001 From: sl-mechanical Date: Mon, 2 Mar 2026 17:35:15 -0500 Subject: [PATCH] feat(mechanical): parametric cable management clips (Issue #264) --- chassis/cable_management_clips.scad | 354 ++++++++++++++++++++++++++++ 1 file changed, 354 insertions(+) create mode 100644 chassis/cable_management_clips.scad diff --git a/chassis/cable_management_clips.scad b/chassis/cable_management_clips.scad new file mode 100644 index 0000000..ff047de --- /dev/null +++ b/chassis/cable_management_clips.scad @@ -0,0 +1,354 @@ +// ============================================================================= +// 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. +// +// =============================================================================