PlanLint · Part 2

Boxes to Walls: how PlanLint grounds a model's guess

A vision model points at every door, window and room and draws a rough box around it. Snapping is the deterministic step that moves that rough box onto the drawing's real geometry, so the size that reaches a code check is measured, not guessed. Here is how it's done, worked on one sheet.

Example
Living Room
Sheet
1st Floor Plan
Scale
1/4" = 1'-0"
Paths
vector · raster

The VLM's boxes are approximate on purpose. Its job is to say what is on the page and roughly where. Snapping takes that label and finds the exact extents in the geometry underneath: the wall a room is bounded by, the gap a door cuts through a wall. If the geometry can't confirm a size, the asset is left unmeasured and marked for review, never handed a made-up number.

01
Classify
route each sheet by type
02
Detect
VLM boxes every entity
03
Snap
ground each box in geometry
04
Measure
extents × scale
05
Check
pure-Python verdict
VLM box, approximate snapped, grounded wall geometry gap = clear width flood fill corner anchor
First-floor-plan sheet with the Living Room region highlighted
FIG 0 The worked example: the Living Room and its openings.

AVector drawings

A born-digital CAD PDF carries exact line-work, so geometry is the ground truth: pure math over the primitives, no model. This is the high-confidence path.

A · 1

Read the walls

Every line and rectangle edge is pulled from the PDF and the long, straight, axis-aligned ones are grouped into wall runs (blue). Segments that end inside a text label, such as dimension lines and callout leaders, are dropped. What survives are wall candidates: a counter edge or a run of porch decking is long and straight too. The coverage, corner and flanked-gap tests in the next steps are what settle which of them are really walls. extract_primitives, build_wall_runs

Extracted wall runs over the Living Room, drawn in blue
FIG A1 The room's bounding walls, recovered deterministically.
A · 2

Snap the room to its inner walls

Each edge of the amber box moves to the nearest wall run that covers most of the room's width or height using a coverage test rather than a center-crossing one, so a doorway sitting at the room's midline doesn't defeat it. The edge is then refined onto the wall's room-facing inner face using the corners where perpendicular walls meet, which also rejects any stray line that isn't part of a real corner. The green box traces the interior exactly. snap_room_box

Living Room box snapped from the loose amber box to the green box on the inner wall faces
FIG A2 Amber → green: every edge on the inner wall face (corners marked).
A · 3

Ground an opening in the wall gap

A door or window is the gap in a wall run, flanked by wall on both sides. The box is rebuilt as that gap span × the wall's thickness, and the gap length, read straight from the geometry, is the clear width (red). Here that is 24". Guards keep a door from swallowing a whole wall; a solid (hatched) shape filling the box marks a pier, not an opening, and is dropped. classify_opening_vector

Window box snapped onto the wall gap, with a clear width of 24 inches
FIG A3 The flanked gap between wall intervals is the clear width.

BScanned drawings

When a sheet is a scan (or a flattened PDF with no usable line-work), geometry comes from the pixels instead: deterministic OpenCV, still no model.

B · 1

Build a wall mask

The rendered page is thresholded to ink, hatched and double-line walls are consolidated into solid bands, and only strokes that are both thick and long survive, so text and symbols fall away. The opening kernel is capped so thin interior partitions aren't erased along with the noise. analyze_page_image

Wall mask of the Living Room built from pixels, showing the perimeter and a thin interior partition
FIG B1 Walls as pixels: perimeter and the thin right-hand partition both kept.
B · 2

Reconcile the room box, edge by edge

A flood fill grows from the box center through free space (openings sealed first) to bound the room and measure its interior area. Its box is then reconciled with the VLM box per edge: whichever edge a wall actually backs in the mask is kept. On this room the flood fill nails the top and bottom (fill), and the VLM holds the left and right (VLM) where the fill would have leaked out a doorway, no wall backs that edge, so the VLM edge wins. Every side lands on its wall. snap_room, reconcile_room_box

Reconciled raster room box with each edge labelled by the source it was kept from
FIG B2 Each edge kept from the source a wall corroborates.

Pixels are a weaker witness, and the system says so

A hatched pier and a densely-drawn window can look identical in a mask, so an ambiguous raster opening is reported as informational and never allowed to fail a check. That is why a vector-snapped result outranks a raster-snapped one below.


every measurement is labelled

Provenance sets the confidence

A value carries where it came from, and a stronger witness outranks a weaker one. A size nothing can confirm becomes NEEDS_REVIEW, never a fabricated number.

0.95
vector-snapped
Grounded in CAD line-work: the wall gap, the bounding runs. The strongest witness.
0.80
raster-snapped
Grounded in pixels: flood fill and wall-mask edges. Real evidence, weaker than vectors.
0.60
vlm-only
Nothing confirmed the box. Kept for review; it can never drive a pass on its own.

The one line the whole design protects

Snapping turns a model's approximation into a grounded measurement, or holds it back for a human. The VLM reasons about which feature is where; the geometry proves how big it is; and a pure-Python checker compares that measurement to the code. No model output ever flows straight into a verdict.

Generated from the American Farmhouse set (CC-BY-SA, freefarmhouse.com) · PlanLint · figures rendered by the live vector_geometry / raster_geometry functions.