Public Access
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>
281 lines
13 KiB
OpenSCAD
281 lines
13 KiB
OpenSCAD
// Skeleton insert for a 250 x 200 x 30 mm photo frame: holds the Planar EL512.256-H
|
|
// near the top and a Wemos D1 mini ESP32 below it, visible through the glass.
|
|
//
|
|
// Panel dimensions are from the datasheet (docs/datasheet, Table 9, Fig. 7), which wins
|
|
// over anything else here: PCB 233 x 136 mm, depth 16.5 mm, four M3 insert nuts
|
|
// (2.5 mm deep) on a 222.4 x 124.0 mm grid, insert nut tops 8.5 mm behind the front.
|
|
// The panel is the bare version (no FRA/FRB mounting frame).
|
|
//
|
|
// Coordinates: X to the right and Y up as seen from the front, Z towards the viewer.
|
|
// The part prints as modelled: back face (z = 0) on the bed, no supports.
|
|
//
|
|
// Stack inside the photo frame, front to back:
|
|
// photo glass | gap | panel (16.5) | >= 5 mm air gap | truss plate | backboard
|
|
// The rim touches the glass and the plate touches the backboard, so the backboard
|
|
// clamps the whole insert in place.
|
|
|
|
/* [Photo frame] */
|
|
frame_w = 245; // inside width of the photo frame
|
|
frame_h = 194; // inside height
|
|
frame_depth = 30; // inside depth, glass included
|
|
glass_t = 2; // photo glass thickness
|
|
backboard_t = 3; // backboard thickness (keep it, but drill vent holes)
|
|
fit = 0.6; // total clearance per axis against the frame
|
|
|
|
/* [Panel, datasheet Table 9 / Fig. 7] */
|
|
pcb_w = 233;
|
|
pcb_h = 136;
|
|
panel_d = 16.5; // front of panel glass to tallest part on the back
|
|
hole_dx = 222.4; // insert nut grid
|
|
hole_dy = 124.0;
|
|
nut_top = 8.5; // front of panel to top of the insert nuts
|
|
insert_depth = 2.5; // M3 thread depth in the nuts
|
|
air_gap = 5; // recommended free space behind the panel
|
|
glass_gap = 0.5; // photo glass to panel front, so the EL glass is not pressed
|
|
top_margin = 10; // frame inside top edge to panel PCB top edge
|
|
|
|
/* [Screws] */
|
|
// The heads sit below the back face, so the back stays flat against the backboard.
|
|
head_type = "socket"; // [countersunk:ISO 10642 / DIN 7991 90 degree, socket:ISO 4762]
|
|
screw_len = 0; // nominal length (countersunk: overall, head included); 0 = longest that fits
|
|
screw_engage = 2; // thread engagement in the insert nut, < insert_depth
|
|
m3_clear = 3.4;
|
|
csk_d = 6.9; // countersink diameter (M3 head 6.72 max)
|
|
socket_d = 6.2; // counterbore for a socket head (5.5 max)
|
|
socket_h = 3.0; // socket head height
|
|
hole_slot = 1.5; // extra hole length in X to absorb measuring differences
|
|
|
|
/* [Truss] */
|
|
plate_t = 3; // plate thickness
|
|
wall = 2; // rim wall thickness
|
|
border = 7; // solid band around the edge, rim included
|
|
cell = 26; // triangle side length
|
|
strut = 3.5; // strut width
|
|
fillet = 1.2; // corner rounding inside the cells
|
|
standoff_d = 9;
|
|
|
|
/* [D1 mini ESP32] */
|
|
d1_l = 39.0; // along the USB axis
|
|
d1_w = 31.4;
|
|
d1_t = 1.2; // PCB thickness
|
|
d1_glass_gap = 7; // photo glass to the board's top face (ESP32 module is ~3.2 high)
|
|
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_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] */
|
|
show_parts = true; // ghosts of the panel and the D1 mini (preview only)
|
|
|
|
$fn = 48;
|
|
eps = 0.01;
|
|
|
|
// ---------- derived ----------
|
|
W = frame_w - fit;
|
|
H = frame_h - fit;
|
|
part_d = frame_depth - glass_t - backboard_t; // total insert depth
|
|
|
|
panel_front = part_d - glass_gap; // z of the panel front
|
|
nut_z = panel_front - nut_top; // z of the insert nut tops
|
|
panel_back = panel_front - panel_d; // z of the tallest panel part
|
|
csk = head_type == "countersunk";
|
|
// longest standard M3 length whose head stays below the back face
|
|
std_len = [6, 8, 10, 12, 14, 16, 18, 20, 25, 30];
|
|
max_len = nut_z + screw_engage - (csk ? 0.3 : socket_h + 0.2);
|
|
fits = [for (l = std_len) if (l <= max_len) l];
|
|
screw = screw_len > 0 ? screw_len : fits[len(fits) - 1];
|
|
head_sink = nut_z + screw_engage - screw; // back face to the top of the screw head
|
|
|
|
pcb_x0 = (W - pcb_w) / 2;
|
|
pcb_y1 = H - top_margin;
|
|
pcb_y0 = pcb_y1 - pcb_h;
|
|
pcb_cx = W / 2;
|
|
pcb_cy = (pcb_y0 + pcb_y1) / 2;
|
|
|
|
holes = [for (sx = [-1, 1], sy = [-1, 1]) [pcb_cx + sx * hole_dx / 2, pcb_cy + sy * hole_dy / 2]];
|
|
|
|
d1_cx = W / 2;
|
|
d1_cy = (border + pcb_y0) / 2;
|
|
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 >= 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),
|
|
str("screw head would stick out of the back: use M3x", floor(max_len), " or shorter"));
|
|
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), 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([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
|
|
// sits on the centre line, so the pattern is symmetric.
|
|
module tri_cells() {
|
|
h = cell * sqrt(3) / 2;
|
|
x0 = W / 2 - cell * ceil(W / 2 / cell) - cell;
|
|
y0 = pcb_y0 - h * ceil(pcb_y0 / h) - h;
|
|
for (j = [0 : ceil(H / h) + 2], k = [0 : 2 * ceil(W / cell) + 4]) {
|
|
x = x0 + k * cell / 2;
|
|
y = y0 + j * h;
|
|
up = (k + j) % 2 == 0;
|
|
pts = up ? [[x, y], [x + cell, y], [x + cell / 2, y + h]]
|
|
: [[x, y + h], [x + cell, y + h], [x + cell / 2, y]];
|
|
offset(r = fillet) offset(delta = -(strut / 2 + fillet)) polygon(pts);
|
|
}
|
|
}
|
|
|
|
module plate_2d() {
|
|
difference() {
|
|
outline();
|
|
// the opening drops slivers of cells clipped by the border and the pads
|
|
offset(r = 1.5) offset(delta = -1.5) difference() {
|
|
intersection() {
|
|
offset(delta = -border) outline();
|
|
tri_cells();
|
|
}
|
|
// solid pads under the standoffs and the D1 mini cradle
|
|
for (p = holes) translate(p) circle(d = standoff_d + 8);
|
|
d1_footprint(2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------- 3D ----------
|
|
module rim() {
|
|
linear_extrude(part_d) difference() {
|
|
outline();
|
|
offset(delta = -wall) outline();
|
|
}
|
|
}
|
|
|
|
module standoff(p) {
|
|
translate(p) {
|
|
cylinder(d = standoff_d, h = nut_z);
|
|
cylinder(d1 = standoff_d + 6, d2 = standoff_d, h = plate_t + 3); // base fillet
|
|
}
|
|
}
|
|
|
|
module slot() hull() for (s = [-1, 1]) translate([s * hole_slot / 2, 0, 0]) children();
|
|
|
|
// Slotted screw hole. Countersunk: the head's top face sits head_sink below the back
|
|
// face. Socket head: its bearing face sits head_sink deep, the head in a counterbore.
|
|
module screw_cut(p) translate(p) {
|
|
slot() translate([0, 0, -1]) cylinder(d = m3_clear, h = nut_z + 2);
|
|
if (csk) {
|
|
slot() translate([0, 0, -1]) cylinder(d = csk_d, h = head_sink + 1);
|
|
slot() translate([0, 0, head_sink - eps])
|
|
cylinder(d1 = csk_d, d2 = m3_clear, h = (csk_d - m3_clear) / 2); // 90 degrees
|
|
} else {
|
|
slot() translate([0, 0, -1]) cylinder(d = socket_d, h = head_sink + 1);
|
|
}
|
|
}
|
|
|
|
// Cradle for the D1 mini, board face up towards the glass. It rests on ledges under its
|
|
// 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
|
|
for (s = [-1, 1]) translate([-d1_in_l / 2, s > 0 ? d1_in_w / 2 : -d1_in_w / 2 - d1_wall, 0])
|
|
cube([d1_in_l, d1_wall, d1_top]);
|
|
// 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]);
|
|
}
|
|
}
|
|
|
|
// 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_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]]);
|
|
}
|
|
|
|
module frame() {
|
|
difference() {
|
|
union() {
|
|
linear_extrude(plate_t) plate_2d();
|
|
rim();
|
|
for (p = holes) standoff(p);
|
|
d1_cradle();
|
|
}
|
|
for (p = holes) screw_cut(p);
|
|
d1_arm_slots();
|
|
}
|
|
d1_arms();
|
|
}
|
|
|
|
module ghosts() {
|
|
// panel: PCB and glass block, front at panel_front
|
|
color("dimgray", 0.35) translate([pcb_x0, pcb_y0, panel_back]) cube([pcb_w, pcb_h, panel_d]);
|
|
color("orange", 0.5) translate([pcb_cx - 97.55, pcb_cy - 48.75, panel_front - 0.2]) cube([195.1, 97.5, 0.2]);
|
|
// D1 mini
|
|
color("royalblue", 0.5) translate([d1_cx, d1_cy, d1_seat]) rotate(d1_rot)
|
|
translate([-d1_l / 2, -d1_w / 2, 0]) cube([d1_l, d1_w, d1_t]);
|
|
// photo glass
|
|
color("lightblue", 0.15) translate([0, 0, part_d]) cube([W, H, glass_t]);
|
|
}
|
|
|
|
frame();
|
|
if (show_parts && $preview) %ghosts();
|