A 3D-printed RC displacement boat, 480 mm LOA, developed from one screenshot of a hull-design tool using atompipe.
Start here: docs/readiness.md — what is proven, what is
assumed, and what only a real boat in real water can settle.
19 bodies: 13 printed, 6 bought parts shown where they sit. Renders are
generated from the same STLs the gates measure — python tools/render.py.
docs/BUY.md |
what to order, with prices and ASINs |
docs/PRINT.md |
what to print (7 parts, 4 beds), in what orientation, every screw, every hole to drill |
docs/BUILD.md |
the assembly order, arranged so mistakes stay cheap |
docs/decisions.md |
every design decision and what LOST |
FRICTION.md |
an unflattering log of using the tool |
The hull in the screenshot is 300 mm long and displaces 73 grams at the waterline the design tool drew. The parts alone weigh 591 g. That waterline was a slider position, not a loading condition.
Displacement scales as the cube of length while a motor, an ESC, a servo, a receiver and a battery do not scale at all, so the hull is scaled 1.6x to 480 mm and the FORM is untouched — every offset is the traced one times 1.6. At that size it floats at 29.6 mm draft with 27.3 mm of freeboard after trim, and 70% of its moulded volume in reserve.
The transom, both watertight bulkheads, the centre girder, the shaft seat and the aft deck are printed INTO the hull segments rather than glued on. That is not tidiness: a bulkhead with no bond line cannot leak at the bond line, and watertightness is the top physical risk on the whole boat. It works because each segment prints standing on a transverse face with its one solid cross-wall ON THE BED — put a cross-wall at the far end of a segment instead and it becomes a 186 mm plate the print has to bridge.
Two merges were rejected and both are in the decision log: the stem cap, and the bow deck. Both would have roofed an open cavity at the top of a print, and both being separate is also what leaves the bow compartment open until its foam and interior epoxy are in.
Because a one-piece hull does not exist at any length that floats. On the 220×220×250 machine this project targets, an exhaustive orientation search against the real hull surface gives:
| largest hull that prints in ONE piece | 321 mm LOA |
| shortest hull that floats the payload with 25 mm freeboard | 376 mm LOA |
| payload a one-piece 321 mm hull could carry | 245 g, against 630 g of fixed hardware |
| build volume a one-piece 480 mm hull needs | 336 × 336 × 336 mm |
Splitting horizontally at the waterline does not help: the binding dimension is LENGTH, and removing the topsides shrinks the required box by 1.2%. Two segments is blocked on sourcing, not printing — it needs a 260 mm stuffing tube and the kit on the BOM has a 200 mm one. All three rejections are in the decision log with their numbers.
The driveline, the rudder, twelve M3 screws, ten brass inserts and four wire runs are
all modelled as solids, so cad.clash sees them and so does the render. Every boss is
blind except the two that bolt the rudder bracket to the transom.
boat.hull_penetrations enumerates every hole through the hull shell or a watertight
bulkhead, states how each is sealed, and refuses more than one below the loaded
waterline — comparing against the waterline the hydrostatics produced on that run, not
a number typed beside it. There is exactly one: the propeller shaft, which is
unavoidable because the propeller has to be driven from inside.
The gate found a second one the minute it existed — the same stuffing tube also crosses the aft bulkhead, 9 mm under water, and nothing had ever said so.
Every geometry gate in every installed pack checks that things do not touch, or that one solid is well formed. None of them can express an absence. So the boat shipped a revision whose rudder had no stock and no tiller arm, whose blade hung 43 mm below its own bracket attached to nothing, and whose pushrod stopped 11 mm short of what it was pushing — with every gate green.
That idea is now cad.assembly_connected in the cad-solid pack, with the question
turned around: instead of checking chains somebody declared, it builds the contact
graph from the geometry and asks whether every part is held by something. The
declared-pairs version found nothing on this project; the coverage version immediately
found the on/off switch floating 5.5 mm from the nearest part, held by tape that
existed only in my head.
The project's own copy has been retired — it duplicated the pack, measured worse, and
depended on rtree, which this project never declared. Where rtree was missing every
gap came back NaN, and NaN defeats every comparator, so the gate written to prove
the steering was connected was the one gate that could neither pass nor fail. The
project now declares its dependencies in requirements.txt and
needs no rtree at all.
See FRICTION.md items 26 and 30-33; they are the most transferable
things in this repository.
brief/ the screenshot this all came from
inputs/ evidence, and what was read out of it
tools/ trace_hull_profile.py screenshot -> hull offsets
emit_docs.py model -> BUY/PRINT/BUILD
render.py STLs -> renders/ (no GPU, no display)
renders/ generated. Regenerate with tools/render.py.
model/ hullform.py the hull surface and its integrals
geometry.py offsets -> meshes
boat.py THE MODEL. Everything else is generated from it.
gates/ the seven checks no installed pack covers
selftest/ a known-bad input for each of those seven
build/ generated STLs (assembly frame) and build/print/ (bed frame)
docs/ generated: readiness, buy, print, build, decisions
site/ the same ledger as a page: atompipe site serve
.venv/bin/atompipe check # tier 0, under a second
.venv/bin/atompipe check --tier 1 # the full sweep, including meshes
.venv/bin/atompipe gate selftest # prove every gate can still fail
.venv/bin/atompipe report # the readiness report
.venv/bin/atompipe why hull_scale # one parameter's whole history
.venv/bin/atompipe site serve # the ledger as a page
.venv/bin/python tools/emit_docs.py # regenerate the buy/print/build docsGenerated files are outputs. Do not hand-edit anything in build/ or docs/;
change model/boat.py and re-run.




