Five drawing sets arrive in five different states — from a native CAD export to a scan of a hand drawing with no CAD underneath. Each needs a different way in. None of them needs an AI vision model, and that is the finding that shaped everything else.
Four of these drawings were plotted from CAD. What looks like a picture is still stored underneath as lines, text and dimensions — so nothing has to be recognised by eye, and the reader takes the architect's own structure rather than guessing at it. The fifth is a scan of a hand drawing, and it is the only one where the pipeline must work from colour and shape.
| Pipeline | Applies to | Reader | Source carries |
|---|---|---|---|
| vector permit set | Bear's Club (wiener) — 34-page permit PDF plotted from CAD | readers/wiener_pdf.py | vector paths, text layer, dimension strings, a door/window schedule |
| native cad | Commodore (connors) — 5 DWG exported from Revit | readers/connors_dwg.py | layers, block names, the Revit family-type ElementId, level datums |
| landscape cad | Landscape (davis) — 23 Land F/X files | readers/davis_dwg.py | site polylines, planting blocks, species in an XML sidecar |
| revision series | Landscape · revisions (davis-revisions) — 5 dated versions | readers/davis_revisions.py | the same site at five points in design development |
| hand drawing | Riverside Drive — a SCAN of a hand-drawn presentation plan | readers/riverside_sketch.py | pixels. No vector geometry, no text layer, no dimension strings. |
The scale step is where this one earns its keep. Plotted dimension lines carry a constant overshoot; fitting a slope alone gives a ~10% scale error that nothing downstream detects, because the output still looks correct. Fitting slope and intercept removes it.
Before choosing an origin the reader asks whether the sheets already share one. They did — and re-basing each sheet to its own extents would have placed one floor 32.57 ft east of another, silently, with every sheet still internally consistent.
This is the hard case and it is deliberately scoped down. The printed ⅛" = 1'-0" note is the only measurement on the sheet, which puts a hard floor of 0.640 inches per pixel on anything derived from it. What comes out is where things are and how big they are — and explicitly not wall thicknesses, openings, rooms or heights.
There is no model in any path. The same drawing produces the same model, and a change in the output always means a change in the input — for four of five projects that holds at the byte level. The one non-deterministic input is a person naming what the drawing cannot say: which walls belong to which structure, and how many structures the sheet actually shows. Every threshold was then swept against that count. The sweep is deterministic; the target is not derivable from the sheet, and saying so is the point.
| Approach | Why it failed | Measured |
|---|---|---|
| buildings = site minus every coloured material | every unwashed pixel becomes 'building', so walkways, terraces and a dozen separate structures merged into ONE 12,413 sf mass. Paving had no class of its own. | 11 masses / 16,651 sf on a 34,853 sf lot — 48% site coverage |
| structures = regions where wall-bar density exceeds a threshold | a single global scale either under-fills small rooms or merges whole wings. Produced organic blobs, not the rectangular shapes the linework describes. | 13 blobs; only 26% of traced edge length within 8 deg of orthogonal |
| fill the regions enclosed by the heavy poche bars | the bars are DISCRETE SEGMENTS, not closed outlines — rooms are implied, not enclosed. | flood-filling enclosures returns 0.04% of the sheet; per-component hole-filling finds 2 structures |
| group wall bars by proximity into structures | the bars of ONE structure sit far apart, because a garage is drawn as a few segments with a wide door opening between them. No distance bridges them without merging the site. | separations within one structure: golf-cart garage 19.9 ft, bonus garage 26.6 ft, 2-car-everyday 29.2 ft. Cluster sweep: 6.0 ft -> 6 structures, 6.8 ft -> 4 (bonus garage merges into the house), 8.1 ft -> 2. |
| make the Riverside IFC byte-identical across rebuilds like the other four | IfcOpenShell interns simple entities through a set, so an identical graph is written with permuted entity numbering. Creating entities directly instead of through ifcopenshell.api did not fix it. | publish a SEMANTIC fingerprint over the sorted (GlobalId, class, name) set — verified stable across three builds — and state the exception rather than quote a hash that moves. |
Each of these is kept in the recipe registry with its root cause, so the next person does not spend a pass rediscovering it.
A structure is drawn wall plus the unwashed interior it bounds. Interiors are bare paper; every outdoor surface carries a colour wash — measured at a garage interior 0.008–0.013 saturation against paving 0.101 and motorcourt 0.19–0.23. Saturation separates inside from outside outright, which is why the knob is a colour threshold and not a distance.
house + attached garage, two garages, casita, dock tackle — validated against operator-named walls
801 sf of drawn poché, the one thing measured rather than inferred
the accuracy floor no upsampling recovers
in any of the five paths
Two figures are declared, not measured, and travel on every object as properties so a consumer sees it without reading documentation: massing height 12 ft, and canopy height at 1.2 × crown spread. The sheet is a plan and states no heights at all.