From 0fd6ea92b0b4dd105f6be354a78765833d076b29 Mon Sep 17 00:00:00 2001 From: sl-mechanical Date: Sat, 14 Mar 2026 10:28:03 -0400 Subject: [PATCH] feat: Camera gimbal mount bracket for RealSense D435i (Issue #552) --- chassis/gimbal_camera_mount.scad | 599 +++++++++++++++++++++++++++++++ 1 file changed, 599 insertions(+) create mode 100644 chassis/gimbal_camera_mount.scad diff --git a/chassis/gimbal_camera_mount.scad b/chassis/gimbal_camera_mount.scad new file mode 100644 index 0000000..d148bfe --- /dev/null +++ b/chassis/gimbal_camera_mount.scad @@ -0,0 +1,599 @@ +// ============================================================ +// gimbal_camera_mount.scad — Pan/Tilt Gimbal Mount for RealSense D435i +// Issue: #552 Agent: sl-mechanical Date: 2026-03-14 +// ============================================================ +// +// Parametric gimbal bracket system mounting an Intel RealSense D435i +// (or similar box camera) on a 2-axis pan/tilt gimbal driven by +// ST3215 serial bus servos (25T spline, Feetech/Waveshare). +// +// Architecture: +// Pan axis — base T-nut clamps to 2020 rail; pan servo rotates yoke +// Tilt axis — tilt servo horn plate bolts to ST3215 horn; camera cradle +// rocks on tilt axis +// Camera — D435i captured via 1/4-20 UNC hex nut in cradle floor +// Damping — PETG flexure ribs on camera contact faces (or TPU pads) +// Wiring — USB-C cable routed through channel in cradle arm +// +// Part catalogue: +// Part 1 — tnut_rail_base() 2020 rail T-nut base + pan servo seat +// Part 2 — pan_yoke() U-yoke connecting pan servo to tilt axis +// Part 3 — tilt_horn_plate() Plate bolting to ST3215 tilt servo horn +// Part 4 — camera_cradle() D435i cradle with 1/4-20 captured nut +// Part 5 — vibe_pad() PETG flexure vibration-damping pad (×2) +// Part 6 — assembly_preview() Full assembly preview +// +// Hardware BOM (per gimbal): +// 2× ST3215 serial bus servo (pan + tilt) +// 2× servo horn (25T spline, ≥Ø36 mm, 4× M3 bolt holes on Ø24 mm BC) +// 2× M3 × 8 mm SHCS horn-to-plate bolts (×4 each horn = 8 total) +// 1× M3 × 16 mm SHCS + nut T-nut rail clamp thumbscrew +// 1× 1/4-20 UNC × 8 mm SHCS camera retention bolt (or existing tripod screw) +// 1× 1/4-20 UNC hex nut captured in cradle floor +// 4× M3 × 12 mm SHCS yoke-to-tilt-plate pivot axle bolts +// 2× M3 × 25 mm SHCS pan yoke attachment to servo body +// (optional) 2× vibe_pad printed in TPU 95A +// +// ST3215 servo interface (caliper-verified Feetech ST3215): +// Body footprint : 40.0 × 20.0 mm (W × D), 36.5 mm tall +// Shaft centre H : 28.5 mm from mounting face +// Shaft spline : 25T, centre Ø5.8 mm, D-cut +// Mount holes : 4× M3 on 32 × 10 mm rectangular pattern (18 mm offset) +// Horn bolt circle: Ø24 mm, 4× M3 +// Horn OD : ~36 mm +// +// D435i camera interface (caliper-verified): +// Body : 90 × 25 × 25 mm (W × D × H) +// Tripod thread : 1/4-20 UNC, centred bottom face, 9 mm from front +// USB-C connector: right rear, 8 × 5 mm opening, 4 mm from edge +// +// Parametric camera size (override to adapt to other cameras): +// CAM_W, CAM_D, CAM_H — body envelope +// CAM_MOUNT_X — tripod hole X offset from camera centre +// CAM_MOUNT_Y — tripod hole Y offset from front face +// +// Coordinate convention: +// Camera looks in +Y direction (forward) +// Pan axis is Z (vertical); tilt axis is X (lateral) +// Rail runs along Z; T-nut base at Z=0 +// All parts at assembly origin; translate for assembly_preview +// +// RENDER options: +// "assembly" full assembly preview (default) +// "tnut_rail_base_stl" Part 1 +// "pan_yoke_stl" Part 2 +// "tilt_horn_plate_stl" Part 3 +// "camera_cradle_stl" Part 4 +// "vibe_pad_stl" Part 5 +// +// Export commands: +// openscad gimbal_camera_mount.scad -D 'RENDER="tnut_rail_base_stl"' -o gcm_tnut_base.stl +// openscad gimbal_camera_mount.scad -D 'RENDER="pan_yoke_stl"' -o gcm_pan_yoke.stl +// openscad gimbal_camera_mount.scad -D 'RENDER="tilt_horn_plate_stl"' -o gcm_tilt_horn_plate.stl +// openscad gimbal_camera_mount.scad -D 'RENDER="camera_cradle_stl"' -o gcm_camera_cradle.stl +// openscad gimbal_camera_mount.scad -D 'RENDER="vibe_pad_stl"' -o gcm_vibe_pad.stl +// ============================================================ + +$fn = 64; +e = 0.01; // epsilon for boolean clearance + +// ── Parametric camera envelope ──────────────────────────────────────────────── +// Override these for cameras other than D435i +CAM_W = 90.0; // camera body width (X) +CAM_D = 25.0; // camera body depth (Y) +CAM_H = 25.0; // camera body height (Z) +CAM_MOUNT_X = 0.0; // tripod hole X offset from camera body centre +CAM_MOUNT_Y = 9.0; // tripod hole from front face (Y) [D435i: 9 mm] +CAM_USBC_X = CAM_W/2 - 4; // USB-C connector X (right side) +CAM_USBC_Z = CAM_H/2; // USB-C connector Z (mid-height rear) +CAM_USBC_W = 9.0; // USB-C opening width (X) +CAM_USBC_H = 5.0; // USB-C opening height (Z) + +// ── Rail geometry (matches sensor_rail.scad / sensor_rail_brackets.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 geometry (matches sensor_rail_brackets.scad) ─────────────────────── +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 + +// ── T-nut base plate geometry ───────────────────────────────────────────────── +BASE_W = 44.0; // wide enough for pan servo body (40 mm) +BASE_H = 40.0; // height along rail (Z) +BASE_T = SLOT_NECK_H + 2.0; // plate depth (Y), rail-face side + +// ── ST3215 servo geometry ───────────────────────────────────────────────────── +SERVO_W = 40.0; // servo body width (X) +SERVO_D = 20.0; // servo body depth (Y) +SERVO_H = 36.5; // servo body height (Z) +SERVO_SHAFT_Z = 28.5; // shaft centre height from mounting face +SERVO_HOLE_X = 16.0; // mount hole half-span X (32 mm span) +SERVO_HOLE_Y = 5.0; // mount hole half-span Y (10 mm span) +SERVO_M3_D = 3.3; // M3 clearance + +// ── Servo horn geometry ─────────────────────────────────────────────────────── +HORN_OD = 36.0; // horn outer diameter +HORN_SPLINE_D = 5.9; // 25T spline bore clearance (5.8 + 0.1) +HORN_BC_D = 24.0; // bolt circle diameter (4× M3) +HORN_BOLT_D = 3.3; // M3 clearance through horn plate +HORN_PLATE_T = 5.0; // tilt horn plate thickness + +// ── Yoke geometry ───────────────────────────────────────────────────────────── +YOKE_WALL_T = 5.0; // yoke arm wall thickness +YOKE_ARM_H = 50.0; // yoke arm height (Z) — clears servo body + camera +YOKE_INNER_W = CAM_W + 8.0; // yoke inner span (camera + pad clearance) +YOKE_BASE_T = 8.0; // yoke base plate thickness + +// ── Tilt pivot ──────────────────────────────────────────────────────────────── +PIVOT_D = 4.3; // M4 pivot axle bore +PIVOT_BOSS_D = 10.0; // boss OD around pivot bore +PIVOT_BOSS_L = 6.0; // boss protrusion from yoke wall + +// ── Camera cradle geometry ──────────────────────────────────────────────────── +CRADLE_WALL_T = 4.0; // cradle side wall thickness +CRADLE_FLOOR_T = 5.0; // cradle floor thickness (holds 1/4-20 nut) +CRADLE_LIP_T = 3.0; // front retaining lip thickness +CRADLE_LIP_H = 8.0; // front lip height +CABLE_CH_W = 12.0; // USB-C cable channel width +CABLE_CH_H = 8.0; // USB-C cable channel height + +// ── 1/4-20 UNC tripod thread ────────────────────────────────────────────────── +QTR20_D = 6.6; // 1/4-20 clearance bore +QTR20_NUT_AF = 11.1; // 1/4-20 hex nut across-flats (standard) +QTR20_NUT_H = 5.5; // 1/4-20 hex nut height + +// ── Vibration-damping pad geometry ──────────────────────────────────────────── +PAD_W = CAM_W - 2*CRADLE_WALL_T - 2; +PAD_H = CAM_H + 4; +PAD_T = 2.5; // pad body thickness +RIB_H = 1.5; // flexure rib height +RIB_W = 1.2; // rib width +RIB_PITCH = 5.0; // rib pitch + +// ── Fastener sizes ──────────────────────────────────────────────────────────── +M3_D = 3.3; +M4_D = 4.3; +M3_NUT_AF = 5.5; +M3_NUT_H = 2.4; + +// ============================================================ +// RENDER DISPATCH +// ============================================================ +RENDER = "assembly"; + +if (RENDER == "assembly") assembly_preview(); +else if (RENDER == "tnut_rail_base_stl") tnut_rail_base(); +else if (RENDER == "pan_yoke_stl") pan_yoke(); +else if (RENDER == "tilt_horn_plate_stl") tilt_horn_plate(); +else if (RENDER == "camera_cradle_stl") camera_cradle(); +else if (RENDER == "vibe_pad_stl") vibe_pad(); + +// ============================================================ +// ASSEMBLY PREVIEW +// ============================================================ +module assembly_preview() { + asm_rail_z = 0; + // Rail section ghost (200 mm) + %color("Silver", 0.25) + translate([-RAIL_W/2, -RAIL_W/2, asm_rail_z]) + cube([RAIL_W, RAIL_W, 200]); + + // T-nut rail base + color("OliveDrab", 0.85) + translate([0, 0, asm_rail_z + 80]) + tnut_rail_base(); + + // Pan servo ghost (sitting in base seat) + %color("DimGray", 0.4) + translate([-SERVO_W/2, BASE_T, asm_rail_z + 80 + (BASE_H - SERVO_H)/2]) + cube([SERVO_W, SERVO_D, SERVO_H]); + + // Pan yoke rising from servo shaft + color("SteelBlue", 0.85) + translate([0, BASE_T + SERVO_D, asm_rail_z + 80 + BASE_H/2]) + pan_yoke(); + + // Tilt horn plate (tilt axis — left yoke wall) + color("DarkOrange", 0.85) + translate([-YOKE_INNER_W/2 - YOKE_WALL_T - HORN_PLATE_T, + BASE_T + SERVO_D + YOKE_BASE_T, + asm_rail_z + 80 + BASE_H/2 + YOKE_ARM_H/2]) + rotate([0, 90, 0]) + tilt_horn_plate(); + + // Camera cradle (centred in yoke) + color("DarkSlateGray", 0.85) + translate([0, BASE_T + SERVO_D + YOKE_BASE_T + CRADLE_FLOOR_T, + asm_rail_z + 80 + BASE_H/2 + YOKE_ARM_H/2 - CAM_H/2]) + camera_cradle(); + + // D435i ghost + %color("Black", 0.4) + translate([-CAM_W/2, + BASE_T + SERVO_D + YOKE_BASE_T + CRADLE_FLOOR_T + PAD_T, + asm_rail_z + 80 + Base_H_mid() - CAM_H/2]) + cube([CAM_W, CAM_D, CAM_H]); + + // Vibe pads (front + rear camera face) + color("DimGray", 0.80) { + translate([-CAM_W/2 + CRADLE_WALL_T + 1, + Base_T + SERVO_D + YOKE_BASE_T + CRADLE_FLOOR_T, + asm_rail_z + 80 + Base_H_mid() - PAD_H/2]) + rotate([90, 0, 0]) + vibe_pad(); + } +} + +// helper (avoids recomputing same expression) +function Base_T() = BASE_T; +function Base_H_mid() = BASE_H/2 + YOKE_ARM_H/2; + +// ============================================================ +// PART 1 — T-NUT RAIL BASE (pan servo seat + rail clamp) +// ============================================================ +// Mounts to 2020 rail via standard T-nut tongue. +// Front face (+Y side) provides flat seat for pan ST3215 servo body. +// Servo body recessed 1 mm into seat for positive lateral registration. +// Pan servo shaft axis = Z (vertical) → pan rotation about Z. +// +// Print: PETG, 5 perims, 50 % gyroid. Orient face-plate down (flat). +module tnut_rail_base() { + difference() { + union() { + // ── Face plate (against rail outer face, -Y side) ──────────── + translate([-BASE_W/2, -BASE_T, 0]) + cube([BASE_W, BASE_T, BASE_H]); + + // ── T-nut neck (enters rail slot, +Y side of face plate) ───── + translate([-TNUT_W/2, 0, (BASE_H - TNUT_L)/2]) + cube([TNUT_W, SLOT_NECK_H + e, TNUT_L]); + + // ── T-nut inner body (wider, locks inside T-groove) ────────── + translate([-TNUT_W/2, SLOT_NECK_H - e, (BASE_H - TNUT_L)/2]) + cube([TNUT_W, TNUT_H - SLOT_NECK_H + e, TNUT_L]); + + // ── Pan servo seat boss (front face, +Y side) ──────────────── + // Proud pad that servo body sits on; 1 mm registration recess + translate([-BASE_W/2, -BASE_T, 0]) + cube([BASE_W, BASE_T + 6, BASE_H]); + } + + // ── Rail clamp bolt bore (M3 through face plate) ───────────────── + translate([0, -BASE_T - e, BASE_H/2]) + rotate([-90, 0, 0]) + cylinder(d = TNUT_BOLT_D, h = BASE_T + TNUT_H + 2*e); + + // ── M3 hex nut pocket (inside T-nut body) ──────────────────────── + translate([0, SLOT_NECK_H + 0.3, BASE_H/2]) + rotate([-90, 0, 0]) + cylinder(d = TNUT_M3_NUT_AF / cos(30), + h = TNUT_M3_NUT_H + 0.3, $fn = 6); + + // ── Servo body recess (1 mm registration pocket in seat face) ──── + translate([-SERVO_W/2 - 0.3, -BASE_T + 6 - 1.0, + (BASE_H - SERVO_H)/2 - 0.3]) + cube([SERVO_W + 0.6, 1.2, SERVO_H + 0.6]); + + // ── Pan servo mount holes (4× M3 in rectangular pattern) ───────── + for (sx = [-SERVO_HOLE_X, SERVO_HOLE_X]) + for (sy = [-SERVO_HOLE_Y, SERVO_HOLE_Y]) + translate([sx, -BASE_T + 6 + e, BASE_H/2 + sy]) + rotate([90, 0, 0]) + cylinder(d = SERVO_M3_D, h = BASE_T + 2*e); + + // ── Pan servo shaft bore (passes shaft through base if needed) ──── + // Centre of shaft at Z = BASE_H/2, no bore needed (shaft exits top) + + // ── Lightening pockets ──────────────────────────────────────────── + translate([0, -BASE_T/2 + 3, BASE_H/2]) + cube([BASE_W - 14, BASE_T - 4, BASE_H - 14], center = true); + } +} + +// ============================================================ +// PART 2 — PAN YOKE +// ============================================================ +// U-shaped yoke that attaches to pan servo horn (below) and carries +// the tilt axis (above). Two vertical arms straddle the camera cradle. +// Tilt servo sits on top of one arm; tilt pivot boss on the other. +// +// Yoke base bolts to pan servo horn (4× M3 on HORN_BC_D bolt circle). +// Pan servo horn spline bore passes through yoke base centre. +// Tilt axis: M4 pivot axle through boss on each arm (X-axis rotation). +// +// Print: upright (yoke in final orientation), PETG, 5 perims, 40% gyroid. +module pan_yoke() { + arm_z_total = YOKE_ARM_H + YOKE_BASE_T; + inner_w = YOKE_INNER_W; + + difference() { + union() { + // ── Yoke base plate (bolts to pan servo horn) ───────────────── + translate([-inner_w/2 - YOKE_WALL_T, 0, 0]) + cube([inner_w + 2*YOKE_WALL_T, YOKE_BASE_T, YOKE_BASE_T]); + + // ── Left arm ────────────────────────────────────────────────── + translate([-inner_w/2 - YOKE_WALL_T, 0, 0]) + cube([YOKE_WALL_T, YOKE_BASE_T, arm_z_total]); + + // ── Right arm (tilt servo side) ─────────────────────────────── + translate([inner_w/2, 0, 0]) + cube([YOKE_WALL_T, YOKE_BASE_T, arm_z_total]); + + // ── Tilt pivot bosses (both arms, X-axis) ───────────────────── + // Left pivot boss (plain pivot — M4 bolt) + translate([-inner_w/2 - YOKE_WALL_T - PIVOT_BOSS_L, + YOKE_BASE_T/2, + YOKE_BASE_T + YOKE_ARM_H/2]) + rotate([0, 90, 0]) + cylinder(d = PIVOT_BOSS_D, h = PIVOT_BOSS_L + YOKE_WALL_T); + + // Right pivot boss (tilt servo horn seat) + translate([inner_w/2, + YOKE_BASE_T/2, + YOKE_BASE_T + YOKE_ARM_H/2]) + rotate([0, 90, 0]) + cylinder(d = PIVOT_BOSS_D + 4, h = PIVOT_BOSS_L + YOKE_WALL_T); + + // ── Tilt servo body seat on right arm top ───────────────────── + translate([inner_w/2, 0, arm_z_total - SERVO_H - 4]) + cube([YOKE_WALL_T + SERVO_D + 2, YOKE_BASE_T, SERVO_H + 4]); + } + + // ── Pan horn spline bore (centre of yoke base) ──────────────────── + translate([0, YOKE_BASE_T/2, YOKE_BASE_T/2]) + rotate([90, 0, 0]) + cylinder(d = HORN_SPLINE_D, h = YOKE_BASE_T + 2*e, + center = true); + + // ── Pan horn bolt holes (4× M3 on HORN_BC_D) ───────────────────── + for (a = [45, 135, 225, 315]) + translate([HORN_BC_D/2 * cos(a), + YOKE_BASE_T/2, + HORN_BC_D/2 * sin(a) + YOKE_BASE_T/2]) + rotate([90, 0, 0]) + cylinder(d = HORN_BOLT_D, h = YOKE_BASE_T + 2*e, + center = true); + + // ── Left tilt pivot bore (M4 clearance) ─────────────────────────── + translate([-inner_w/2 - YOKE_WALL_T - PIVOT_BOSS_L - e, + YOKE_BASE_T/2, + YOKE_BASE_T + YOKE_ARM_H/2]) + rotate([0, 90, 0]) + cylinder(d = PIVOT_D, h = PIVOT_BOSS_L + YOKE_WALL_T + 2*e); + + // ── Right tilt pivot bore (larger — tilt horn plate seats here) ─── + translate([inner_w/2 - e, + YOKE_BASE_T/2, + YOKE_BASE_T + YOKE_ARM_H/2]) + rotate([0, 90, 0]) + cylinder(d = HORN_SPLINE_D, + h = PIVOT_BOSS_L + YOKE_WALL_T + 2*e); + + // ── Tilt servo mount holes in right arm seat ────────────────────── + for (sz = [-SERVO_HOLE_Y, SERVO_HOLE_Y]) + translate([inner_w/2 + YOKE_WALL_T + SERVO_D/2, + YOKE_BASE_T/2, + arm_z_total - SERVO_H/2 + sz]) + rotate([90, 0, 0]) + cylinder(d = SERVO_M3_D, h = YOKE_BASE_T + 2*e, + center = true); + + // ── M3 nut pockets (tilt servo mount, rear of arm seat) ────────── + for (sz = [-SERVO_HOLE_Y, SERVO_HOLE_Y]) + translate([inner_w/2 + YOKE_WALL_T + SERVO_D/2, + YOKE_BASE_T - M3_NUT_H - 0.5, + arm_z_total - SERVO_H/2 + sz]) + rotate([90, 0, 0]) + cylinder(d = M3_NUT_AF / cos(30), h = M3_NUT_H + 0.5, + $fn = 6); + + // ── Lightening slots in yoke arms ───────────────────────────────── + translate([-inner_w/2 - YOKE_WALL_T/2, + YOKE_BASE_T/2, + YOKE_BASE_T + YOKE_ARM_H/2 - 10]) + cube([YOKE_WALL_T - 2, YOKE_BASE_T - 2, YOKE_ARM_H - 24], + center = true); + translate([inner_w/2 + YOKE_WALL_T/2, + YOKE_BASE_T/2, + YOKE_BASE_T + YOKE_ARM_H/2 - 10]) + cube([YOKE_WALL_T - 2, YOKE_BASE_T - 2, YOKE_ARM_H - 30], + center = true); + } +} + +// ============================================================ +// PART 3 — TILT HORN PLATE +// ============================================================ +// Disc plate bolting to tilt ST3215 servo horn on the right yoke arm. +// Servo horn spline centres into disc bore (captured, no free rotation). +// Camera cradle attaches to opposite face via 2× M3 bolts. +// +// Tilt range: ±45° limited by yoke arm geometry. +// Plate thickness HORN_PLATE_T provides stiffness for cantilevered cradle. +// +// Print: flat (disc face down), PETG, 5 perims, 50 % infill. +module tilt_horn_plate() { + plate_od = HORN_OD + 8; // plate OD (4 mm rim outside horn BC) + + difference() { + union() { + // ── Main disc ───────────────────────────────────────────────── + cylinder(d = plate_od, h = HORN_PLATE_T); + + // ── Cradle attachment arm (extends to camera cradle) ────────── + // Rectangular boss on top of disc toward camera + translate([-CAM_W/2, HORN_PLATE_T - e, -CAM_H/2]) + cube([CAM_W, HORN_PLATE_T + 4, CAM_H]); + } + + // ── Servo horn spline bore (centre) ─────────────────────────────── + translate([0, 0, -e]) + cylinder(d = HORN_SPLINE_D, h = HORN_PLATE_T + 2*e); + + // ── Horn bolt holes (4× M3 on HORN_BC_D) ───────────────────────── + for (a = [45, 135, 225, 315]) + translate([HORN_BC_D/2 * cos(a), + HORN_BC_D/2 * sin(a), -e]) + cylinder(d = HORN_BOLT_D, h = HORN_PLATE_T + 2*e); + + // ── Pivot axle bore (M4, coaxial with horn centre) ──────────────── + translate([0, 0, -e]) + cylinder(d = PIVOT_D, h = HORN_PLATE_T + 2*e); + + // ── Cradle attachment bolts (2× M3 in arm boss) ────────────────── + for (cz = [-CAM_H/2 + 6, CAM_H/2 - 6]) + translate([0, HORN_PLATE_T + 2, cz]) + rotate([90, 0, 0]) + cylinder(d = M3_D, h = HORN_PLATE_T + 6 + 2*e); + + // ── M3 hex nut pockets (rear of disc face) ──────────────────────── + for (cz = [-CAM_H/2 + 6, CAM_H/2 - 6]) + translate([0, M3_NUT_H + 0.5, cz]) + rotate([90, 0, 0]) + cylinder(d = M3_NUT_AF / cos(30), + h = M3_NUT_H + 0.5, $fn = 6); + + // ── Weight-relief arcs (between horn bolt holes) ────────────────── + for (a = [0, 90, 180, 270]) + translate([(plate_od/2 - 5) * cos(a), + (plate_od/2 - 5) * sin(a), -e]) + cylinder(d = 6, h = HORN_PLATE_T + 2*e); + } +} + +// ============================================================ +// PART 4 — CAMERA CRADLE +// ============================================================ +// Open-front U-cradle holding D435i via captured 1/4-20 hex nut. +// Front lip retains camera from sliding forward (+Y). +// Vibration-damping pads seat in recessed pockets on inner faces. +// USB-C cable routing channel exits cradle right rear wall. +// +// 1/4-20 captured nut in cradle floor — tighten with standard +// tripod screw or M6→1/4-20 adapter from camera bottom. +// +// Print: cradle-floor-down (flat), PETG, 5 perims, 40 % gyroid. +// No supports needed (overhangs < 45°). +module camera_cradle() { + outer_w = CAM_W + 2*CRADLE_WALL_T; + outer_h = CAM_H + CRADLE_FLOOR_T; + + difference() { + union() { + // ── Cradle body ─────────────────────────────────────────────── + translate([-outer_w/2, 0, 0]) + cube([outer_w, CAM_D + CRADLE_WALL_T, outer_h]); + + // ── Front retaining lip ─────────────────────────────────────── + translate([-outer_w/2, CAM_D + CRADLE_WALL_T - CRADLE_LIP_T, 0]) + cube([outer_w, CRADLE_LIP_T, CRADLE_LIP_H]); + + // ── Cable channel boss (right rear, exits +X side) ──────────── + translate([CAM_W/2 + CRADLE_WALL_T - e, + 0, + CRADLE_FLOOR_T + CAM_H/2 - CABLE_CH_H/2]) + cube([CABLE_CH_W + CRADLE_WALL_T, CAM_D * 0.6, CABLE_CH_H]); + + // ── Tilt horn attachment tabs (left + right, bolt to horn plate)─ + for (sx = [-outer_w/2 - 4, outer_w/2]) + translate([sx, CAM_D/2, CRADLE_FLOOR_T + CAM_H/2 - 6]) + cube([4, 12, 12]); + } + + // ── Camera pocket (hollow interior) ────────────────────────────── + translate([-CAM_W/2, 0, CRADLE_FLOOR_T]) + cube([CAM_W, CAM_D + CRADLE_WALL_T + e, CAM_H + e]); + + // ── 1/4-20 UNC clearance bore (camera tripod thread, bottom) ───── + translate([CAM_MOUNT_X, CAM_MOUNT_Y, -e]) + cylinder(d = QTR20_D, h = CRADLE_FLOOR_T + 2*e); + + // ── 1/4-20 hex nut pocket (captured in cradle floor) ───────────── + translate([CAM_MOUNT_X, CAM_MOUNT_Y, CRADLE_FLOOR_T - QTR20_NUT_H - 0.5]) + cylinder(d = QTR20_NUT_AF / cos(30), + h = QTR20_NUT_H + 0.6, $fn = 6); + + // ── USB-C cable channel (exit through right rear wall) ──────────── + translate([CAM_W/2 - e, + 0, + CRADLE_FLOOR_T + CAM_H/2 - CABLE_CH_H/2]) + cube([CABLE_CH_W + CRADLE_WALL_T + 2*e, + CAM_D * 0.6 + e, CABLE_CH_H]); + + // ── Vibe pad recesses on inner camera-contact faces ─────────────── + // Rear wall recess (camera front face → +Y side of rear wall) + translate([-CAM_W/2 + CRADLE_WALL_T, CRADLE_WALL_T, CRADLE_FLOOR_T]) + cube([CAM_W, PAD_T, CAM_H]); + + // ── Tilt horn bolt holes in attachment tabs ─────────────────────── + for (sx = [-outer_w/2 - 4 - e, outer_w/2 - e]) + translate([sx, CAM_D/2 + 6, CRADLE_FLOOR_T + CAM_H/2]) + rotate([0, 90, 0]) + cylinder(d = M3_D, h = 6 + 2*e); + + // ── M3 nut pockets in attachment tabs ───────────────────────────── + translate([outer_w/2 + 4 - M3_NUT_H - 0.4, + CAM_D/2 + 6, + CRADLE_FLOOR_T + CAM_H/2]) + rotate([0, 90, 0]) + cylinder(d = M3_NUT_AF / cos(30), + h = M3_NUT_H + 0.4, $fn = 6); + translate([-outer_w/2 - 4 - e, + CAM_D/2 + 6, + CRADLE_FLOOR_T + CAM_H/2]) + rotate([0, 90, 0]) + cylinder(d = M3_NUT_AF / cos(30), + h = M3_NUT_H + 0.4, $fn = 6); + + // ── Lightening pockets in cradle walls ──────────────────────────── + for (face_x = [-CAM_W/2 - CRADLE_WALL_T - e, CAM_W/2 - e]) + translate([face_x, CAM_D * 0.2, CRADLE_FLOOR_T + 3]) + cube([CRADLE_WALL_T + 2*e, CAM_D * 0.55, CAM_H - 6]); + } +} + +// ============================================================ +// PART 5 — VIBRATION-DAMPING PAD +// ============================================================ +// Flat pad with transverse PETG flexure ribs pressing against camera body. +// Rib geometry (thin fins ~1.5 mm tall) deflects under camera vibration, +// attenuating high-frequency input from motor/drive-train. +// For superior damping: print in TPU 95A (no infill changes needed). +// Pads seat in recessed pockets in camera cradle inner wall. +// Optional M2 bolt-through at corners or adhesive-back foam tape. +// +// Print: pad-back-face-down, PETG or TPU 95A, 3 perims, 20 % infill. +module vibe_pad() { + rib_count = floor((PAD_W - RIB_W) / RIB_PITCH); + + union() { + // ── Base plate ──────────────────────────────────────────────────── + translate([-PAD_W/2, -PAD_T, -PAD_H/2]) + cube([PAD_W, PAD_T, PAD_H]); + + // ── Flexure ribs (parallel to Z, spaced RIB_PITCH apart) ───────── + for (i = [0 : rib_count - 1]) { + rx = -PAD_W/2 + RIB_PITCH/2 + i * RIB_PITCH + RIB_W/2; + if (rx <= PAD_W/2 - RIB_W/2) + translate([rx, 0, 0]) + cube([RIB_W, RIB_H, PAD_H - 6], center = true); + } + + // ── Corner nubs (M2 bolt-through retention, optional) ───────────── + 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 = 2.4, h = PAD_T + 2*e, center = true); + } + } +} -- 2.47.2