The model said it fit
Every Croplock enclosure is a source file in the repository and a print on a small desktop printer. Here is how they are made, and what three months of parts that passed every check and failed in the hand taught us about checking.
Croplock is software, but software in a grow tent needs somewhere to live. The hub needs a box that keeps a single-board computer cool outside the tent. Each sensor pod needs a shell that holds a small board and a sensor, lets air reach the sensor, and clamps to a tent pole. None of that is a part you can buy, so we print it.
Since the first enclosure landed in late June, the directory that holds them has grown to 17 parametric source files and 39 printable meshes, across more than two hundred commits. Everything is written in OpenSCAD — geometry as code, which means every dimension is a named parameter, every revision is a diff, and a fix to a shared joint is a fix everywhere that joint is used. The parts are printed and assembled on our bench, on a Bambu A1 Mini with a 180 mm bed.
That split — a model on one side, a part in a hand on the other — is what this post is about. The short version: when the model and the part disagree, the part is right, every time.
Why print at all
We build to order, and injection molding needs tooling and inventory we will not carry. A printed shell costs cents of filament, changes in an afternoon, and can be tuned to a board we have only read the datasheet for. Board dimensions come from the manufacturer’s published mechanical drawings, and a fit is judged by the mating part — the board either seats in its bay or it does not. When we need a go/no-go answer on something finer, we print a gauge.
The mount: from a snap clip to a screw collar
The first pods clipped onto the tent pole with a printed C-shaped snap. It worked the first time and a little less every time after: opening the clip over the pole stretched it, and a stretched clip stops gripping. A mount that gets weaker each time you use it is not a mount.
It was replaced across the whole fleet by a two-piece screw collar — a split ring bolted around the pole, with a captive nut in one half and a counterbored screw head in the other. Every part that mounts to a pole includes the same collar source file, and every one uses the same M3 screw and nut. The ring later became a V-block clamp, so one print fits four common pole diameters.
One assembly rule is worth passing on to anyone who builds a split clamp: the two halves must never touch before they grip the pole. If they bottom out on each other, the screws clamp plastic to plastic, the collar feels tight, and the pod rotates on the pole anyway — weeks later.
Four screws that held nothing
The hub lid is held on by four screws into captive nuts in the base. They threaded, they tightened, the box closed. They also did nothing. The nut pocket opened flush at the top of the base with no material above the nut, so tightening pulled the nut up against the underside of the lid: the head pushed down on the lid, the nut pulled up on the lid, and the base took no load at all.
The fix — a roof over the nut, loaded from a slot in the side — got the load path right and exposed a second problem. The slot sat at the same height as the nut, so the nut’s working position was missing three of its six flats. Tightening was fine. Unscrewing pushed the nut straight back out through the slot it came in by — found by unscrewing, the one direction no check had tried. The final form puts the entry below a fully enclosed hexagon and lets the screw draw the nut up into it, so the way in and the thing that holds it are different features.
The question that would have caught both, and that we now ask of every threaded joint: when this screw is tight, what does the nut bear against — and is that the thing we are trying to clamp?
The board held down by 31 millimetres of air
For weeks the hub’s design notes said the lid clamped the board in place. Measured in the model, the top of the board and the underside of the lid were 31 mm apart. Nothing held the board down; it sat on locating pins and moved the first time a USB-C plug was pushed into it.
It is now screwed to four posts with M2 self-tapping screws — M2×5, not the M2×8 used on the pod lids. On a pod lid that screw passes through 3 mm of plastic; here it passes through a 1.6 mm circuit board, so the longer screw would bottom out in its pilot hole: tight in the hole, clamping nothing, the exact failure the revision was meant to fix. The screw length is now checked by an assertion in the model instead of by someone remembering.
The hub base also has exactly one connector opening: power. The board has an HDMI port and a microSD slot, and the enclosure deliberately covers both. Grower data stays on the device, and a slot that lets anyone pull the card out of the box would quietly undo that in plastic. The vents and the fan stay, because the chip needs them.
The bezel that kept falling out
The camera pod had a small printed plug that closed the slot above the lens. It fell out. The first fix trusted the lid to trap it. The second cut a T-slot so the wall would hold it. The third moved it 0.225 mm. After each one the model reported that the plug did not collide with the base and that a pull-out check found it retained — and the printed plug fell out of the printed pod anyway.
Every check had asked “once it is seated, does it stay?” None had asked “can it get to its seat?” The fix was to stop trying to retain a loose part and delete it: the plug became a tongue on the underside of the lid, held by the four screws that already hold the lid. Part count went from four to three, and falling out stopped being possible, because there was no separate object left to fall.
Deleting it paid twice. The wide internal channel that existed only to hold the bezel had been cutting four small holes clean through the front face beside the lens. With the bezel gone the channel went too, and so did the holes. That revision was verified by ray-casting the exported mesh on a half-millimetre grid rather than by looking at a render.
Checks that could not fail
We run a lot of automated geometry checks, and the most useful thing they taught us is how many ways a check can pass without checking anything.
- The right question at the wrong scope. A check confirmed that a boom part touched the print bed. It did — its pole clamp sat on the bed. Meanwhile 26 separate bodies in the same file floated in mid-air, which the slicer showed at a glance. A whole-file minimum cannot see a floating piece inside a part that also touches the bed; the replacement script asks the question of every connected body separately.
- A check that works and is never run. Twelve days later another part shipped with three floating bodies. That script caught it correctly on its first run — after the fact, because nothing had run it. It is now a pre-commit gate that costs nothing unless a mesh is being committed. A check that exists and is not run is the same as no check.
- A check named so the runner never saw it. The test runner collected checks by name prefix. The check that asked whether each pod’s lid actually shuts had the wrong name, so its control ran on every commit and the check itself never did.
- A check against a part nobody prints. Every shipped collar is exported with a build flag that switches on the V-block clamp. For a while the checks ran without it — against the old round clamp — and one of them could never fail in that mode no matter where the parts sat. Comparing exported meshes to their source the same way made correct meshes report as mismatched, and some were overwritten before the cause was found. Build flags now live in a manifest that the export, the comparison and the checks all read.
- A “must be solid” check satisfied by nothing. Two faces that merely touch produce a zero-thickness shell. To a check that asks “did any geometry come out?” that shell counts as solid, so a check that a card was stopped by a backstop passed with nothing stopping the card. Probes are now inset a twentieth of a millimetre, so contact is either real volume or absent.
- A typo that renders. OpenSCAD does not stop on an undefined name; it warns, substitutes nothing, and carries on. One pod rendered for weeks with its pole collar buried inside the box, because the variable that positioned it was never defined and quietly became zero. The check runner now fails the build on that warning.
The practice that comes out of all six: prove a check can fail before trusting that it passes. Put the broken geometry back and confirm the check goes red.
Numbers the printer decides, not the drawing
- A printed hole is not a machined hole. One lid tube was sized from the circuit board’s own machined mounting hole, leaving well under a tenth of a millimetre around its peg. Printed holes come out small, so the peg never went in and the lid sat 2.75 mm proud. A printed hole over a pin now gets at least 0.4 mm of radial clearance, enforced by an assertion.
- A feature thinner than one extrusion line does not exist. A board fit on the leaf-temperature pod was tuned across five print rounds, with crush ribs from 0.15 to 0.35 mm proud. The printer lays a 0.42 mm outer line, so none of the ribs ever printed and the fit was always decided by the bare wall. The rib module now refuses to render one below the printer’s line width.
- The bed is 180 mm, and a comment is not a measurement. A boom part commented as fitting a 200 mm bed with room to spare exported at 180.5. A convenience layout of scissor links was 217 mm wide. Every mesh is now bounds-checked against the bed we actually own.
Material, and why colour is not cosmetic
Prototypes were printed in PLA; production parts are PETG, because a warm tent will slowly creep PLA over the length of a grow. The switch was a re-fit, not a filament swap — PETG prints slightly fatter and is less stiff, so clearances tuned in PLA had to be looked at again.
We also learned that a mesh file carries no material. The printer project sitting beside the meshes had PLA baked into it while every document said PETG, and the slicer was the first thing to notice. Each part now ships with a generated print project that pins the material and the printer vendor’s own PETG profile. We first seeded it with a generic PETG profile, which turned out to run ten degrees hotter, with no retraction and nearly twice the cooling fan — a recipe for stringing.
Colour matters on a sensor pod. An air-temperature pod reprinted in black read about 0.35 °C warmer against the same reference than it had in its lighter shell. A dark shell absorbs light from the lamp and warms the air around the sensor inside it, which pushes temperature up and humidity down — and both errors move computed VPD in the same direction. Sensor pods are printed in light grey.
What we would tell someone starting this
- The part in your hand is the measurement. When the model says it fits and the part says no, the model is answering the wrong question. Go find the question.
- Prefer the fix that makes the failure impossible over the fix that tightens a number. Delete the loose part; make it part of something already fastened.
- Trace every fastener’s load path, in both directions: tightening and undoing.
- Check insertion, not just retention. Sweep a part along the path it installs on.
- Make every check prove it can fail, and make sure something actually runs it.
- Reproduce the build before comparing against it. Keep export flags in a file the tools read.
- Design to the printer: its bed, its line width, and how undersized its holes come out.
- Make every hole in an enclosure justify itself.
Sources
All of this is from the enclosure source files, their revision and print notes, the geometry-check scripts and our bug register, re-read on 2026-09-13. Dimensions are the model’s design values; the 0.35 °C figure is a like-for-like bench comparison on one pod under the grow light, not a controlled study. Parts are revised often; this post describes what we learned, not the current state of any particular part.
Also read: The AI drew the parts. A human made them fit. · The parts we cannot buy · Where should the probe go?