Add sealed enclosures and sensor housings for outdoor IP54 protection: - ip54_enclosure.scad: main electronics box (Jetson/FC/ESC), O-ring lid, fan+filter duct, PG7/PG9 cable glands, quarter-turn latches, heat sink recesses; gasket DXF export - ip54_sensor_housings.scad: IMX219 clear PC dome (O-ring + anti-fog pocket), D435i IR-transparent window housing (PG7 rear cap), RPLIDAR static clear PC dome base ring (120 mm OD, O-ring, quarter-turn clips) - ip54_BOM.md: hardware list, thermal analysis (≤52°C at 40°C ambient), IP54 compliance checklist, mass ~930g total kit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
551 lines
24 KiB
OpenSCAD
551 lines
24 KiB
OpenSCAD
// ============================================================
|
||
// ip54_sensor_housings.scad — IP54 Sensor Housings
|
||
// Issue: #144 Agent: sl-mechanical Date: 2026-03-01
|
||
// ============================================================
|
||
//
|
||
// Weatherproof housings for sensors exposed to outdoor conditions:
|
||
//
|
||
// Part A — imx219_dome() Clear PC dome for IMX219 CSI camera
|
||
// IP54, anti-fog element pocket, gasket-sealed base ring
|
||
// Part B — d435i_housing() Sealed D435i RealSense housing
|
||
// IP54, IR-transparent PC window, O-ring sealed front frame
|
||
// Part C — rplidar_dome() Spinning dome for RPLIDAR A1M8 scanner
|
||
// Clear PC hemisphere, static base + rotary lip seal
|
||
//
|
||
// Window/dome materials:
|
||
// Camera domes : 2 mm clear polycarbonate (Covestro Makrolon)
|
||
// D435i window : 3 mm IR-transparent PC (transmits 850 nm / 930 nm IR)
|
||
// e.g. Evonik PLEXIGLAS IR (Altuglas 8N, 85% IR850 T)
|
||
// RPLIDAR dome : 1.5 mm clear PC hemisphere, Ø120 mm OD
|
||
// (off-the-shelf: plastic Easter egg halves or custom vac-form)
|
||
//
|
||
// Anti-fog provision:
|
||
// Each camera dome has a pocket for a 1 g silica gel packet (demountable)
|
||
// and an anti-fog coating groove (optionally apply Rain-X or similar).
|
||
//
|
||
// Coordinate convention:
|
||
// Sensor faces +Y (forward). Z = 0 at housing base / mounting face.
|
||
//
|
||
// All housings mount to sensor_rail_brackets.scad T-nut arm ends.
|
||
// Bracket arm interfaces re-used from existing rail brackets.
|
||
//
|
||
// RENDER options:
|
||
// "assembly" all 3 housings side by side
|
||
// "imx219_dome_stl" IMX219 dome base ring (print 1× per camera)
|
||
// "imx219_dome_2d" DXF — dome polycarbonate disc profile
|
||
// "d435i_body_stl" D435i housing body (print 1×)
|
||
// "d435i_window_2d" DXF — IR-transparent PC window profile
|
||
// "d435i_frame_stl" D435i window retention frame (print 1×)
|
||
// "rplidar_base_stl" RPLIDAR dome base ring (print 1×)
|
||
// "rplidar_dome_2d" DXF — RPLIDAR clear PC dome spec
|
||
//
|
||
// Export commands:
|
||
// openscad ip54_sensor_housings.scad -D 'RENDER="imx219_dome_stl"' -o imx219_dome.stl
|
||
// openscad ip54_sensor_housings.scad -D 'RENDER="imx219_dome_2d"' -o imx219_dome_profile.dxf
|
||
// openscad ip54_sensor_housings.scad -D 'RENDER="d435i_body_stl"' -o d435i_body.stl
|
||
// openscad ip54_sensor_housings.scad -D 'RENDER="d435i_window_2d"' -o d435i_window.dxf
|
||
// openscad ip54_sensor_housings.scad -D 'RENDER="d435i_frame_stl"' -o d435i_frame.stl
|
||
// openscad ip54_sensor_housings.scad -D 'RENDER="rplidar_base_stl"' -o rplidar_base.stl
|
||
// openscad ip54_sensor_housings.scad -D 'RENDER="rplidar_dome_2d"' -o rplidar_dome_spec.dxf
|
||
// ============================================================
|
||
|
||
$fn = 64;
|
||
e = 0.01;
|
||
|
||
// ── Fasteners ─────────────────────────────────────────────────────────────────
|
||
M2_D = 2.4;
|
||
M3_D = 3.3;
|
||
M4_D = 4.3;
|
||
|
||
// ── O-ring groove standard (2 mm cord, 70 % compression) ─────────────────────
|
||
ORING_W = 2.2;
|
||
ORING_D = 1.7;
|
||
|
||
// ── IMX219 sensor interface (matches sensor_rail_brackets.scad) ───────────────
|
||
IMX_PCB_W = 32.0;
|
||
IMX_PCB_H = 32.0;
|
||
IMX_HOLE_SPC = 24.0;
|
||
|
||
// ── D435i sensor interface ────────────────────────────────────────────────────
|
||
D4_BODY_W = 90.0;
|
||
D4_BODY_D = 25.0;
|
||
D4_BODY_H = 25.0;
|
||
D4_MOUNT_D = 6.5;
|
||
|
||
// ── RPLIDAR A1M8 sensor interface ─────────────────────────────────────────────
|
||
RPL_BODY_D = 70.0;
|
||
RPL_BODY_H = 40.0; // approximate scan head height
|
||
RPL_MOTOR_D = 30.0; // motor spindle OD (approximate, lower portion)
|
||
RPL_BC_D = 58.0;
|
||
RPL_BOLT_D = 3.3;
|
||
|
||
// ============================================================
|
||
// ── PART A: IMX219 CLEAR DOME ─────────────────────────────────────────────────
|
||
// ============================================================
|
||
//
|
||
// Base ring: printed PETG, gasket-sealed, mounts to sensor rail bracket.
|
||
// Dome: 2 mm clear PC disc or hemisphere (separate purchase / fabrication).
|
||
//
|
||
// Design overview:
|
||
// • Circular base ring sized to accept the dome OD (snap or screw)
|
||
// • O-ring groove at dome seating face → IP54
|
||
// • Camera PCB attaches inside on 4× M2 standoffs
|
||
// • Silica gel pocket on inner wall (1 g sachet, removable via dome)
|
||
// • Anti-fog groove (optional coating during assembly)
|
||
// • Base flange: M3 bolt pattern matching IMX219 bracket arm
|
||
|
||
IMX_DOME_OD = 55.0; // clear PC dome outer diameter
|
||
IMX_DOME_RIM_T = 4.0; // base ring wall thickness
|
||
IMX_DOME_H = 35.0; // inner cavity height (lens to dome apex clearance)
|
||
IMX_RING_H = 18.0; // base ring height (below dome seating face)
|
||
IMX_PCB_STOFF = 4.0; // PCB standoff height inside dome
|
||
IMX_TILT_DEG = 10.0; // dome tilt (same as imx219 bracket)
|
||
|
||
module imx219_dome() {
|
||
base_od = IMX_DOME_OD + 2*IMX_DOME_RIM_T;
|
||
base_id = IMX_DOME_OD - 2*IMX_DOME_RIM_T; // internal cavity OD
|
||
|
||
difference() {
|
||
union() {
|
||
// ── Base ring cylinder ─────────────────────────────────────
|
||
cylinder(d = base_od, h = IMX_RING_H);
|
||
|
||
// ── Dome retention lip (retains dome from below) ───────────
|
||
// Dome rests on this lip; screw-on retainer ring holds it.
|
||
translate([0, 0, IMX_RING_H])
|
||
difference() {
|
||
cylinder(d = base_od, h = 4);
|
||
// Dome inner bore (dome drops in)
|
||
translate([0, 0, -e])
|
||
cylinder(d = IMX_DOME_OD + 0.4, h = 4 + 2*e);
|
||
}
|
||
|
||
// ── Base flange (mounts to bracket arm) ───────────────────
|
||
// Square flange extending below ring
|
||
translate([-base_od/2, -base_od/2, -8])
|
||
cube([base_od, base_od, 8]);
|
||
}
|
||
|
||
// ── Internal cavity ────────────────────────────────────────────
|
||
translate([0, 0, -e])
|
||
cylinder(d = IMX_DOME_OD - 2*IMX_DOME_RIM_T + 0.4,
|
||
h = IMX_RING_H + 4 + 2*e);
|
||
|
||
// ── O-ring groove at dome seating face ─────────────────────────
|
||
translate([0, 0, IMX_RING_H - ORING_D])
|
||
difference() {
|
||
cylinder(d = IMX_DOME_OD - 2 + ORING_W,
|
||
h = ORING_D + e);
|
||
cylinder(d = IMX_DOME_OD - 2 - ORING_W,
|
||
h = ORING_D + e);
|
||
}
|
||
|
||
// ── 4× M2 PCB standoff bores (square 24×24 mm pattern) ────────
|
||
for (sx = [-1, 1]) for (sy = [-1, 1])
|
||
translate([sx*IMX_HOLE_SPC/2, sy*IMX_HOLE_SPC/2, -e])
|
||
cylinder(d = M2_D, h = IMX_RING_H + 4 + 2*e);
|
||
|
||
// ── Base flange M3 bolt holes (4 corners, match bracket arm) ──
|
||
for (bx = [-1, 1]) for (by = [-1, 1])
|
||
translate([bx*(base_od/2 - 5),
|
||
by*(base_od/2 - 5), -8 - e])
|
||
cylinder(d = M3_D, h = 8 + 2*e);
|
||
|
||
// ── Silica gel pocket (inside ring wall, rear) ─────────────────
|
||
// Small pocket ~25×20×5 mm accessible when dome removed
|
||
translate([0, -(base_od/2 - 3), IMX_RING_H/2])
|
||
cube([20, 6, 12], center = true);
|
||
|
||
// ── FFC cable exit slot (bottom of ring) ──────────────────────
|
||
translate([0, 0, -8 - e])
|
||
cube([10, base_od + 2*e, 5], center = true);
|
||
}
|
||
|
||
// ── M2 standoffs (inside ring) ──────────────────────────────────────
|
||
for (sx = [-1, 1]) for (sy = [-1, 1])
|
||
translate([sx*IMX_HOLE_SPC/2, sy*IMX_HOLE_SPC/2, 0])
|
||
difference() {
|
||
cylinder(d = 5, h = IMX_PCB_STOFF);
|
||
cylinder(d = M2_D, h = IMX_PCB_STOFF + e);
|
||
}
|
||
}
|
||
|
||
// ── IMX219 dome PC disc profile (DXF) ────────────────────────────────────────
|
||
// 2D profile for laser-cutting / ordering the clear PC dome disc.
|
||
// Material: 2 mm clear polycarbonate.
|
||
module imx219_dome_profile_2d() {
|
||
circle(d = IMX_DOME_OD);
|
||
}
|
||
|
||
// ── IMX219 retainer ring (screw-on, holds dome in base) ──────────────────────
|
||
// M24 metric thread (modelled as cylindrical press-fit with snap grooves).
|
||
module imx219_retainer_ring() {
|
||
base_od = IMX_DOME_OD + 2*IMX_DOME_RIM_T;
|
||
ring_h = 8.0;
|
||
|
||
difference() {
|
||
cylinder(d = base_od, h = ring_h);
|
||
translate([0, 0, -e])
|
||
cylinder(d = IMX_DOME_OD + 0.6, h = ring_h + 2*e);
|
||
// Grip notches
|
||
for (i = [0:5])
|
||
rotate([0, 0, i*60])
|
||
translate([base_od/2 - 1, 0, ring_h/2])
|
||
cylinder(d = 2, h = ring_h + e, center = true);
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// ── PART B: D435i REALSENSE SEALED HOUSING ───────────────────────────────────
|
||
// ============================================================
|
||
//
|
||
// Body: PETG printed U-channel wraps D435i body.
|
||
// Camera inserts from front; front window frame retains it.
|
||
// Window: 3 mm IR-transparent PC (88 × 22 mm), O-ring sealed.
|
||
// Transmits >85% at 850 nm (IR stereo projector + receiver).
|
||
// Rear cap: snap-on / screw-on PETG cap, O-ring sealed.
|
||
// Cable exits via PG7 gland on rear cap.
|
||
// Top: flat, M3 holes for mounting to sensor rail D435i bracket.
|
||
//
|
||
// Internal clearance: 94 × 29 × 29 mm (D4 body + 2 mm each side).
|
||
|
||
D4H_INT_W = D4_BODY_W + 4; // internal width clearance
|
||
D4H_INT_D = D4_BODY_D + 4; // internal depth
|
||
D4H_INT_H = D4_BODY_H + 4; // internal height
|
||
D4H_WALL = 3.5; // housing wall thickness
|
||
D4H_WIN_W = D4_BODY_W - 2; // window aperture width
|
||
D4H_WIN_H = D4_BODY_H - 2; // window aperture height
|
||
D4H_WIN_T = 3.0; // window thickness (PC)
|
||
D4H_WIN_REC= 1.5; // window recess depth (sits into frame)
|
||
D4H_TILT = 8.0; // nose-down tilt, matches existing mount
|
||
|
||
module d435i_housing_body() {
|
||
out_w = D4H_INT_W + 2*D4H_WALL;
|
||
out_d = D4H_INT_D + 2*D4H_WALL;
|
||
out_h = D4H_INT_H + 2*D4H_WALL;
|
||
|
||
difference() {
|
||
// ── Outer shell ──────────────────────────────────────────────
|
||
cube([out_w, out_d, out_h], center = true);
|
||
|
||
// ── Internal cavity ─────────────────────────────────────────
|
||
cube([D4H_INT_W, D4H_INT_D, D4H_INT_H + 2*e], center = true);
|
||
|
||
// ── Front window aperture (sensor-facing face, +Y) ──────────
|
||
translate([0, out_d/2 - D4H_WALL - e, 0])
|
||
cube([D4H_WIN_W, D4H_WALL + 2*e, D4H_WIN_H], center = true);
|
||
|
||
// ── Window recess (window sits flush in face) ────────────────
|
||
translate([0, out_d/2 - D4H_WIN_REC, 0])
|
||
cube([D4H_WIN_W + 2*D4H_WIN_REC,
|
||
D4H_WIN_REC + e,
|
||
D4H_WIN_H + 2*D4H_WIN_REC], center = true);
|
||
|
||
// ── O-ring groove around front aperture ─────────────────────
|
||
// Groove on front face, surrounds window aperture
|
||
translate([0, out_d/2 - ORING_D - 0.5, 0])
|
||
difference() {
|
||
cube([D4H_WIN_W + 2*(D4H_WIN_REC + ORING_W),
|
||
ORING_D + e,
|
||
D4H_WIN_H + 2*(D4H_WIN_REC + ORING_W)], center = true);
|
||
cube([D4H_WIN_W + 2*D4H_WIN_REC,
|
||
ORING_D + 2*e,
|
||
D4H_WIN_H + 2*D4H_WIN_REC], center = true);
|
||
}
|
||
|
||
// ── Rear cap opening (camera inserted from rear, cap closes it) ─
|
||
translate([0, -out_d/2 - e, 0])
|
||
cube([D4H_INT_W - 2, out_d/4, D4H_INT_H - 2], center = true);
|
||
|
||
// ── PG7 gland hole in rear cap mating face ───────────────────
|
||
translate([0, -out_d/2 - e, 0])
|
||
rotate([90, 0, 0])
|
||
cylinder(d = 12.7, h = D4H_WALL + 2*e);
|
||
|
||
// ── 1/4-20 captured nut for tripod/bracket mount (bottom) ────
|
||
translate([0, 0, -out_h/2 - e])
|
||
rotate([180, 0, 0])
|
||
cylinder(d = 6.5, h = D4H_WALL + 2*e);
|
||
translate([0, 0, -out_h/2 + 4])
|
||
cylinder(d = 11.4/cos(30), h = 5, $fn = 6);
|
||
|
||
// ── M3 mounting holes on top (sensor rail bracket interface) ──
|
||
for (mx = [-20, 0, 20])
|
||
translate([mx, 0, out_h/2 - e])
|
||
cylinder(d = M3_D, h = D4H_WALL + 2*e);
|
||
}
|
||
}
|
||
|
||
// ── D435i window retention frame ─────────────────────────────────────────────
|
||
// Screws onto front of housing body, sandwiches the IR-transparent PC window.
|
||
// 4× M2.5 screws at corners pull frame against O-ring seal.
|
||
module d435i_window_frame() {
|
||
out_w = D4H_INT_W + 2*D4H_WALL;
|
||
out_h = D4H_INT_H + 2*D4H_WALL;
|
||
frame_t = 5.0;
|
||
frame_w = out_w + 2;
|
||
frame_h = out_h + 2;
|
||
|
||
difference() {
|
||
// Frame plate
|
||
cube([frame_w, frame_t, frame_h], center = true);
|
||
|
||
// Central window reveal (slightly smaller than aperture — shows window)
|
||
cube([D4H_WIN_W - 4, frame_t + 2*e, D4H_WIN_H - 4], center = true);
|
||
|
||
// Window recess (PC sits in this pocket on rear face)
|
||
translate([0, -frame_t/2 + D4H_WIN_REC/2, 0])
|
||
cube([D4H_WIN_W + 0.4, D4H_WIN_REC + e, D4H_WIN_H + 0.4],
|
||
center = true);
|
||
|
||
// 4× M2.5 mounting screws at corners
|
||
for (fx = [-1, 1]) for (fz = [-1, 1])
|
||
translate([fx*(frame_w/2 - 5), -frame_t/2 - e,
|
||
fz*(frame_h/2 - 5)])
|
||
rotate([90, 0, 0])
|
||
cylinder(d = 2.8, h = frame_t + 2*e);
|
||
}
|
||
}
|
||
|
||
// ── D435i window profile (DXF) ───────────────────────────────────────────────
|
||
// Profile for laser-cutting 3 mm IR-transparent PC.
|
||
module d435i_window_profile_2d() {
|
||
square([D4H_WIN_W, D4H_WIN_H], center = true);
|
||
}
|
||
|
||
// ── D435i rear cap ────────────────────────────────────────────────────────────
|
||
// O-ring sealed rear cap. Snap-over with 2× M3 retention screws.
|
||
module d435i_rear_cap() {
|
||
out_w = D4H_INT_W + 2*D4H_WALL;
|
||
out_h = D4H_INT_H + 2*D4H_WALL;
|
||
cap_t = 5.0;
|
||
|
||
difference() {
|
||
union() {
|
||
cube([out_w, cap_t, out_h], center = true);
|
||
// Lip that wraps inside housing rear opening
|
||
translate([0, cap_t/2 - e, 0])
|
||
cube([D4H_INT_W - 2 - 0.4,
|
||
6,
|
||
D4H_INT_H - 2 - 0.4], center = true);
|
||
}
|
||
|
||
// PG7 cable gland
|
||
rotate([90, 0, 0])
|
||
cylinder(d = 12.7, h = cap_t + 6 + 2*e, center = true);
|
||
|
||
// O-ring groove on lip perimeter
|
||
translate([0, cap_t/2 + 3, 0])
|
||
difference() {
|
||
cube([D4H_INT_W - 2 + ORING_W,
|
||
ORING_D,
|
||
D4H_INT_H - 2 + ORING_W], center = true);
|
||
cube([D4H_INT_W - 2 - ORING_W,
|
||
ORING_D + e,
|
||
D4H_INT_H - 2 - ORING_W], center = true);
|
||
}
|
||
|
||
// M3 retention screw holes
|
||
for (mz = [-1, 1])
|
||
translate([0, -cap_t/2 - e, mz*(out_h/2 - 8)])
|
||
rotate([90, 0, 0])
|
||
cylinder(d = M3_D, h = cap_t + 2*e);
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// ── PART C: RPLIDAR SPINNING DOME ────────────────────────────────────────────
|
||
// ============================================================
|
||
//
|
||
// The RPLIDAR A1M8 scan head spins continuously. A transparent PC dome
|
||
// covers the entire scanner, protecting it from water and debris.
|
||
//
|
||
// Architecture:
|
||
// • Static base ring: mounts to robot deck; RPLIDAR mounts inside on its
|
||
// standard 4× M3 bolt circle (Ø58 mm BC).
|
||
// • Spinning clear dome: rotates with scan head OR is statically mounted
|
||
// with sufficient clearance for the scan head to spin inside.
|
||
// ★ Design choice here: STATIC dome, sized Ø120 mm OD × 95 mm tall.
|
||
// The scanner spins inside the static dome. No rotary seal needed.
|
||
// Scan laser exits through dome walls at all angles (clear PC transmits
|
||
// the 785 nm laser with <5 % absorption at 1.5 mm wall thickness).
|
||
// • Dome lip sits in O-ring groove in base ring → IP54 seal.
|
||
// • Dome retention: 3× M3 captive-nut clips, quarter-turn removal.
|
||
//
|
||
// Dome spec: custom vac-form, OR cut top from clear PC tube Ø120 mm OD.
|
||
// Off-the-shelf option: clear plastic cylinder Ø120×120 mm (party supply).
|
||
// Top cap: 1.5–2 mm clear PC disc, Ø120 mm.
|
||
|
||
RPL_DOME_OD = 120.0; // dome outer diameter
|
||
RPL_DOME_H = 95.0; // dome total height (covers scanner + motor)
|
||
RPL_DOME_T = 2.0; // dome wall thickness (clear PC)
|
||
RPL_BASE_H = 20.0; // base ring height
|
||
RPL_BASE_WALL = 5.0; // base ring wall thickness
|
||
RPL_CLEAR = 5.0; // radial clearance between scanner and dome wall
|
||
|
||
// Base ring inner bore must clear RPLIDAR motor (Ø70 mm body + clearance)
|
||
RPL_BASE_BORE = RPL_BODY_D + 2*RPL_CLEAR; // = 80 mm
|
||
|
||
module rplidar_dome_base() {
|
||
base_od = RPL_DOME_OD + 2*RPL_BASE_WALL;
|
||
|
||
difference() {
|
||
union() {
|
||
// ── Outer base cylinder ──────────────────────────────────
|
||
cylinder(d = base_od, h = RPL_BASE_H);
|
||
|
||
// ── Dome seat lip (raised inner lip, dome rests on top) ──
|
||
translate([0, 0, RPL_BASE_H])
|
||
difference() {
|
||
cylinder(d = base_od, h = 4);
|
||
// Dome drops over this; 0.5 mm radial clearance
|
||
translate([0, 0, -e])
|
||
cylinder(d = RPL_DOME_OD + 1, h = 4 + 2*e);
|
||
}
|
||
}
|
||
|
||
// ── RPLIDAR body bore (scanner sits inside base ring) ────────
|
||
translate([0, 0, -e])
|
||
cylinder(d = RPL_BASE_BORE, h = RPL_BASE_H + 4 + 2*e);
|
||
|
||
// ── 4× M3 bolt holes — RPLIDAR mounting (Ø58 mm BC, 45° off) ─
|
||
for (a = [45, 135, 225, 315])
|
||
translate([RPL_BC_D/2 * cos(a), RPL_BC_D/2 * sin(a), -e])
|
||
cylinder(d = RPL_BOLT_D, h = RPL_BASE_H + 4 + 2*e);
|
||
|
||
// ── O-ring groove at dome seating face ───────────────────────
|
||
// Groove in top face of base ring; dome rim presses onto O-ring.
|
||
translate([0, 0, RPL_BASE_H + 4 - ORING_D])
|
||
difference() {
|
||
cylinder(d = RPL_DOME_OD + ORING_W, h = ORING_D + e);
|
||
cylinder(d = RPL_DOME_OD - ORING_W, h = ORING_D + e);
|
||
}
|
||
|
||
// ── 3× M3 dome retention clip pockets (quarter-turn inserts) ─
|
||
for (a = [0, 120, 240])
|
||
translate([RPL_DOME_OD/2 * cos(a),
|
||
RPL_DOME_OD/2 * sin(a),
|
||
RPL_BASE_H + 1])
|
||
rotate([0, 0, a]) {
|
||
// Clip slot: L-shaped slot for quarter-turn retention
|
||
translate([0, 0, 0])
|
||
cube([M3_D + 0.5, 8, 8], center = true);
|
||
translate([3, 0, -3])
|
||
cube([M3_D + 0.5 + 6, 8, 3 + e], center = true);
|
||
}
|
||
|
||
// ── Cable pass-through (motor USB + power) ───────────────────
|
||
// Slot in base ring floor for cable routing
|
||
translate([0, RPL_BASE_BORE/2, -e])
|
||
cube([12, RPL_BASE_BORE/2, 6], center = true);
|
||
|
||
// ── Deck mounting holes (4× M4 on standard bolt circle) ──────
|
||
for (a = [0, 90, 180, 270])
|
||
translate([(base_od/2 - 8) * cos(a),
|
||
(base_od/2 - 8) * sin(a), -e])
|
||
cylinder(d = M4_D, h = RPL_BASE_H + 4 + 2*e);
|
||
}
|
||
}
|
||
|
||
// ── RPLIDAR dome profile (DXF — cylindrical tube spec) ───────────────────────
|
||
// 2D cross-section profile for clear PC dome cylinder purchase/fabrication.
|
||
// Cut a length of Ø120 mm clear PC tube; add a disc cap.
|
||
module rplidar_dome_profile_2d() {
|
||
// Cross-section annulus: OD = RPL_DOME_OD, wall = RPL_DOME_T
|
||
difference() {
|
||
circle(d = RPL_DOME_OD);
|
||
circle(d = RPL_DOME_OD - 2*RPL_DOME_T);
|
||
}
|
||
}
|
||
|
||
// ── RPLIDAR dome top cap (clear PC disc — DXF profile only) ──────────────────
|
||
module rplidar_dome_cap_2d() {
|
||
circle(d = RPL_DOME_OD - RPL_DOME_T);
|
||
}
|
||
|
||
// ── RPLIDAR dome retention clip ────────────────────────────────────────────────
|
||
// Printed clip slides into quarter-turn slot on base ring.
|
||
// Captive M3 bolt tip engages hole drilled in dome wall.
|
||
// Print: PETG, 5 perims, 60% infill. 3× per dome.
|
||
module rplidar_dome_clip() {
|
||
difference() {
|
||
union() {
|
||
// T-body (fits in L-slot)
|
||
cube([M3_D + 2, 8, 8], center = true);
|
||
// Engagement lug
|
||
translate([3, 0, -3])
|
||
cube([M3_D + 8, 7, 3], center = true);
|
||
}
|
||
// M3 bore (bolt presses against dome wall)
|
||
rotate([0, 90, 0])
|
||
cylinder(d = M3_D, h = M3_D + 10, center = true);
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// RENDER DISPATCH
|
||
// ============================================================
|
||
RENDER = "assembly";
|
||
|
||
if (RENDER == "assembly") {
|
||
assembly();
|
||
} else if (RENDER == "imx219_dome_stl") {
|
||
imx219_dome();
|
||
} else if (RENDER == "imx219_dome_2d") {
|
||
projection(cut = true) translate([0, 0, -0.5])
|
||
linear_extrude(1) imx219_dome_profile_2d();
|
||
} else if (RENDER == "d435i_body_stl") {
|
||
d435i_housing_body();
|
||
} else if (RENDER == "d435i_window_2d") {
|
||
projection(cut = true) translate([0, 0, -0.5])
|
||
linear_extrude(1) d435i_window_profile_2d();
|
||
} else if (RENDER == "d435i_frame_stl") {
|
||
d435i_window_frame();
|
||
} else if (RENDER == "rplidar_base_stl") {
|
||
rplidar_dome_base();
|
||
} else if (RENDER == "rplidar_dome_2d") {
|
||
projection(cut = true) translate([0, 0, -0.5])
|
||
linear_extrude(1) rplidar_dome_profile_2d();
|
||
}
|
||
|
||
// ============================================================
|
||
// ASSEMBLY PREVIEW
|
||
// ============================================================
|
||
module assembly() {
|
||
// IMX219 dome (left)
|
||
color("DodgerBlue", 0.80)
|
||
translate([-120, 0, 0])
|
||
imx219_dome();
|
||
|
||
// IMX219 clear dome ghost
|
||
%color("LightCyan", 0.25)
|
||
translate([-120, 0, 18])
|
||
cylinder(d = IMX_DOME_OD + 0.4, h = IMX_DOME_H);
|
||
|
||
// D435i housing (centre)
|
||
color("DarkSlateGray", 0.85)
|
||
translate([0, 0, 0])
|
||
d435i_housing_body();
|
||
|
||
// D435i window frame ghost
|
||
%color("LightBlue", 0.30)
|
||
translate([0, (D4H_INT_D + 2*D4H_WALL)/2, 0])
|
||
rotate([90, 0, 0])
|
||
d435i_window_frame();
|
||
|
||
// RPLIDAR dome base (right)
|
||
color("OliveDrab", 0.85)
|
||
translate([160, 0, 0])
|
||
rplidar_dome_base();
|
||
|
||
// RPLIDAR clear dome ghost
|
||
%color("LightCyan", 0.25)
|
||
translate([160, 0, RPL_BASE_H + 4])
|
||
cylinder(d = RPL_DOME_OD, h = RPL_DOME_H - RPL_BASE_H - 4);
|
||
|
||
// Ghost scanner inside dome
|
||
%color("Black", 0.35)
|
||
translate([160, 0, RPL_BASE_H/2])
|
||
cylinder(d = RPL_BODY_D, h = RPL_BODY_H, center = true);
|
||
}
|