Compare commits

..
2 Commits
Author SHA1 Message Date
mangavalkandClaude Opus 5.5 b28ee2892a 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>
2026-10-22 00:27:20 +02:00
mangavalkandClaude Opus 5.5 ab5844ce98 Add a 3D-printed photo frame insert for the panel and D1 mini
OpenSCAD truss insert that holds the EL512.256-H and a Wemos D1 mini
ESP32 in a 250 x 200 x 30 mm photo frame. The D1 mini snap-tab lips
print without supports: a 45 degree chamfer bridges the gap to the
pocket, only the catch is a short flat overhang, and the tip is blunt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:08:28 +02:00
2 changed files with 281 additions and 0 deletions

No files matched your search

+1
View File
@@ -99,6 +99,7 @@ home_assistant/packages/ el512_panel.yaml: helper sensors for the ESPHome pages
tools/ import_still_alive.py, konosuba_sprites.py, steins_sprites.py, tools/ import_still_alive.py, konosuba_sprites.py, steins_sprites.py,
trek_sprites.py, yourname_sprites.py, render_pages.py trek_sprites.py, yourname_sprites.py, render_pages.py
(+ render/: PC renderer) (+ render/: PC renderer)
enclosure/ el512_frame.scad: 3D-printed insert for a 250x200x30 photo frame
test/ unit tests (placeholder) test/ unit tests (placeholder)
docs/ datasheet and notes docs/ datasheet and notes
``` ```
+280
View File
@@ -0,0 +1,280 @@
// 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();