Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Parametric Wall-Mount Enclosure — 10.1" DSI Display + Raspberry Pi 5

A wall-mountable enclosure for a Waveshare 10.1" DSI touch display, a Raspberry Pi 5, two side-firing speakers and a 5015 radial blower with an integral cooling duct — defined entirely in code with CadQuery, and verified headlessly rather than eyeballed in a GUI.

In service — wall-mounted, running a CCTV dashboard

The bare print Assembled Interior detail
printed part assembled interior

Left: as it came off the bed — speaker grilles, waffle rib grid, bottom mounting ear, bottom vent row. Centre: blower, duct and both speakers fitted. Right: the two keyholes, the rib grid clipped around them, and the ducted blower in the top-left corner.

247.0 × 85.6 × 177.0 mm     373.5 cm³     one watertight solid
~342 g PLA                  7–13 h print  ~₹2,050 to have made

Built around: a Waveshare 10.1-DSI-TOUCH-A display, a Raspberry Pi 5 with the active cooler, a 5015 12 V blower, a CNLINKO YT-RJ45-JSX-29-001 IP67 Ethernet receptacle, a pair of Waveshare 8Ω 5W speakers and ten M3 heat-set inserts. Full part list with links in BOM.md.


Why this is built in code

The whole model is one ~100 KB Python script. Every dimension lives in a single P{} dictionary at the top, so changing the bezel width re-derives the shell, the display opening, the tab positions, the duct routing and the rib grid — and the build asserts loudly if any of that pushes a feature into another.

That matters because this part has genuinely coupled constraints. The duct has to reach the right wall without crossing the display's screwdriver access path or clipping a speaker. The fan standoff is a function of wall thickness. The display's mounting holes sit on a plane tilted 5°, so every offset up the board is an in-plane distance whose vertical component is d × cos(5°).

In a GUI, those relationships live in your head. Here they're executable.


What's actually in the part

Display mounting — four corner brackets on a tilted plane

The screen sits at a 5° inclination, so its four fixing points lie on a plane tilted out of every axis. Each corner gets a 22 × 18 × 3.5 mm bracket with an Ø3.0 clearance hole drilled along the display's own normal, not vertically — an early version angled only the clearance hole and left the counterbore vertical, which put two different diameters inside each slot and skewed the screw axis by 10°.

The brackets were also rebuilt once. Originally a 22 × 2 mm strip, which meant the two left-hand tabs had zero side-wall contact — they hung off the front face alone. They're now full corner brackets tied into two walls each, with out-of-plane gussets (4 mm thick, 11 mm tab leg, 14 mm back leg) fusing them into the shell.

The top two corners have Ø11 mm access holes straight through the back so you can get a driver onto those screws once the Pi is in.

Sizing note: the display's captive threads are M2.5, not M3 — the drawing calls out "8-M2.5" and the STEP's tapped holes measure Ø2.04, the M2.5 tapping drill. The brackets were originally cut Ø3.4 for M3 clearance, which passes an M2.5 screw but lets it wander. Now Ø3.0: a loose M2.5 fit, snug enough to locate the screw, forgiving of print tolerance.

Wall mounting — two keyholes and a bottom ear

Two keyhole slots in the back wall, Ø11.0 opening into a Ø5.5 slot, at X = ±61.75. The Ø11 clears a No.10 pan head (~Ø9.5); the Ø5.5 takes the 4.8 mm shank with 0.35 mm per side.

Orientation matters and was got wrong the first time. The big hole sits below the slot: you pass the screw heads through, let the enclosure drop, and the screws ride up to rest at the top of each slot (Z = 143.0). Built the other way round the screw sits in the big hole with nothing trapping it, and the enclosure lifts straight off.

They're also not open holes. Each is a two-layer membrane — the cutters span ±3.5 mm about the wall centre and stop 0.5 mm short of the inner face, so the back reads as a closed surface until you pierce it. Keeps dust and fingers out of the electronics.

A third fixing hangs below the enclosure: a 44 mm wide external ear, 22 mm drop, 5 mm thick (1 mm more than the wall, for meat around the screw), with a Ø6.5 hole and a tapered R9 nose. It runs 2 mm up into the bottom wall so the joint is volumetric rather than a surface weld, and carries two gussets. Two keyholes alone let the enclosure pivot; this stops it.

RJ45 — waterproof receptacle, D-cutout

A Ø21.2 mm D-shaped cutout through the top wall for a YT-RJ45-JSX-29-001 waterproof panel receptacle — Ø21.2 with a flat at 19.7 across (a 10.87 mm chord sitting 9.10 mm off centre), the flat facing the wall side. The flat is the receptacle's anti-rotation feature, so the connector can't spin when you torque the nut.

Positioned at X = −75.0, and Y = 26.0 forward of the back face — far enough forward that the retaining nut clears the back-wall rib grid, which tops out at Y = 10.

Fastening — ten heat-set inserts

Ten M3 × 4 mm slant-knurled brass heat-set inserts: eight in Ø7.5 × 10 mm speaker bosses, two in Ø10 × 8 mm blower standoffs. Every bore is Ø4.0 × 6.0 mm, which leaves only a 1.50 mm annulus of plastic on the speaker bosses (2.75 mm on the fan ones) — that thin ring is why the print spec calls for 3 perimeters rather than 2, so it comes out solid instead of part infill. An insert pulling out of a boss is the most likely mechanical failure on this part.

A 'heat_insert' / 'self_tap' switch in the parameter dict reverts every boss to pilot holes for No.6 self-tappers in one edit, if you'd rather not buy an insert iron.

Also in there

Angled side and bottom vent slots (sheared through the wall thickness so they aren't see-through), a perforated speaker grille of 39 holes per driver, a 150 × 90 mm rear service opening, and an internal waffle rib grid — 27 segments after automatic clipping against ten keep-out zones so it never crosses a keyhole, an access hole or a boss.


Headless verification

verify_enclosure.py runs the checks that caught real bugs during development. It exists because OCC's own validity flag is not trustworthy on this model — the build has reported valid: True while producing inverted patches with negative volume, non-manifold edges, and a boolean that silently swallowed a cutter.

$ python verify_enclosure.py

1. MESH INTEGRITY
  PASS  single connected body (found 1)
  PASS  watertight
  PASS  no non-manifold edges (found 0)
  PASS  bbox X/Y/Z = 247.00 / 85.56 / 177.00 mm

2. PRINT ORIENTATION
  orientation                    height   overhang   bed contact
  BACK face down  (CORRECT)      85.6mm       2.5%      24,609mm2
  side face down                247.0mm       7.1%      11,286mm2
  front face down                85.6mm      14.0%           0mm2
  bottom face down (upright)    177.0mm      22.6%          16mm2

3. INTERFERENCE WITH THE PLACED BODIES
  PASS  fan_placed.step         0.00% of 400 points inside the plastic
  PASS  speaker_placed.step     0.00% of 400 points inside the plastic
  PASS  display_placed.step     0.25% of 400 points inside the plastic

4. MATERIAL AND PRINT TIME ESTIMATE
  TOTAL EXTRUDED    :   275.5 cm3  = 342 g PLA  (70% dense)
  print time        : 7-13 h on a Bambu A1

Two things worth calling out about section 3. OCC's boolean intersect() reports the entire 80,300 mm³ left speaker as inside the enclosure — it is open space. And axis-aligned ray casting is degenerate against this model: a +X ray fired from inside the fan runs straight down the duct bore and grazes its wall, flipping parity on ~1% of samples and inventing a fan clash that isn't there. The verifier uses three oblique directions with a majority vote.


Engineering worth reading about

Duct aerodynamics. The blower's outlet is ducted to the right wall above the speaker. Rather than just area-matching, the loss was modelled properly: a 1D lumped-loss model with area and wetted perimeter sampled at 400 stations along the real loft, Colebrook friction, sudden-expansion / contraction / bend / exit terms, intersected with a parametric 5015 fan curve. Net penalty ~6–9%, exit kinetic energy dominant.

An early version of that model counted the 150 × 90 mm back opening as intake area. It presses against the wall, so it's closed — the real intake is 1,217 mm² of bottom vents, not 13,500 mm². The conclusion survived; the number didn't.

Printability. Designed around back-face-down printing throughout. The duct was re-grounded onto the back wall specifically to eliminate its support requirement, taking total overhang from 6,757 mm² (3.4%) to 5,337 mm² (2.5%).

Cost engineering. The first vendor quote came back at ₹5,305. Their engine reported "Material Volume 745.50 cm³" — exactly 2.00× the part's true 373.5 cm³. It doubles solid volume as a blanket support allowance and multiplies by a fixed rate; orientation is not an input, so rotating the model changes nothing. That's roughly 4× what the part actually consumes at sane settings. Same part, per-gram pricing, correct orientation: ~₹2,050.

Both vendor auto-orient tools tried to print it standing upright — 177 mm tall, balanced on 16 mm² of bed contact, generating as much support plastic as the part itself.

Fastening. Ten M3 × 4 mm brass heat-set inserts, with a reversible 'heat_insert' / 'self_tap' mode switch in the parameter dict. Boss walls are sized so the 1.75 mm annulus around each Ø4.0 bore prints solid at 3 perimeters.


Build it

conda env create -f environment.yml     # reliable on Windows
conda activate encl

python build_enclosure_cq.py    # -> enclosure.step + enclosure.stl  (~2 min)
python place_parts.py           # -> the assembly-check bodies
python verify_enclosure.py      # exit 0 = safe to slice

Pinned to CadQuery 2.7.0 / Python 3.10 / trimesh 5.0.0. Verified to rebuild from a bare directory containing only the three scripts.

Print it

Back face down, 85.6 mm build height. 0.2 mm layers, 3 perimeters, 20% infill, matte black PLA. Full rationale, vendor comparison and an order template in PRINTING.md.


Repository layout

Path
build_enclosure_cq.py The entire model. All dimensions in the P{} dict
place_parts.py Places display / speakers / fan into the build frame
verify_enclosure.py Verification suite
BOM.md Every part, with model numbers and buying links
PRINTING.md Orientation, settings, cost analysis, vendors
OPEN_ISSUES.md Unresolved items, known traps, superseded-facts table
MANIFEST.md What every file is
DESIGN_LOG.md 93 KB chronological log — every measurement and every bug
enclosure.step The deliverable

Reference models

Vendor CAD for the display, blower and speaker is not redistributed here. build_enclosure_cq.py doesn't need it; only place_parts.py does. Download from the vendors and drop into the repo root:

  • 10_1-DSI-TOUCH-A-20250702.stp — Waveshare 10.1" DSI display
  • Radial-Cooling-Fan-5015-DC12V.STEP — 5015 12 V radial blower
  • speaker_unibody.step — speaker

Still open

The design has been printed and assembled. Status from the physical part:

Confirmed good — speaker mounting-hole spacing lands correctly, and the bezel terracing reads as a deliberate finish in matte black rather than a defect.

Still open — one of the 39 grille holes in the −X wall's upper driver group is solid: a ray through Y = 41.62, Z = 98.40 crosses the wall at both faces, so the cutter did nothing there. It's a CAD defect, not a print fault, and OCC has resisted six different fixes (chained cuts, one 78-tool cut, fuzzy boolean, depth jitter, and a double pass that collapsed the model to 44 cm³). Impact is one blocked hole out of 39 — drillable with a Ø3 bit.

What makes it interesting is that nothing caught it for months. A missing hole is still watertight, still manifold, still valid: True, and 28 mm³ out of 373,500 is invisible in a volume check. The verifier now counts grille holes per wall specifically so it can't pass silently again.

Full detail in OPEN_ISSUES.md.

License

MIT for the code and the enclosure geometry. Vendor CAD excluded — see LICENSE.

About

Parametric 3D-printable wall enclosure for a 10.1" DSI touch display, Raspberry Pi 5, Waveshare 8Ω 5W speakers and a ducted 5015 blower — defined in CadQuery, verified headlessly.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages