saltylab-firmware/chassis/cable_management_clips.scad
sl-mechanical 479a33a6fa
Some checks failed
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 9s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (pull_request) Has been skipped
social-bot integration tests / Latency profiling (GPU, Orin) (pull_request) Has been cancelled
feat(mechanical): parametric cable management clips (Issue #264)
2026-03-02 20:44:27 -05:00

355 lines
15 KiB
OpenSCAD
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// =============================================================================
// 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.
//
// =============================================================================