Merge pull request 'feat(mechanical): Cable management clips (Issue #264)' (#283) from sl-mechanical/issue-264-cable-clips into main

This commit is contained in:
sl-jetson 2026-03-02 21:05:14 -05:00
commit a4d29376b8

View File

@ -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 46 mm bundles (small signal cables)
// Clip 8mm: Holds 610 mm bundles (mixed power + signal)
// Clip 12mm: Holds 1014 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 510 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 150200 mm apart.
// Mixed signal bundles: Use 8mm clips, spacing 100150 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 ~23 mm slack in long runs.
// Color-code bundles: Use electrical tape or heatshrink before clipping.
//
// =============================================================================