D1 mini cradle: sideways snap arms so the clips survive PLA

The upright snap tabs bent across the layer lines, which cracks in PLA.
Each clip is now a thin arm along the board's short end, anchored at a
corner with the lip at its free end, so it flexes in the layer plane.
The arms stand on the bed in slots through the plate (no supports) and
leave room for the USB plug. Asserts guard the USB keep-out and keep the
bending strain under 1.5 %; the echo reports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-22 00:27:20 +02:00
1 parent ab5844ce98
commit b28ee2892a
1 file changed
+53 -30
+53 -30
View File
@@ -62,9 +62,12 @@ d1_glass_gap = 7; // photo glass to the board's top face (ESP32 module is ~3
d1_rot = 0; // 0: USB to the right, 180: USB to the left
d1_clear = 0.3; // clearance around the board
d1_ledge = 2; // seat width under the short ends (the pin rows run along the long sides)
d1_tab_w = 6; // snap tab width
d1_tab_t = 1.4; // snap tab thickness
d1_arm_t = 0.8; // snap arm thickness (two 0.4 mm perimeters)
d1_arm_l = 12; // snap arm free length, root to tip
d1_arm_gap = 0.5; // snap arm to the end of the pocket
d1_lip = 0.8; // how far the snap lip reaches over the board
d1_lip_w = 3; // snap lip length along the arm
d1_usb_w = 11; // kept free between the arms at each end for the USB plug
d1_wall = 1.6;
/* [Preview] */
@@ -103,9 +106,19 @@ d1_top = part_d - d1_glass_gap; // z of the board's top face
d1_seat = d1_top - d1_t; // z of the board's bottom face
d1_in_l = d1_l + 2 * d1_clear;
d1_in_w = d1_w + 2 * d1_clear;
d1_flex = d1_clear + d1_lip; // worst-case arm deflection while snapping in
d1_arm_x = d1_in_l / 2 + d1_arm_gap; // inner face of the snap arms
d1_x_out = d1_arm_x + d1_arm_t + d1_flex + 0.4; // outer edge of the slot the arms flex into
d1_root = d1_in_w / 2 + d1_wall; // y of the arm roots
d1_tip = d1_root - d1_arm_l; // y of the arm tips
d1_catch = d1_top + 0.1; // z of the underside of the lips
// bending strain at the arm root with the lip pushed fully out
d1_strain = 1.5 * d1_arm_t * d1_flex / pow(d1_arm_l - d1_lip_w / 2, 2);
assert(pcb_x0 >= wall + 1, "panel does not fit the frame width");
assert(pcb_y0 - border >= d1_w + 2 * d1_wall + 4, "no room below the panel for the D1 mini");
assert(pcb_y0 - border >= 2 * (d1_root + d1_wall) + 2, "no room below the panel for the D1 mini");
assert(d1_tip >= d1_usb_w / 2, "snap arms reach into the USB plug's way: shorten d1_arm_l");
assert(d1_strain <= 0.015, str("snap arms strained ", d1_strain * 100, " %, too much for PLA"));
assert(panel_back - plate_t >= air_gap, "less than the recommended air gap behind the panel");
assert(screw_engage < insert_depth, "screw would bottom out in the insert nut");
assert(head_sink >= (csk ? 0.3 : socket_h + 0.2),
@@ -114,14 +127,15 @@ assert(head_sink <= nut_z - 4, "screw_len too short for this stack");
echo(str("Insert depth ", part_d, " mm, air gap behind panel ", panel_back - plate_t,
" mm, standoff height ", nut_z - plate_t, " mm, use M3x", screw, " ", head_type,
" (head ", head_sink, " mm below the back face)"));
" (head ", head_sink, " mm below the back face), snap arm strain ",
round(d1_strain * 1000) / 10, " %"));
// ---------- 2D ----------
module outline() square([W, H]);
module d1_footprint(grow = 0) {
translate([d1_cx, d1_cy]) rotate(d1_rot)
square([d1_in_l + 2 * (d1_tab_t + 1) + 2 * grow, d1_in_w + 2 * d1_wall + 2 * grow], center = true);
square([2 * (d1_x_out + 1 + grow), 2 * (d1_root + d1_wall + grow)], center = true);
}
// Triangle cells; a horizontal strut runs along the panel's bottom edge and a vertex
@@ -187,11 +201,17 @@ module screw_cut(p) translate(p) {
}
// Cradle for the D1 mini, board face up towards the glass. It rests on ledges under its
// short ends and two snap tabs at each end hold it down; the pin rows hang free over
// the hollow inside. The upper long wall has a slot for the wires to the panel.
module d1_cradle() {
zl = d1_top + d1_lip + 0.6; // top of the snap tabs
translate([d1_cx, d1_cy, 0]) rotate(d1_rot) {
// short ends and four snap arms hold it down; the pin rows hang free over the hollow
// inside. The upper long wall has a slot for the wires to the panel.
//
// The snap arms run along the short ends, anchored at the corners with the lip at the
// free end, and bend sideways: the strain runs along the printed perimeters instead of
// across the layers, so they hold up in PLA. Each arm stands on the bed in a slot through
// the plate. The arms leave d1_usb_w free in the middle of each end for the USB plug.
module d1_place() translate([d1_cx, d1_cy, 0]) rotate(d1_rot) children();
module d1_quadrants() for (mx = [0, 1], my = [0, 1]) mirror([mx, 0, 0]) mirror([0, my, 0]) children();
module d1_cradle() d1_place() {
difference() {
union() {
// long walls, flush with the board's top face
@@ -200,33 +220,34 @@ module d1_cradle() {
// seat ledges under the short ends
for (s = [-1, 1]) translate([s > 0 ? d1_in_l / 2 - d1_ledge : -d1_in_l / 2, -d1_in_w / 2, 0])
cube([d1_ledge, d1_in_w, d1_seat]);
// arm anchors, outside the long walls at the corners
d1_quadrants() translate([d1_in_l / 2 - 2, d1_root - eps, 0])
cube([d1_x_out + 1 - (d1_in_l / 2 - 2), d1_wall + eps, d1_top]);
}
// wire slot towards the panel
translate([-10, d1_in_w / 2 - eps, plate_t]) cube([20, d1_wall + 2 * eps, d1_seat - plate_t - 2]);
}
// snap tabs, two per end, clear of the centred USB socket
for (sx = [-1, 1], sy = [-1, 1])
translate([sx * (d1_in_l / 2 + 1 + d1_tab_t / 2), sy * (d1_in_w / 2 - d1_tab_w / 2), 0])
rotate(sx > 0 ? 0 : 180) snap_tab(zl);
}
}
// Tab standing outside the board edge, lip pointing to -X (towards the board).
// Printable without supports: the underside is a 45 degree chamfer across the 1 mm gap
// to the pocket, and only the catch itself (clearance + d1_lip) is a flat overhang.
// The tip has a short vertical face, so it does not print as a knife edge.
module snap_tab(zl) {
translate([-d1_tab_t / 2, -d1_tab_w / 2, 0]) cube([d1_tab_t, d1_tab_w, zl]);
gap = 1; // the tab stands 1 mm clear of the pocket
reach = gap + d1_clear + d1_lip; // tab face to lip tip
// Slots through the plate that free the arms from everything but their anchors.
module d1_arm_slots() d1_place() d1_quadrants()
translate([d1_in_l / 2, d1_tip - 0.6, -1]) cube([d1_x_out - d1_in_l / 2, d1_root - d1_tip + 0.6, plate_t + 2]);
// The lip has a 45 degree chamfer underneath across the gap to the pocket, so only the
// catch itself (clearance + d1_lip) is a flat overhang, and a blunt tip.
module d1_arms() d1_place() d1_quadrants() {
t = d1_arm_t;
g = d1_arm_gap;
reach = g + d1_clear + d1_lip; // arm face to lip tip
tip_h = 0.6;
translate([d1_tab_t / 2, d1_tab_w / 2, d1_top + 0.1]) rotate([90, 0, 0])
linear_extrude(d1_tab_w) polygon([
[0, -gap], [-d1_tab_t, -gap], // chamfer starts on the tab face
[-d1_tab_t - gap, 0], // 45 degrees across the gap
[-d1_tab_t - reach, 0], // flat catch over the board
[-d1_tab_t - reach, tip_h], // blunt tip
[-d1_tab_t, tip_h + reach], // 45 degree lead-in
translate([d1_arm_x, d1_tip, 0]) cube([t, d1_arm_l + eps, d1_catch]);
translate([d1_arm_x + t, d1_tip + d1_lip_w, d1_catch]) rotate([90, 0, 0])
linear_extrude(d1_lip_w) polygon([
[0, -g], [-t, -g], // chamfer starts on the arm face
[-t - g, 0], // 45 degrees across the gap
[-t - reach, 0], // flat catch over the board
[-t - reach, tip_h], // blunt tip
[-t, tip_h + reach], // 45 degree lead-in
[0, tip_h + reach]]);
}
@@ -239,7 +260,9 @@ module frame() {
d1_cradle();
}
for (p = holes) screw_cut(p);
d1_arm_slots();
}
d1_arms();
}
module ghosts() {