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_rot = 0; // 0: USB to the right, 180: USB to the left
d1_clear = 0.3; // clearance around the board 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_ledge = 2; // seat width under the short ends (the pin rows run along the long sides)
d1_tab_w = 6; // snap tab width d1_arm_t = 0.8; // snap arm thickness (two 0.4 mm perimeters)
d1_tab_t = 1.4; // snap tab thickness 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 = 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; d1_wall = 1.6;
/* [Preview] */ /* [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_seat = d1_top - d1_t; // z of the board's bottom face
d1_in_l = d1_l + 2 * d1_clear; d1_in_l = d1_l + 2 * d1_clear;
d1_in_w = d1_w + 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_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(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(screw_engage < insert_depth, "screw would bottom out in the insert nut");
assert(head_sink >= (csk ? 0.3 : socket_h + 0.2), 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, 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, " 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 ---------- // ---------- 2D ----------
module outline() square([W, H]); module outline() square([W, H]);
module d1_footprint(grow = 0) { module d1_footprint(grow = 0) {
translate([d1_cx, d1_cy]) rotate(d1_rot) 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 // 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 // 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 // short ends and four snap arms hold it down; the pin rows hang free over the hollow
// the hollow inside. The upper long wall has a slot for the wires to the panel. // 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 // The snap arms run along the short ends, anchored at the corners with the lip at the
translate([d1_cx, d1_cy, 0]) rotate(d1_rot) { // 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() { difference() {
union() { union() {
// long walls, flush with the board's top face // long walls, flush with the board's top face
@@ -200,33 +220,34 @@ module d1_cradle() {
// seat ledges under the short ends // 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]) 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]); 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 // 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]); 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). // Slots through the plate that free the arms from everything but their anchors.
// Printable without supports: the underside is a 45 degree chamfer across the 1 mm gap module d1_arm_slots() d1_place() d1_quadrants()
// to the pocket, and only the catch itself (clearance + d1_lip) is a flat overhang. 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 tip has a short vertical face, so it does not print as a knife edge.
module snap_tab(zl) { // The lip has a 45 degree chamfer underneath across the gap to the pocket, so only the
translate([-d1_tab_t / 2, -d1_tab_w / 2, 0]) cube([d1_tab_t, d1_tab_w, zl]); // catch itself (clearance + d1_lip) is a flat overhang, and a blunt tip.
gap = 1; // the tab stands 1 mm clear of the pocket module d1_arms() d1_place() d1_quadrants() {
reach = gap + d1_clear + d1_lip; // tab face to lip tip t = d1_arm_t;
g = d1_arm_gap;
reach = g + d1_clear + d1_lip; // arm face to lip tip
tip_h = 0.6; tip_h = 0.6;
translate([d1_tab_t / 2, d1_tab_w / 2, d1_top + 0.1]) rotate([90, 0, 0]) translate([d1_arm_x, d1_tip, 0]) cube([t, d1_arm_l + eps, d1_catch]);
linear_extrude(d1_tab_w) polygon([ translate([d1_arm_x + t, d1_tip + d1_lip_w, d1_catch]) rotate([90, 0, 0])
[0, -gap], [-d1_tab_t, -gap], // chamfer starts on the tab face linear_extrude(d1_lip_w) polygon([
[-d1_tab_t - gap, 0], // 45 degrees across the gap [0, -g], [-t, -g], // chamfer starts on the arm face
[-d1_tab_t - reach, 0], // flat catch over the board [-t - g, 0], // 45 degrees across the gap
[-d1_tab_t - reach, tip_h], // blunt tip [-t - reach, 0], // flat catch over the board
[-d1_tab_t, tip_h + reach], // 45 degree lead-in [-t - reach, tip_h], // blunt tip
[-t, tip_h + reach], // 45 degree lead-in
[0, tip_h + reach]]); [0, tip_h + reach]]);
} }
@@ -239,7 +260,9 @@ module frame() {
d1_cradle(); d1_cradle();
} }
for (p = holes) screw_cut(p); for (p = holes) screw_cut(p);
d1_arm_slots();
} }
d1_arms();
} }
module ghosts() { module ghosts() {