Add parametric 3D-printable enclosure (OpenSCAD)

A printable reimagining of the wooden Kidsbox: rectangular cabinet with two
front speakers, removable top lid (NFC tag rests there, reader mounts beneath),
and swappable speaker mounts.

- kidsbox.scad: master model + part dispatcher (body/lid/speaker_adapter/grill)
- params.scad: all tunable dimensions in one place
- lib.scad: helper modules (rounded shell, bosses, bolt circles)
- Fixed front-panel speaker interface + swappable adapter so any driver fits
  by printing a new adapter (edit spk_* params, re-export)
- Internal mounts: 2x18650 cradle, ESP32-LyraT standoffs, RC522 under lid,
  3 buttons on lid, USB/charge + power-switch side ports, back-wall vents
- Snap-in speaker grills
- Makefile to export STLs and render previews (headless via xvfb)
- Exported STLs and preview renders included
- Link enclosure from main README

Dimensions are sensible defaults from the reference photos and common parts;
README flags the fit-critical values to verify before mass-printing.

https://claude.ai/code/session_01MhAnh68DPNnTjcKqjNYDmk
This commit is contained in:
Claude committed 2026-06-13 10:39:55 +00:00
1 parent 64475dbf5e
commit 75dd63b56f
17 files changed
+170230

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
# Regenerate Kidsbox enclosure STLs and preview images from the OpenSCAD source.
#
# make stl export all printable STLs into stl/
# make img render preview PNGs into img/ (needs a display or xvfb)
# make all both
# make clean remove generated STLs and images
#
# Override the renderer if openscad isn't on PATH: make OPENSCAD=/path/to/openscad
OPENSCAD ?= openscad
SRC = kidsbox.scad params.scad lib.scad
PARTS = body lid speaker_adapter grill
STL_FILES = $(addprefix stl/,$(addsuffix .stl,$(PARTS)))
# Software GL so it works headless under xvfb on a server.
RENDER_ENV = LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe
XVFB = xvfb-run -a -s "-screen 0 1280x960x24"
IMGOPTS = --viewall --autocenter --imgsize=1280,960 --colorscheme=Tomorrow
.PHONY: all stl img clean
all: stl img
stl: $(STL_FILES)
stl/%.stl: $(SRC)
@mkdir -p stl
$(OPENSCAD) -D 'part="$*"' -o $@ kidsbox.scad
img: $(SRC)
@mkdir -p img
$(RENDER_ENV) $(XVFB) $(OPENSCAD) -D 'part="assembly"' -D 'explode=0' \
--camera=0,0,0,64,0,28,0 $(IMGOPTS) -o img/hero.png kidsbox.scad
$(RENDER_ENV) $(XVFB) $(OPENSCAD) -D 'part="assembly"' \
--camera=0,0,0,62,0,28,0 $(IMGOPTS) -o img/assembly.png kidsbox.scad
$(RENDER_ENV) $(XVFB) $(OPENSCAD) -D 'part="body"' \
--camera=0,0,0,60,0,25,0 $(IMGOPTS) -o img/body.png kidsbox.scad
$(RENDER_ENV) $(XVFB) $(OPENSCAD) -D 'part="lid"' \
--camera=0,0,0,60,0,200,0 $(IMGOPTS) -o img/lid_top.png kidsbox.scad
$(RENDER_ENV) $(XVFB) $(OPENSCAD) -D 'part="speaker_adapter"' \
--camera=0,0,0,60,0,20,0 $(IMGOPTS) -o img/speaker_adapter.png kidsbox.scad
$(RENDER_ENV) $(XVFB) $(OPENSCAD) -D 'part="grill"' \
--camera=0,0,0,55,0,20,0 $(IMGOPTS) -o img/grill.png kidsbox.scad
clean:
rm -f stl/*.stl img/*.png
+145
View File
@@ -0,0 +1,145 @@
# Kidsbox — 3D-printable enclosure
A parametric, fully 3D-printable reimagining of the original wooden Kidsbox.
The design keeps the look of the hand-built box — a rectangular cabinet with two
front-firing speakers and a removable top where the NFC tag rests — but makes the
**speaker mount swappable** so you can print an adapter to fit whatever drivers you
have on hand.
![Assembly](img/hero.png)
> Modelled in [OpenSCAD](https://openscad.org). Everything is driven by
> [`params.scad`](params.scad) — change a few numbers, re-export, and print.
---
## What's in the box
| Part | Qty | File | Notes |
|---|---|---|---|
| Body / cabinet | 1 | `stl/body.stl` | Shell with all internal mounts |
| Lid | 1 | `stl/lid.stl` | Buttons + NFC reader mount underneath |
| Speaker adapter | 2 | `stl/speaker_adapter.stl` | **Swappable** — print to fit your driver |
| Speaker grill | 2 | `stl/grill.stl` | Snaps into the sound opening |
The body carries mounts for everything else:
- **2× 18650 battery holder** — open-top cradle with retaining lips (`batt_*` params)
- **ESP32-LyraT v4.3** — four floor standoffs (`lyrat_*` params)
- **RC522 NFC reader** — pocket on the lid underside, so a tag placed on top reads
through the thin (`lid_t = 2.4 mm`) PLA window
- **3 buttons** (vol−, play/pause, vol+) — 12 mm holes in a row near the lid's front edge
- **Speaker interface** — a fixed bolt pattern the swappable adapters mount to
- **Side ports** — USB/charging cutout and a power-switch hole
- **Vents** on the back wall
| Body | Lid (underside) |
|---|---|
| ![Body](img/body.png) | ![Lid](img/lid.png) |
---
## Printing
| Setting | Recommendation |
|---|---|
| Material | PLA or PETG (PLA is fine; keep metal away from the NFC window) |
| Layer height | 0.2 mm |
| Walls / perimeters | 3+ |
| Infill | 15–20 % |
| Supports | **Body:** none (print open-side up). **Lid:** none. **Adapter/Grill:** none. |
| Orientation | Body open-top up; lid top-face down; adapter & grill flat |
The body is ~190 × 115 × 110 mm, so it fits a 220 × 220 bed (Ender-3 class) and larger.
---
## Assembly
1. **Speakers** → bolt each driver to a `speaker_adapter` using the adapter's inner
screw holes, then bolt the adapter to the four bosses inside the front wall (M3).
2. **Grills** → press a `grill` into each sound opening from the front.
3. **Electronics** → screw the ESP32-LyraT onto the floor standoffs; drop the 18650
holder into the cradle; route the USB/charge lead to the side cutout.
4. **NFC reader** → screw the RC522 to the four posts under the lid.
5. **Buttons** → fit the three 12 mm momentary switches into the lid holes.
6. **Close up** → lower the lid (the lip locates it) and drive four M3 screws into the
corner bosses.
---
## Swapping speakers (the whole point)
The front wall never changes — it has a **fixed interface**: a `spk_sound_d` opening
and four bosses on a `spk_bolt_d` circle. Every speaker bolts to the box through an
**adapter** that you print to match *your* driver. To make a new adapter, edit the
adapter block in [`params.scad`](params.scad):
```scad
// ---- swappable speaker ADAPTER (edit for YOUR speaker) ----
spk_frame_d = 66; // your speaker's outer frame diameter
spk_cutout_d = 58; // cone clearance (a bit less than the cone surround)
spk_screw_count = 4; // your speaker's mounting-hole count
spk_screw_bolt_d = 60; // your speaker's mounting-hole bolt circle
```
Then re-export just that part:
```bash
openscad -D 'part="speaker_adapter"' -o stl/speaker_adapter.stl kidsbox.scad
```
Leave the `spk_sound_d` / `spk_bolt_d` (panel) values alone and your new adapter will
drop straight onto the same box.
| Speaker adapter (swappable) | Grill |
|---|---|
| ![Adapter](img/speaker_adapter.png) | ![Grill](img/grill.png) |
---
## Regenerating everything
```bash
make stl # export all STLs into stl/
make img # render preview PNGs (needs a display, or xvfb on a server)
make all
```
Or one part at a time:
```bash
openscad -D 'part="body"' -o stl/body.stl kidsbox.scad
openscad -D 'part="lid"' -o stl/lid.stl kidsbox.scad
openscad -D 'part="speaker_adapter"' -o stl/speaker_adapter.stl kidsbox.scad
openscad -D 'part="grill"' -o stl/grill.stl kidsbox.scad
```
---
## ⚠️ Verify before mass-printing
These dimensions are sensible defaults derived from the reference photos and common
parts, **not** from calipers on your exact hardware. Before printing the whole set,
print a quick test of the fit-critical features and confirm against your parts:
- ESP32-LyraT mounting-hole rectangle (`lyrat_hole_dx`, `lyrat_hole_dy`)
- RC522 mounting-hole rectangle (`bx`, `by` in the `rc522_mount()` module)
- Your 18650 holder footprint (`batt_holder_*`)
- Speaker frame/cone/bolt-circle (`spk_frame_d`, `spk_cutout_d`, `spk_screw_bolt_d`)
A cheap way to validate the speaker fit is to print one `speaker_adapter` first.
---
## Files
```
enclosure/
├── kidsbox.scad master model + part dispatcher
├── params.scad all tunable dimensions ← edit here
├── lib.scad helper modules
├── Makefile export/render targets
├── stl/ exported STLs
└── img/ rendered previews
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+275
View File
@@ -0,0 +1,275 @@
// ============================================================================
// Kidsbox — parametric 3D-printable enclosure
// ---------------------------------------------------------------------------
// Open in OpenSCAD and set `part` (top-right) or export from the CLI:
//
// openscad -D 'part="body"' -o stl/body.stl kidsbox.scad
// openscad -D 'part="lid"' -o stl/lid.stl kidsbox.scad
// openscad -D 'part="speaker_adapter"' -o stl/speaker_adapter.stl kidsbox.scad
// openscad -D 'part="grill"' -o stl/grill.stl kidsbox.scad
//
// `part="assembly"` shows everything together (for preview only).
// All tunables live in params.scad.
// ============================================================================
include <params.scad>
use <lib.scad>
part = "assembly"; // body | lid | speaker_adapter | grill | assembly
// ---------------------------------------------------------------------------
// Front-panel speaker interface (the FIXED "mount for the speaker mount")
// ---------------------------------------------------------------------------
// Subtractive: the sound opening + an outer recess for the grill flange.
module speaker_cut(cx, cz) {
translate([cx, 0, cz]) rotate([-90, 0, 0]) {
// sound opening straight through the front wall
translate([0, 0, -1]) cylinder(h = wall + 2, d = spk_sound_d);
// grill flange recess on the OUTSIDE face
translate([0, 0, -0.01]) cylinder(h = grill_t + 0.01, d = spk_sound_d + 8);
}
}
// Additive: 4 bosses on the INSIDE so a swappable adapter can bolt on.
module speaker_bosses(cx, cz) {
translate([cx, 0, cz]) rotate([-90, 0, 0])
translate([0, 0, wall - 0.5])
bolt_circle(spk_bolt_d, spk_bolt_count, 45)
boss(spk_boss_d, spk_boss_h + 0.5, 2.6); // self-tap for M3
}
// ---------------------------------------------------------------------------
// Button holes through the lid (row near the front edge)
// ---------------------------------------------------------------------------
module button_cuts() {
for (i = [0 : btn_count - 1]) {
x = box_w/2 + (i - (btn_count - 1) / 2) * btn_pitch;
translate([x, btn_row_y, box_h - 1])
cylinder(h = lid_t + 2, d = btn_d);
}
}
// ---------------------------------------------------------------------------
// Side ports: USB/charge (right wall) and power switch (left wall)
// ---------------------------------------------------------------------------
module side_ports() {
// USB on +X wall
translate([box_w - wall - 1, box_d * 0.30, usb_center_z])
cube([wall + 2, usb_w, usb_h], center = false);
// power switch on -X wall
translate([-1, box_d * 0.30, sw_center_z]) rotate([0, 90, 0])
cylinder(h = wall + 2, d = sw_d);
}
// ---------------------------------------------------------------------------
// Ventilation slots on the back wall (+Y)
// ---------------------------------------------------------------------------
module vents() {
total = (vent_count - 1) * (vent_slot_w + 4);
for (i = [0 : vent_count - 1]) {
x = box_w/2 - total/2 + i * (vent_slot_w + 4);
translate([x, box_d - wall - 1, box_h * 0.55])
cube([vent_slot_w, wall + 2, vent_slot_len]);
}
}
// ---------------------------------------------------------------------------
// Internal mounts
// ---------------------------------------------------------------------------
// Corner posts the lid screws into (fused into the wall corners, top-anchored).
module lid_bosses() {
boss_len = lid_rim_h + 10;
for (sx = [lid_boss_inset, box_w - lid_boss_inset],
sy = [lid_boss_inset, box_d - lid_boss_inset])
translate([sx, sy, box_h - boss_len])
boss(lid_boss_d, boss_len, lid_screw_d - 0.6); // self-tap
}
// ESP32-LyraT standoffs (board lies flat on the floor, toward the back).
lyrat_cx = box_w/2;
lyrat_cy = box_d - lyrat_d/2 - 10;
module lyrat_standoffs() {
for (sx = [-lyrat_hole_dx/2, lyrat_hole_dx/2],
sy = [-lyrat_hole_dy/2, lyrat_hole_dy/2])
translate([lyrat_cx + sx, lyrat_cy + sy, floor_t])
boss(lyrat_standoff_d, lyrat_standoff_h, lyrat_screw_d);
}
// 2x 18650 battery cradle (open-top tray with end stops + clips), front-left.
batt_cx = box_w/2;
batt_cy = wall + batt_holder_d/2 + 4;
module battery_cradle() {
ow = batt_holder_w + 2*batt_wall;
od = batt_holder_d + 2*batt_wall;
translate([batt_cx - ow/2, batt_cy - od/2, floor_t])
difference() {
cube([ow, od, batt_holder_h]);
// hollow pocket, open top
translate([batt_wall, batt_wall, batt_wall])
cube([batt_holder_w, batt_holder_d, batt_holder_h]);
// cable notch in the front wall of the cradle
translate([ow/2 - 6, -1, batt_holder_h - 8])
cube([12, batt_wall + 2, 10]);
}
if (batt_clip) {
// two retaining lips along the long sides
for (sy = [batt_cy - od/2 + batt_wall/2, batt_cy + od/2 - batt_wall/2])
translate([batt_cx - batt_holder_w/3, sy, floor_t + batt_holder_h])
rotate([0, 0, 0])
cube([batt_holder_w*2/3, batt_wall, 2.5], center = false);
}
}
// ---------------------------------------------------------------------------
// BODY
// ---------------------------------------------------------------------------
module body() {
spk_l = box_w/2 - spk_spacing/2;
spk_r = box_w/2 + spk_spacing/2;
difference() {
union() {
shell([box_w, box_d, box_h], corner_r, wall, floor_t);
speaker_bosses(spk_l, spk_center_z);
speaker_bosses(spk_r, spk_center_z);
lid_bosses();
lyrat_standoffs();
battery_cradle();
}
speaker_cut(spk_l, spk_center_z);
speaker_cut(spk_r, spk_center_z);
side_ports();
vents();
}
}
// ---------------------------------------------------------------------------
// LID (NFC reader sits underneath; print thin-side up)
// ---------------------------------------------------------------------------
// RC522 pocket posts on the lid underside.
module rc522_mount() {
// board centred, posts hang DOWN from the lid underside (local -Z here)
bx = 54; by = 34; // RC522 mounting-hole rectangle (verify)
for (sx = [-bx/2, bx/2], sy = [-by/2, by/2])
translate([sx, sy, -8])
cylinder(h = 8, d = 6);
// pilot holes
for (sx = [-bx/2, bx/2], sy = [-by/2, by/2])
translate([sx, sy, -8.5])
cylinder(h = 9, d = 2.4);
}
module lid() {
difference() {
union() {
// top plate sitting on the wall tops
translate([box_w/2, box_d/2, box_h])
linear_extrude(lid_t)
rrect(box_w, box_d, corner_r);
// locating lip dropping into the interior
translate([box_w/2, box_d/2, box_h - lid_rim_h])
linear_extrude(lid_rim_h)
difference() {
rrect(inner_w - 2*fit, inner_d - 2*fit, max(0.1, corner_r - wall));
rrect(inner_w - 2*fit - 2*lid_rim_t,
inner_d - 2*fit - 2*lid_rim_t,
max(0.1, corner_r - wall - lid_rim_t));
}
// RC522 mount, centred under the lid
translate([box_w/2, box_d/2, box_h + lid_t]) rc522_mount();
}
// corner screw holes (countersunk) through the plate
for (sx = [lid_boss_inset, box_w - lid_boss_inset],
sy = [lid_boss_inset, box_d - lid_boss_inset]) {
translate([sx, sy, box_h - 0.5])
cylinder(h = lid_t + 1, d = lid_screw_d);
translate([sx, sy, box_h + lid_t - 1.4])
cylinder(h = 1.6, d1 = lid_screw_d, d2 = lid_screw_d + 3.5);
}
// button holes near the front edge
button_cuts();
}
}
// ---------------------------------------------------------------------------
// SWAPPABLE SPEAKER ADAPTER (print one per speaker model)
// ---------------------------------------------------------------------------
module speaker_adapter() {
difference() {
union() {
cylinder(h = adapter_t, d = adapter_outer_d);
// frame seat ring raised on the back so the speaker locates
translate([0, 0, adapter_t])
ring(spk_frame_d + 6, spk_cutout_d, spk_frame_seat);
}
// cone clearance
translate([0, 0, -0.5]) cylinder(h = adapter_t + spk_frame_seat + 1, d = spk_cutout_d);
// speaker frame recess on the back
translate([0, 0, adapter_t - 0.01])
cylinder(h = spk_frame_seat + 0.02, d = spk_frame_d);
// mounting holes to the panel bosses (FIXED interface)
bolt_circle(spk_bolt_d, spk_bolt_count, 45)
translate([0, 0, -0.5]) cylinder(h = adapter_t + spk_frame_seat + 1, d = spk_bolt_hole_d);
// the speaker's OWN screw holes (swappable per model)
bolt_circle(spk_screw_bolt_d, spk_screw_count, 0)
translate([0, 0, -0.5]) cylinder(h = adapter_t + spk_frame_seat + 1, d = spk_screw_d);
}
}
// ---------------------------------------------------------------------------
// SPEAKER GRILL (snaps into the sound opening)
// ---------------------------------------------------------------------------
module grill() {
r_out = spk_sound_d/2;
union() {
// outer flange that seats in the panel recess
ring(spk_sound_d + 8, spk_sound_d - 4, grill_t);
// friction lip into the hole
translate([0, 0, -grill_lip_h])
ring(spk_sound_d - 2*fit, spk_sound_d - 2*fit - 3, grill_lip_h + grill_t);
// concentric rings
for (i = [1 : grill_rings])
ring(2*r_out * i/(grill_rings+1) + 1.4,
2*r_out * i/(grill_rings+1) - 1.4, grill_t);
// central hub
cylinder(h = grill_t, d = 6);
// radial spokes
for (i = [0 : grill_spokes - 1])
rotate([0, 0, i * 360/grill_spokes])
translate([0, -1.0, 0]) cube([r_out, 2.0, grill_t]);
}
}
// ---------------------------------------------------------------------------
// ASSEMBLY PREVIEW
// ---------------------------------------------------------------------------
explode = 45; // lid lift for the exploded preview (0 = closed)
module assembly() {
spk_l = box_w/2 - spk_spacing/2;
spk_r = box_w/2 + spk_spacing/2;
color("WhiteSmoke") body();
color("Gainsboro") translate([0, 0, explode]) lid();
for (cx = [spk_l, spk_r]) {
// adapter just inside the front wall
color("SteelBlue")
translate([cx, wall + adapter_t, spk_center_z]) rotate([90, 0, 0])
speaker_adapter();
// grill in the opening
color("DimGray")
translate([cx, grill_t, spk_center_z]) rotate([-90, 0, 0])
grill();
}
}
// ---------------------------------------------------------------------------
// DISPATCH
// ---------------------------------------------------------------------------
if (part == "body") body();
else if (part == "lid") lid();
else if (part == "speaker_adapter") speaker_adapter();
else if (part == "grill") grill();
else assembly();
+53
View File
@@ -0,0 +1,53 @@
// ============================================================================
// Kidsbox enclosure — helper modules
// ============================================================================
// A box with rounded VERTICAL edges only (flat top & bottom for clean FDM).
// size = [x, y, z], r = corner radius.
module rbox(size, r) {
x = size[0]; y = size[1]; z = size[2];
hull() {
for (sx = [r, x - r], sy = [r, y - r])
translate([sx, sy, 0]) cylinder(h = z, r = r);
}
}
// Hollow shell: outer rbox minus inner rbox, open at the top (+Z).
// Floor thickness `ft`, wall `w`.
module shell(size, r, w, ft) {
x = size[0]; y = size[1]; z = size[2];
difference() {
rbox([x, y, z], r);
translate([w, w, ft])
rbox([x - 2*w, y - 2*w, z - ft + 1], max(0.1, r - w));
}
}
// A screw boss (solid post with a pilot hole) standing in +Z.
module boss(od, h, hole_d) {
difference() {
cylinder(h = h, d = od);
translate([0, 0, -0.5]) cylinder(h = h + 1, d = hole_d);
}
}
// A flat ring.
module ring(od, id, h) {
difference() {
cylinder(h = h, d = od);
translate([0, 0, -0.5]) cylinder(h = h + 1, d = id);
}
}
// Bolt circle: place children at `count` points on diameter `d`.
module bolt_circle(d, count, start = 0) {
for (i = [0 : count - 1])
rotate([0, 0, start + i * 360 / count])
translate([d/2, 0, 0]) children();
}
// A rounded-rectangle plate (2D) for linear_extrude.
module rrect(x, y, r) {
hull() for (sx = [r, x - r], sy = [r, y - r])
translate([sx - x/2, sy - y/2]) circle(r = r);
}
+96
View File
@@ -0,0 +1,96 @@
// ============================================================================
// Kidsbox enclosure — parameters
// All dimensions in millimetres. Edit here, then re-export the STLs.
// See README.md for the export commands.
// ============================================================================
// ---- global print settings ------------------------------------------------
$fn = 64; // facet count for curves (raise for final render)
wall = 3.0; // shell wall thickness
floor_t = 3.0; // bottom thickness
corner_r = 6.0; // outer vertical-edge radius
fit = 0.30; // general clearance for mating parts (per side)
// ---- outer box dimensions (interior is derived) ---------------------------
box_w = 190; // width (X) — roughly one banana, per reference photo
box_h = 110; // height (Z)
box_d = 115; // depth (Y)
// derived interior
inner_w = box_w - 2*wall;
inner_d = box_d - 2*wall;
inner_h = box_h - floor_t; // open-top shell, lid closes it
// ---- lid -------------------------------------------------------------------
lid_t = 2.4; // thin enough for 13.56 MHz NFC to read through PLA
lid_rim_h = 6.0; // depth of the lip that drops into the box
lid_rim_t = 1.6; // thickness of that lip
lid_screw_d = 3.2; // M3 clearance
lid_boss_d = 9.0; // corner screw-boss outer diameter
lid_boss_inset = 9.0; // boss centre inset from interior corner
// ---- speaker panel interface (FIXED) --------------------------------------
// This is the standard "mount for the speaker mount". Every swappable
// speaker adapter bolts to THIS pattern, so the box never changes.
spk_sound_d = 66; // sound opening through the front panel
spk_bolt_d = 80; // bolt circle for the adapter screws
spk_bolt_count = 4; // number of adapter screws
spk_bolt_hole_d = 3.2; // M3 clearance through the panel
spk_boss_d = 8.0; // screw boss diameter on the inside of the panel
spk_boss_h = 5.0; // screw boss height (into the box)
spk_spacing = 94; // centre-to-centre of the two speakers (X)
spk_center_z = 48; // speaker centre height from the box floor (Z)
// ---- swappable speaker ADAPTER (edit for YOUR speaker) ---------------------
// Print one adapter per speaker model. Defaults suit a common 3" driver.
adapter_t = 3.0; // adapter plate thickness
adapter_outer_d = 88; // outer diameter of the adapter disc
spk_frame_d = 66; // speaker outer frame diameter (seat recess)
spk_frame_seat = 1.5; // depth the frame sinks into the adapter
spk_cutout_d = 58; // cone clearance hole through the adapter
spk_screw_count = 4; // the SPEAKER's own mounting holes
spk_screw_bolt_d = 60; // bolt circle of the speaker's mounting holes
spk_screw_d = 3.0; // speaker screw clearance
// ---- speaker grill (snaps into the sound opening) -------------------------
grill_t = 2.4; // grill face thickness
grill_rings = 3; // number of concentric rings
grill_spokes = 6; // number of radial spokes
grill_lip_h = 5.0; // snap lip depth into the sound hole
grill_standoff = 3.0; // gap the grill holds off the cone
// ---- buttons (top lid, row near the front edge) ---------------------------
// Kept off the front face (which stays clean like the reference box) and
// clear of the central NFC reader, which sits under the lid centre.
btn_d = 12.2; // panel hole for a 12 mm momentary switch
btn_count = 3; // vol-, play/pause, vol+
btn_pitch = 26; // spacing between buttons along X
btn_row_y = 18; // distance of the button row from the front wall
// ---- side ports ------------------------------------------------------------
usb_w = 12; // micro-USB / charge access cutout (W)
usb_h = 7; // (H)
usb_center_z = 22; // height of the port from the floor
sw_d = 7; // power slide-switch hole diameter
sw_center_z = 22;
// ---- ESP32-LyraT v4.3 mount (verify against your board) -------------------
lyrat_w = 99; // board long edge
lyrat_d = 57; // board short edge
lyrat_hole_dx = 92; // mounting-hole rectangle, long axis (VERIFY)
lyrat_hole_dy = 50; // mounting-hole rectangle, short axis (VERIFY)
lyrat_standoff_h = 6; // standoff height off the floor
lyrat_standoff_d = 6; // standoff outer diameter
lyrat_screw_d = 2.6; // self-tap for M2.5/M3
// ---- 2x 18650 battery holder cradle ---------------------------------------
batt_holder_w = 78; // typical 2-cell holder footprint (X)
batt_holder_d = 39; // (Y)
batt_holder_h = 21; // (Z)
batt_wall = 2.0; // cradle wall
batt_clip = true; // add retaining clips
// ---- ventilation -----------------------------------------------------------
vent_slot_w = 3;
vent_slot_len = 28;
vent_count = 5;
+78850
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+50682
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff