// ============================================================ // phone_mount_bracket.scad — Spring-Loaded Phone Mount for T-Slot Rail // Issue: #535 Agent: sl-mechanical Date: 2026-03-07 // ============================================================ // // Parametric spring-loaded phone mount that clamps to the 2020 aluminium // T-slot sensor rail. Adjustable phone width 60–85 mm. Quick-release // cam lever for tool-free phone swap. Vibration-damping flexure ribs // on grip pads absorb motor/terrain vibration (PETG compliance). // // Design overview: // - Fixed jaw + sliding jaw on a 40 mm guide rail (M4 rod) // - Coil spring (Ø8 × 30 mm) compressed between jaw and end-stop — // spring pre-load keeps phone clamped at any width in range // - Cam lever (printed PETG) rotates 90° to release / lock spring // - Anti-vibration flexure ribs on both grip pad faces // - Landscape or portrait orientation: bracket rotates on T-nut base // // Parts (STL exports): // Part 1 — tnut_base() Rail attachment base (universal) // Part 2 — fixed_jaw() Fixed bottom jaw + guide rail bosses // Part 3 — sliding_jaw() Spring-loaded sliding jaw // Part 4 — cam_lever() Quick-release cam lever // Part 5 — grip_pad() Flexure grip pad (print ×2, TPU optional) // Part 6 — assembly_preview() Full assembly // // Hardware BOM (per mount): // 1× M4 × 60 mm SHCS guide rod + spring bolt // 1× M4 hex nut end-stop on sliding jaw // 1× Ø8 × 30 mm coil spring ~0.5 N/mm rate (spring clamping) // 2× M3 × 16 mm SHCS T-nut base thumbscrew + arm bolts // 1× M3 hex nut thumbscrew nut in T-nut // 4× M2 × 8 mm SHCS grip pad retention bolts (optional) // // Dimensions: // Phone width range : PHONE_W_MIN–PHONE_W_MAX (60–85 mm) parametric // Phone thickness : up to PHONE_THICK_MAX (12 mm) — open-front jaw // Phone height held : GRIP_SPAN (22 mm each jaw) — portrait/landscape // Overall bracket H : ~110 mm W: ~90 mm D: ~55 mm // // Print settings: // Material : PETG (tnut_base, fixed_jaw, sliding_jaw, cam_lever) // TPU 95A optional for grip_pad (or PETG for rigidity) // Perimeters: 5 (structural parts), 3 (grip_pad) // Infill : 40 % gyroid (jaws), 20 % (grip_pad) // Supports : none needed (designed for FDM orientation) // Layer ht : 0.2 mm // // Export commands: // openscad phone_mount_bracket.scad -D 'RENDER="tnut_base_stl"' -o pm_tnut_base.stl // openscad phone_mount_bracket.scad -D 'RENDER="fixed_jaw_stl"' -o pm_fixed_jaw.stl // openscad phone_mount_bracket.scad -D 'RENDER="sliding_jaw_stl"' -o pm_sliding_jaw.stl // openscad phone_mount_bracket.scad -D 'RENDER="cam_lever_stl"' -o pm_cam_lever.stl // openscad phone_mount_bracket.scad -D 'RENDER="grip_pad_stl"' -o pm_grip_pad.stl // ============================================================ $fn = 64; e = 0.01; // epsilon for boolean clearance // ── Phone parameters (adjust to target device) ─────────────────────────────── PHONE_W_MIN = 60.0; // narrowest phone width supported (mm) PHONE_W_MAX = 85.0; // widest phone width supported (mm) PHONE_THICK_MAX = 12.0; // max phone body thickness incl. case (mm) // ── Rail geometry (must match sensor_rail.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 constants ─────────────────────────────────────────────────────────── 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 // ── Base plate geometry ─────────────────────────────────────────────────────── BASE_FACE_W = 30.0; BASE_FACE_H = 25.0; BASE_FACE_T = SLOT_NECK_H + 1.5; // ── Jaw geometry ───────────────────────────────────────────────────────────── JAW_BODY_W = 88.0; // jaw outer width (> PHONE_W_MAX for rim) JAW_BODY_H = 28.0; // jaw height (Z) — phone grip span JAW_BODY_T = 14.0; // jaw depth (Y) — phone cradled this deep JAW_WALL_T = 4.0; // jaw side wall thickness JAW_LIP_T = 3.0; // front retaining lip thickness JAW_LIP_H = 5.0; // front lip height (retains phone) PHONE_POCKET_D = PHONE_THICK_MAX + 0.5; // pocket depth for phone // ── Guide rod / spring system ───────────────────────────────────────────────── GUIDE_ROD_D = 4.3; // M4 clearance bore in sliding jaw GUIDE_BOSS_D = 10.0; // boss OD around guide bore GUIDE_BOSS_T = 6.0; // boss length SPRING_OD = 8.5; // coil spring OD pocket (spring is Ø8) SPRING_L = 32.0; // spring pocket length (spring compressed ~22 mm) SPRING_SEAT_T = 3.0; // spring seat wall at end-stop boss JAW_TRAVEL = PHONE_W_MAX - PHONE_W_MIN + 4.0; // max jaw travel (mm) ARM_SPAN = PHONE_W_MAX + 2 * JAW_WALL_T + 8; // fixed jaw total width // ── Cam lever geometry ──────────────────────────────────────────────────────── CAM_R_MIN = 5.0; // cam small radius (engaged / clamped) CAM_R_MAX = 9.0; // cam large radius (released, spring compressed) CAM_THICK = 8.0; // cam disc thickness CAM_HANDLE_L = 45.0; // lever arm length CAM_HANDLE_W = 8.0; // lever handle width CAM_HANDLE_T = 5.0; // lever handle thickness CAM_BORE_D = 4.3; // M4 pivot bore CAM_DETENT_D = 3.0; // detent ball pocket (3 mm bearing) // ── Grip pad geometry (vibration dampening flexure ribs) ───────────────────── PAD_W = JAW_BODY_W - 2*JAW_WALL_T - 2; // pad width PAD_H = JAW_BODY_H - 2; // pad height PAD_T = 2.5; // pad body thickness RIB_H = 1.5; // flexure rib height above pad face RIB_W = 1.2; // rib width RIB_PITCH = 5.0; // rib pitch (centre-to-centre) RIB_COUNT = floor(PAD_W / RIB_PITCH) - 1; // ── Arm geometry (base to jaw body) ────────────────────────────────────────── ARM_REACH = 38.0; // distance from rail face to jaw centreline (+Y) ARM_T = 4.0; // arm thickness ARM_H = BASE_FACE_H; // ── Fasteners ───────────────────────────────────────────────────────────────── M2_D = 2.4; M3_D = 3.3; M4_D = 4.3; M4_NUT_AF = 7.0; // M4 hex nut across-flats M4_NUT_H = 3.2; // M4 hex nut height // ============================================================ // RENDER DISPATCH // ============================================================ RENDER = "assembly"; if (RENDER == "assembly") assembly_preview(); else if (RENDER == "tnut_base_stl") tnut_base(); else if (RENDER == "fixed_jaw_stl") fixed_jaw(); else if (RENDER == "sliding_jaw_stl") sliding_jaw(); else if (RENDER == "cam_lever_stl") cam_lever(); else if (RENDER == "grip_pad_stl") grip_pad(); // ============================================================ // ASSEMBLY PREVIEW // ============================================================ module assembly_preview() { // Ghost rail section (20 × 20 × 200) %color("Silver", 0.30) linear_extrude(200) square([RAIL_W, RAIL_W], center = true); // T-nut base at Z=80 on rail color("OliveDrab", 0.85) translate([0, 0, 80]) tnut_base(); // Fixed jaw assembly (centred, extending +Y from base) color("DarkSlateGray", 0.85) translate([0, SLOT_NECK_H + BASE_FACE_T + ARM_REACH, 80]) fixed_jaw(); // Sliding jaw — shown at mid-travel (phone ~72 mm wide) color("SteelBlue", 0.85) translate([PHONE_W_MIN + (PHONE_W_MAX - PHONE_W_MIN)/2, SLOT_NECK_H + BASE_FACE_T + ARM_REACH, 80]) sliding_jaw(); // Grip pads on both jaws color("DimGray", 0.85) { translate([0, SLOT_NECK_H + BASE_FACE_T + ARM_REACH, 80]) translate([JAW_WALL_T, JAW_BODY_T, JAW_BODY_H/2]) rotate([90, 0, 0]) grip_pad(); translate([PHONE_W_MIN + (PHONE_W_MAX - PHONE_W_MIN)/2, SLOT_NECK_H + BASE_FACE_T + ARM_REACH, 80]) translate([-JAW_WALL_T - PAD_T, JAW_BODY_T, JAW_BODY_H/2]) rotate([90, 0, 180]) grip_pad(); } // Cam lever — shown in locked (clamped) position color("OrangeRed", 0.85) translate([ARM_SPAN/2 + 6, SLOT_NECK_H + BASE_FACE_T + ARM_REACH + GUIDE_BOSS_D/2, 80 + JAW_BODY_H/2]) rotate([0, 0, 0]) cam_lever(); } // ============================================================ // PART 1 — T-NUT BASE // ============================================================ // Standard 2020 T-slot rail attachment base. // Identical interface to sensor_rail_brackets.scad universal_tnut_base(). // Arm extends in +Y; rail clamp bolt in -Y face. // // Print flat (face plate down), PETG, 5 perims, 60 % infill. module tnut_base() { difference() { union() { // Face plate (flush against rail outer face) translate([-BASE_FACE_W/2, -BASE_FACE_T, 0]) cube([BASE_FACE_W, BASE_FACE_T, BASE_FACE_H]); // T-nut neck (enters rail slot) translate([-TNUT_W/2, 0, (BASE_FACE_H - TNUT_L)/2]) cube([TNUT_W, SLOT_NECK_H + e, TNUT_L]); // T-nut body (wider, inside T-groove) translate([-TNUT_W/2, SLOT_NECK_H - e, (BASE_FACE_H - TNUT_L)/2]) cube([TNUT_W, TNUT_H - SLOT_NECK_H + e, TNUT_L]); // Arm stub (face plate → jaw) translate([-BASE_FACE_W/2, -BASE_FACE_T, 0]) cube([BASE_FACE_W, BASE_FACE_T + ARM_REACH, ARM_T]); } // M3 rail clamp bolt bore (centre of T-nut, through face plate) translate([0, -BASE_FACE_T - e, BASE_FACE_H/2]) rotate([-90, 0, 0]) cylinder(d = TNUT_BOLT_D, h = BASE_FACE_T + TNUT_H + 2*e); // M3 hex nut pocket (inside T-nut body) translate([0, SLOT_NECK_H + 0.3, BASE_FACE_H/2]) rotate([-90, 0, 0]) cylinder(d = TNUT_M3_NUT_AF / cos(30), h = TNUT_M3_NUT_H + 0.3, $fn = 6); // 2× M3 bolt holes for arm-to-jaw bolting for (bx = [-10, 10]) translate([bx, ARM_REACH - BASE_FACE_T - e, ARM_T/2]) rotate([-90, 0, 0]) cylinder(d = M3_D, h = 8 + 2*e); // Lightening slot in arm translate([0, -BASE_FACE_T/2 + ARM_REACH/2, ARM_T/2]) cube([BASE_FACE_W - 12, ARM_REACH - 16, ARM_T + 2*e], center = true); } } // ============================================================ // PART 2 — FIXED JAW // ============================================================ // Fixed lower jaw of the clamping system. Phone sits in the pocket // formed by the fixed jaw (bottom) and sliding jaw (top). // Two guide bosses on the right wall carry the M4 guide rod + spring. // The cam lever pivot boss is on the outer right face. // // Coordinate origin: centre-bottom of jaw body. // Phone entry face: +Y (open front), phone pocket opens toward +Y. // Fixed jaw left edge is at X = -JAW_BODY_W/2. // // Print jaw-pocket-face down, PETG, 5 perims, 40 % infill. module fixed_jaw() { difference() { union() { // ── Main jaw body ──────────────────────────────────────────── translate([-JAW_BODY_W/2, -JAW_BODY_T/2, 0]) cube([JAW_BODY_W, JAW_BODY_T, JAW_BODY_H]); // ── Front retaining lip (keeps phone from falling forward) ─── translate([-JAW_BODY_W/2, JAW_BODY_T/2 - JAW_LIP_T, 0]) cube([JAW_BODY_W, JAW_LIP_T, JAW_LIP_H]); // ── Guide boss right (outer, carries spring + end-stop) ────── translate([JAW_BODY_W/2, 0, JAW_BODY_H/2]) rotate([0, 90, 0]) cylinder(d = GUIDE_BOSS_D, h = GUIDE_BOSS_T); // ── Cam lever pivot boss (right face, above guide boss) ────── translate([JAW_BODY_W/2, 0, JAW_BODY_H/2 + GUIDE_BOSS_D + 4]) rotate([0, 90, 0]) cylinder(d = CAM_THICK + 4, h = 6); // ── Arm attachment bosses (left side, connect to tnut_base) ── for (bx = [-10, 10]) translate([bx, -JAW_BODY_T/2 - 8, ARM_T/2]) cylinder(d = 8, h = 8); } // ── Phone pocket (open-top U channel centred in jaw) ──────────── // Pocket opens toward +Y (front), phone drops in from above. translate([0, -JAW_BODY_T/2 - e, JAW_LIP_H]) cube([JAW_BODY_W - 2*JAW_WALL_T, PHONE_POCKET_D + JAW_WALL_T, JAW_BODY_H - JAW_LIP_H + e], center = [true, false, false]); // ── Guide rod bore (M4 clearance, through both guide bosses) ──── translate([-JAW_BODY_W/2 - e, 0, JAW_BODY_H/2]) rotate([0, 90, 0]) cylinder(d = GUIDE_ROD_D, h = JAW_BODY_W + GUIDE_BOSS_T + 2*e); // ── Spring pocket (coaxial with guide rod, in right boss) ──────── translate([JAW_BODY_W/2 + e, 0, JAW_BODY_H/2]) rotate([0, -90, 0]) cylinder(d = SPRING_OD, h = SPRING_L); // ── M4 hex nut pocket in spring-seat wall (end-stop nut) ──────── translate([JAW_BODY_W/2 + GUIDE_BOSS_T + e, 0, JAW_BODY_H/2]) rotate([0, -90, 0]) cylinder(d = M4_NUT_AF / cos(30), h = M4_NUT_H + 0.5, $fn = 6); // ── Cam pivot bore (M4 pivot, through pivot boss) ──────────────── translate([JAW_BODY_W/2 - e, 0, JAW_BODY_H/2 + GUIDE_BOSS_D + 4]) rotate([0, 90, 0]) cylinder(d = CAM_BORE_D, h = 6 + 2*e); // ── Arm attachment bolt holes (M3, to tnut_base arm stubs) ────── for (bx = [-10, 10]) translate([bx, -JAW_BODY_T/2 - 8 - e, ARM_T/2]) rotate([-90, 0, 0]) cylinder(d = M3_D, h = 12 + 2*e); // ── Grip pad seats (recessed Ø1.5 mm, 2 mm deep, optional) ────── for (pz = [JAW_BODY_H * 0.3, JAW_BODY_H * 0.7]) for (px = [-PAD_W/4, PAD_W/4]) translate([px, -JAW_BODY_T/2 + PHONE_POCKET_D + 1, pz]) rotate([-90, 0, 0]) cylinder(d = M2_D, h = 10); // ── Lightening pockets (non-structural core removal) ───────────── translate([0, 0, JAW_BODY_H/2]) cube([JAW_BODY_W - 2*JAW_WALL_T - 4, JAW_BODY_T - 2*JAW_WALL_T, JAW_BODY_H - JAW_LIP_H - 4], center = true); } } // ============================================================ // PART 3 — SLIDING JAW // ============================================================ // Upper clamping jaw. Slides along the M4 guide rod. // Spring pushes this jaw toward the phone (inward). // M4 hex nut on the guide rod limits maximum travel (full open). // Cam lever pushes on this jaw face to compress spring (release). // // Coordinate origin same convention as fixed_jaw() for assembly. // Jaw slides in +X direction (away from fixed jaw left wall). // // Print jaw-pocket-face down, PETG, 5 perims, 40 % infill. module sliding_jaw() { difference() { union() { // ── Main jaw body ──────────────────────────────────────────── translate([-JAW_WALL_T, -JAW_BODY_T/2, 0]) cube([JAW_BODY_W/2 + JAW_WALL_T, JAW_BODY_T, JAW_BODY_H]); // ── Front retaining lip ────────────────────────────────────── translate([-JAW_WALL_T, JAW_BODY_T/2 - JAW_LIP_T, 0]) cube([JAW_BODY_W/2 + JAW_WALL_T, JAW_LIP_T, JAW_LIP_H]); // ── Guide boss (carries guide rod, spring butts against face) ─ translate([-JAW_WALL_T - GUIDE_BOSS_T, 0, JAW_BODY_H/2]) rotate([0, 90, 0]) cylinder(d = GUIDE_BOSS_D, h = GUIDE_BOSS_T); // ── Cam follower ear (contacts cam lever) ──────────────────── translate([-JAW_WALL_T - 2, 0, JAW_BODY_H/2 + GUIDE_BOSS_D + 4]) cube([4, CAM_THICK + 2, CAM_THICK + 2], center = true); } // ── Phone pocket (inner face, contacts phone side) ─────────────── translate([-JAW_WALL_T - e, -JAW_BODY_T/2 - e, JAW_LIP_H]) cube([JAW_BODY_W/2 - JAW_WALL_T + e, PHONE_POCKET_D + JAW_WALL_T + 2*e, JAW_BODY_H - JAW_LIP_H + e]); // ── Guide rod bore (M4 clearance through boss + jaw wall) ──────── translate([-JAW_WALL_T - GUIDE_BOSS_T - e, 0, JAW_BODY_H/2]) rotate([0, 90, 0]) cylinder(d = GUIDE_ROD_D, h = GUIDE_BOSS_T + JAW_WALL_T + 2*e); // ── M4 nut pocket (end-stop nut, rear of guide boss) ──────────── translate([-JAW_WALL_T - GUIDE_BOSS_T - e, 0, JAW_BODY_H/2]) rotate([0, 90, 0]) cylinder(d = M4_NUT_AF / cos(30), h = M4_NUT_H + 1, $fn = 6); // ── Cam follower bore (M4 pivot passes through ear) ───────────── translate([-JAW_WALL_T - 2 - e, 0, JAW_BODY_H/2 + GUIDE_BOSS_D + 4]) rotate([0, 90, 0]) cylinder(d = CAM_BORE_D, h = 6 + 2*e); // ── Grip pad seats ─────────────────────────────────────────────── for (pz = [JAW_BODY_H * 0.3, JAW_BODY_H * 0.7]) for (px = [JAW_BODY_W/8]) translate([px, -JAW_BODY_T/2 + PHONE_POCKET_D + 1, pz]) rotate([-90, 0, 0]) cylinder(d = M2_D, h = 10); } } // ============================================================ // PART 4 — CAM LEVER (QUICK-RELEASE) // ============================================================ // Eccentric cam disc + integral handle lever. // Rotates 90° on M4 pivot pin between CLAMPED and RELEASED states: // CLAMPED : cam small radius (CAM_R_MIN) toward jaw → spring pushes jaw // RELEASED : cam large radius (CAM_R_MAX) toward jaw → compresses spring // by (CAM_R_MAX - CAM_R_MIN) = 4 mm, opening jaw // // Detent ball pocket (Ø3 mm) snaps into rail-dimple for each position. // Handle points rearward (-Y) in clamped state for low profile. // // Print standing on cam edge (cam disc vertical), PETG, 5 perims, 40%. module cam_lever() { cam_offset = (CAM_R_MAX - CAM_R_MIN) / 2; // 2 mm eccentricity union() { difference() { union() { // ── Eccentric cam disc ─────────────────────────────────── // Offset so pivot bore is eccentric to disc profile translate([cam_offset, 0, 0]) cylinder(r = CAM_R_MAX, h = CAM_THICK, center = true); // ── Lever handle arm ───────────────────────────────────── hull() { translate([cam_offset, 0, 0]) cylinder(r = CAM_R_MAX, h = CAM_THICK, center = true); translate([cam_offset + CAM_HANDLE_L, 0, 0]) cylinder(r = CAM_HANDLE_W/2, h = CAM_HANDLE_T, center = true); } } // ── M4 pivot bore (through cam centre) ─────────────────────── cylinder(d = CAM_BORE_D, h = CAM_THICK + 2*e, center = true); // ── Detent pockets (2× Ø3 mm, at 0° and 90°) ──────────────── // Pocket at 0° → clamped detent translate([CAM_R_MAX - 2, 0, CAM_THICK/2 - 1.5]) cylinder(d = CAM_DETENT_D + 0.2, h = 2); // Pocket at 90° → released detent translate([0, CAM_R_MAX - 2, CAM_THICK/2 - 1.5]) cylinder(d = CAM_DETENT_D + 0.2, h = 2); // ── Lightening recesses on cam disc face ───────────────────── for (a = [0, 60, 120, 180, 240, 300]) translate([cam_offset + (CAM_R_MAX - 4) * cos(a), (CAM_R_MAX - 4) * sin(a), 0]) cylinder(d = 4, h = CAM_THICK + 2*e, center = true); // ── Handle grip grooves ────────────────────────────────────── for (i = [0:4]) translate([cam_offset + 20 + i * 5, 0, 0]) rotate([90, 0, 0]) cylinder(d = 2.5, h = CAM_HANDLE_W + 2*e, center = true); } } } // ============================================================ // PART 5 — GRIP PAD (VIBRATION DAMPENING) // ============================================================ // Flat pad with transverse flexure ribs that press against phone side. // The rib profile (thin PETG fins) provides compliance in Z (vertical) // absorbing vibration transmitted through the bracket. // Optional: print in TPU 95A for superior damping. // M2 bolts or adhesive-backed foam tape attach pad to jaw pocket face. // // Pad face (+Y) contacts phone. Mounting face (-Y) bonds to jaw. // Ribs run parallel to Z axis (vertical). // // Print flat (mounting face down), PETG or TPU 95A, 3 perims, 20%. module grip_pad() { union() { // ── Base plate ─────────────────────────────────────────────────── translate([-PAD_W/2, -PAD_T, -PAD_H/2]) cube([PAD_W, PAD_T, PAD_H]); // ── Flexure ribs (transverse, dampening in Z) ──────────────────── // RIB_COUNT ribs spaced RIB_PITCH apart, centred on pad for (i = [0 : RIB_COUNT - 1]) { rx = -PAD_W/2 + RIB_PITCH/2 + i * RIB_PITCH; if (abs(rx) <= PAD_W/2 - RIB_W/2) // stay within pad translate([rx, 0, 0]) cube([RIB_W, RIB_H, PAD_H - 4], center = true); } // ── Corner retention nubs (M2 boss for optional bolt-through) ──── 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 = M2_D, h = PAD_T + 2*e, center = true); } } }