saltylab-firmware/chassis/gimbal_camera_mount.scad

600 lines
29 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.

// ============================================================
// 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);
}
}
}