Each of the video sphere’s twelve screens lives in a snap-fit case that I mostly didn’t model. I described it. The case is built on YAPPgenerator_v3, a 6,164-line OpenSCAD file you include and then configure: you declare your PCB’s dimensions, its standoffs, and arrays of cutouts per face, and YAPP generates a base and lid with walls, ridges, and snap joins. My entire enclosure (case, lid, a glue-in stem port, and a printable BOOT-button plunger) is one file just shy of 400 lines that reads more like a config than like CAD.
This post is the YAPP reference I wanted when I started: a real enclosure with real constraints, and the gotchas in the order I hit them.
The setup
git clone https://github.com/mrWheel/YAPP_Box- Copy
YAPPgenerator_v3.scadnext to your enclosure file (or add the YAPP_Box directory toOPENSCADPATH). include <YAPPgenerator_v3.scad>at the top, set variables, F5 to preview, F6 to render, export.
Describing the board
The screen module is an LCDWIKI ES3C28P, an ESP32-S3 with a 2.8″ ILI9341 display on an 86 × 50 × 1.6 mm PCB. YAPP wants the board described, not drawn:
pcbLength = 86.0; // long axis (X)
pcbWidth = 50.0; // short axis (Y)
pcbThickness = 1.6;
standoffHeight = 13.5;
standoffDiameter = 5.6; // matches the 5.60 mm pad on the spec
standoffPinDiameter = 2.7; // M3 self-tap
pcbStands = [
[ 4.0, 4.0, yappBoth, yappPin],
[82.0, 4.0, yappBoth, yappPin],
[ 4.0, 46.0, yappBoth, yappPin],
[82.0, 46.0, yappBoth, yappPin],
];
Four M3 mount holes, inset 4 mm from each PCB edge. All of these numbers came from the module’s datasheet, except the ones that couldn’t be (like where the BOOT button actually sits). Those are flagged in the source with a MEASURE YOUR BOARD comment because they had to be corrected against the physical PCB. They were.
The Z budget
The one genuinely three-dimensional decision in the whole case is vertical. Each module carries a 42 × 25.5 × 11.2 mm Li-Po battery, and it lives under the PCB: the standoffs are 13.5 mm tall specifically so the battery slots beneath the board with ~2.3 mm of clearance for wrappers, kapton, and the lead bulge. Everything else follows from that one choice:
internal Z = standoffHeight (13.5) + pcbThickness (1.6)
+ lcdAboveBoard (4.30) + 1 mm clear above the LCD
= 20.4 mm
The seam between base and lid lands exactly at the PCB top: baseWallHeight = 15.1, lidWallHeight = 5.3. The battery is held by a pocket of four low walls (5 mm tall, below the SMD clearance band, so the pocket can’t collide with components even if the footprint drifts), drawn in a hookBaseInside, YAPP’s escape hatch for geometry it has no declaration for.
The cutouts
Everything that penetrates a wall is a row in an array. The whole personality of the case fits in one table:
| Face | What | Key numbers |
|---|---|---|
| Lid | LCD window | 63.2 × 50 mm rectangle |
| Front | USB-C | 11 × 6 mm, centered at Z = −3 (below the PCB plane) |
| Back | Power toggle | Ø6.2 mm circle at Z = −7.8, for an M6 threaded bushing |
| Base | Stem-port pocket | Ø25.4 mm circle, 1.2 mm deep, centered at PCB (40, 25) |
| Base | BOOT plunger hole | Ø4.5 mm, through the full base plate at PCB (83.5, 14) |
Two of these deserve a sentence. The USB-C connector is bottom-mounted (its body hangs below the PCB), so its cutout spans Z = −6 to 0 and stays entirely in the base wall, never notching the lid. And the toggle is a sub-mini SPDT with an M6 bushing (an MTS-102 or NKK M2012; bump the hole to 6.5 mm for a 1/4-40 toggle like the C&K 7101), wired inline with the battery’s positive lead, with comfortable margin over the module’s 560 mA peak draw.
The two turned parts
Beyond the YAPP shells, the file defines two small rotate_extrude parts.
The stem port is how a case hangs off the sphere. It’s a 25 mm-foot, 18 mm-tall pedestal (the same quarter-ellipse profile as the big pedestal, reused at case scale) with a socket that receives a 10 mm stem. The case base has a circular pocket 1.2 mm deep with 0.2 mm of radial slack, and the port’s foot drops into it. The pocket walls fully constrain the port in XY before any glue is applied, so the stem ends up concentric with the LCD by construction; the CA or epoxy under the foot only has to carry load, not alignment.
The BOOT plunger exists because the ES3C28P’s BOOT button is an SMT tactile switch on the underside of the PCB, facing the case floor, and I need it after assembly (it’s also the sleep button). The plunger is a Ø4 mm rod with a Ø6.5 mm flange partway up: the lower stem pokes 1.5 mm out the bottom of the case as a button, the flange retains it from falling out, and the upper stem rests ~0.5 mm below the switch actuator. A 3 mm guide tube printed onto the inside floor extends the bearing length to 5 mm, holding the plunger’s tilt to about 2.9° instead of the ~7° the base plate alone would allow. Total press travel: about 0.8 mm. You drop it in before seating the PCB, and it can only come out the top.
Centering a screen that isn’t centered
The LCD sits asymmetrically on the PCB: the bezel is wider on the USB-C end. Left alone, the visible screen would sit off-center in the case, which would read as an error twelve times over on the finished sphere. The fix is a matched pair of asymmetries. The lid cutout is trimmed 6 mm on the USB-C side to hide the wide bezel, and the case padding on the opposite side is extended by the same 6 mm (paddingBack = 9.0 vs 3.0 everywhere else). Net effect: the visible screen, the case interior, and the stem-port pocket all share the same center at PCB X = 40. It took two commits of nudging to land. The case looks symmetric precisely because the numbers aren’t.
Gotchas I’d want in the manual
- YAPP-consumed variables must be literals. YAPP evaluates its known arrays and flags during the
includesetup pass, before your own assignments resolve.printBaseShell = (part == "case")silently doesn’t work; neither does referencing your own constants insidecutoutsBase. The pattern that survives: literals in YAPP’s arrays, plus a keep-in-sync comment pointing at the named parameters they mirror. hookBaseInsidehas its own origin. The hook’s coordinate frame starts at the inside back-left corner of the base: YAPP’s wrapping translate has already consumedwallThickness, and Z = 0 is the inside floor, not the outside bottom. PCB (0,0) is at local(paddingBack, paddingLeft), not(wallThickness + paddingBack, ...). This class of trap is real; the battery pocket in this project sat onebasePlaneThickness(2 mm) too high until a commit fixed the hook’s Z origin.- Negative Z in
yappCoordPCBis your friend. Z = 0 is the PCB’s bottom face, so features in the base wall live at negative Z: the toggle at −7.8, the USB-C centered at −3. Once you learn “below the board is negative,” side-wall cutouts stop being trial and error. - Snap joins need ridge to bite into.
ridgeHeightmust be ≥ 1.8 ×wallThicknessfor the snaps to work (mine: 5.0 mm against 2.4 mm walls). AndridgeSlackis a print-tuned number, not a design number: it shipped at 0.2 and got loosened to 0.35 after the first printed pair. - Keep an assembled preview. All four part flags (
printBaseShell,printLidShell,printPort,printPlunger) default to true so F5 shows the whole assembly; you flip things off only at export time. Cheap, and it catches interference visually before the slicer ever sees an STL.
YAPP comes down to a declarative DSL with one escape hatch. Learn where each array’s origin sits, keep YAPP’s own inputs literal, put everything custom in the hooks, and an enclosure becomes a table of holes with a PCB in the middle.
Part 2 of the video-sphere series. Previous: The Icosahedron Hub · Next: The Printed Parts.