What bldg buys: measured
Measured on 55 ground-truth plans: 31 from public floor-plan datasets plus 24 synthetic. Together they hold 2,095 walls, 996 openings, 393 rooms and 18 components after the collinear merge described below.
Where these numbers come from. They were measured with the earlier Python implementation of bldg, on its version of the format, with its benchmark scripts and an earlier local set of test plans; the function names below (emit_dict, canonical_dict, to_svg ...) are that implementation's, and the DXF and IFC files were written by its exporters. Those scripts are not part of bldg today, and the current engine has not re-run them.
Frame, units, precision. Every plan is stored as a bldg document in millimetres (+y up), traced on its image, which is the level's underlay: Source.scale mm per image pixel, 27 plans with a calibrated scale (the dataset gives one) and 28 with an assumed one (scale_status: assumed, the stand-in 10 mm/px; their type ids are numbered, Q1 / M1, because a size read off an assumed scale is not a physical size). Every model-side encoding below is written in the LLM image frame (llm_schema(task="extract", frame="image", length_unit="mm")): coordinates and planar lengths in image pixels, x right, y down from the image's top-left corner, to 0.1 px; heights in mm. The stored document is in mm to 0.01 mm. Coordinate noise is generated in image pixels. Lengths that are reported in mm use the 27 calibrated plans only.
Tokens for the same drawing, at full (fine) drawing detail
This comparison is at ONE drawing depth only, the compiler's fine render detail, the full drawing; no coarse or medium figures are measured. At fine the drawing has walls with their joins and cuts, doors with frame / jambs, leaf thickness and swing arc, windows with frame, sash, glass lines and sill, furniture and fittings as catalog symbols, stairs, printed dimensions, door / window type marks, room names with net areas and the north arrow. The reference is the SVG the compiler draws at fine (to_svg(detail="fine")), parsed into its visible primitives (lines, polylines / polygons, circles, arcs, filled rings, texts) in draw order; the hidden clearance layer and the invisible hover footprints are not drawn and not counted. Every baseline is generated from those primitives, so it holds the same drawing, primitive for primitive:
- SVG (minified drawing): what a model would write to draw the plan directly: one element per primitive with the compiler's class names, no
data-*semantics, no groups, no metadata. The compiled SVG itself (newlines removed, itsdata-*semantics included) is listed for reference. - DXF-like entity JSON: LINE / LWPOLYLINE / CIRCLE / ARC / HATCH / TEXT / MTEXT entities with their layer names, as compact JSON.
- Absolute-geometry primitive JSON (the flat-geometry approach): every drawn primitive in world coordinates with a type tag.
- GeoJSON of the same primitives (LineString / Polygon / Point; GeoJSON has no curves, so arcs and circles are chords with a sagitta of at most one drawing quantum).
Coordinates in every baseline are rounded to the drawing quantum (1 mm in a mm document, 0.01 in in an inch one), angles to 0.01°, defaults left out. That is coarser than bldg's own 0.01 mm grid, so the baselines get the benefit of the doubt. The pens (the style sheet or DXF layer table: colours, line weights, fills, dashes) are not counted on either side: bldg's compiler supplies them, and so would a baseline's renderer by convention (written out, they add about 851 tokens per plan as an SVG style sheet). bldg is the document the drawing compiles from: the extract answer a model writes (plan frame, document unit, defaults left out), the detail answer (with heights) and the stored canonical document. Tokens: tiktoken o200k_base on minified JSON / SVG. No Anthropic count is given, because that needs the API.
Same drawing, checked. Each baseline is decoded back to primitives and compared with the reference one by one: same count, order, style and text, every point within 1.5 quanta, and a chord polyline lying on its arc or circle with a sagitta within that. SVG passes on 58 of 58 plans (55 ground-truth plans plus the 3 editor examples), DXF-like on 58 of 58, absolute geometry on 58 of 58 and GeoJSON on 58 of 58. Each bldg answer is decoded the way a model's answer is (ingest, with the stored plan as document context for document-only fields and the heights the extract task leaves out) and compiled again. It draws exactly the reference primitives: the extract answer on 58 of 58 plans, the detail answer on 58 of 58, the stored document on 58 of 58. The detail answer decoded with no context at all draws the same on 47 of 58; on the other 11 only the room names and areas are placed differently (and the type marks that keep clear of them, or the answer does not validate without the storey height a stair needs): a hand-moved label (Room.label_offset) is a document-only field. Rasterised in Chrome (1600 px on the long side, 232 renders), each baseline drawn back differs from the compiled SVG on at most 0.09% (SVG), 0.08% (DXF-like), 0.08% (absolute geometry), 0.08% (GeoJSON) of the pixels (a channel off by more than 64), which is the anti-aliasing of the rounded coordinates.
55 ground-truth plans (2,095 walls, 996 openings (506 doors and cased openings, 490 windows), 393 rooms, 18 components; 11,862 drawn primitives)
| encoding of the same fine drawing | total tokens | × bldg extract | median per plan |
|---|---|---|---|
| SVG (minified drawing) | 411,924 | 2.6× | 2.9× |
| DXF-like entity JSON | 423,859 | 2.7× | 2.9× |
| absolute-geometry primitive JSON | 339,997 | 2.2× | 2.3× |
| GeoJSON | 583,739 | 3.7× | 4.0× |
| SVG as compiled (with data-* semantics) | 1,054,654 | 6.8× | 7.3× |
| bldg extract answer | 156,055 | 1.00× | |
| bldg detail answer | 156,090 | 1.00× | |
| bldg stored document | 165,711 | 1.06× |
The editor examples (fine drawing)
| plan | walls / doors / windows / rooms / components / dimensions | primitives | bldg extract | SVG | DXF-like | absolute geometry | GeoJSON | bldg stored |
|---|---|---|---|---|---|---|---|---|
| synthetic_19 | 26 / 8 / 13 / 8 / 30 / 8 | 426 | 3,842 | 18,224 (4.7×) | 18,130 (4.7×) | 15,349 (4.0×) | 25,569 (6.7×) | 4,435 |
| benchmark | 5 / 1 / 1 / 2 / 0 / 0 | 28 | 470 | 918 (2.0×) | 1,008 (2.1×) | 811 (1.7×) | 1,362 (2.9×) | 564 |
| us_plan | 7 / 2 / 1 / 2 / 3 / 0 | 54 | 581 | 3,022 (5.2×) | 3,034 (5.2×) | 2,632 (4.5×) | 4,663 (8.0×) | 709 |
Per element kind (tokens; all 58 plans together: 55 ground-truth plans plus the 3 editor examples)
Walls: bldg's nodes, walls and wall types; drawn, the cut wall outlines. Doors and windows: their openings and opening types; drawn, frame, leaves, arcs, glass, sills and the type mark. Furniture: components; drawn, their catalog symbols. Rooms: bldg's spaces and groups; drawn, the room fill, name and area. Annotation: printed dimensions, texts and grid; drawn, the same plus the north arrow. "Other" is the frame (headers, metadata, level wrappers).
| kind | drawn primitives | bldg extract | SVG | DXF-like | absolute geometry | GeoJSON |
|---|---|---|---|---|---|---|
| walls | 3,024 | 90,484 | 103,783 (1.1×) | 128,392 (1.4×) | 98,153 (1.1×) | 158,516 (1.8×) |
| doors | 3,496 | 25,308 | 122,079 (4.8×) | 125,986 (5.0×) | 102,230 (4.0×) | 200,466 (7.9×) |
| windows | 4,042 | 21,764 | 136,603 (6.3×) | 127,511 (5.9×) | 104,203 (4.8×) | 171,396 (7.9×) |
| furniture | 338 | 2,084 | 16,047 (7.7×) | 14,874 (7.1×) | 13,039 (6.3×) | 22,213 (10.7×) |
| rooms | 1,213 | 12,366 | 45,438 (3.7×) | 42,363 (3.4×) | 35,040 (2.8×) | 53,665 (4.3×) |
| annotation | 257 | 6,515 | 8,577 (1.3×) | 6,556 (1.0×) | 5,537 (0.8×) | 8,960 (1.4×) |
| other | 0 | 2,427 | 1,561 | 349 | 587 | 117 |
Walls cost 1.1-1.8× as much in the drawing formats: a wall drawn at fine is one outline per segment, not much more than what bldg writes for it (two node references and a type, plus the shared corner nodes). The gap is in the symbols, which the compiler draws from a few semantic fields: doors 4.0-7.9×, windows 4.8-7.9×, furniture 6.3-10.7×. In bldg a door is a type, a host wall, a reference corner, an offset, a hinge and a swing; in a drawing format it is its frames, a leaf rectangle, a swing arc and a mark, each written out as coordinates.
The other comparisons below (semantic answer size, coordinate noise, 3D) measure other things and are kept separate from this one.
Semantic answer size (the same elements, no drawing)
This section compares encodings of the model's semantic answer (walls, openings, rooms, components with their fields), none of which carries drawing primitives; the drawing comparison is the fine-detail section above.
Each plan is converted to bldg (the bridge stores it in project north where it can: turned so its walls are at 0 / 90°, skew under 0.5° removed, the image turned back by its source transform) and repaired once. Straight walls that the dataset split into collinear pieces are then merged into one wall (339 nodes removed: two walls of the same wall type and height, turning by less than 0.5°, with no third wall at the node), because an absolute encoding would write such a wall as one segment. The plan is then rounded to the image grid: every coordinate on the underlay's 0.1 px grid and every planar length (thickness, opening width and offset, component size) a whole number of 0.1 px steps, stored in canonical form (canonical_dict, the form every tool writes: mm to 0.01 mm, integral values as integers, defaults left out), a room whose ring is a closed ring of walls given by a seed point at inside it (256 of 393 rooms), and connects only where it differs from the rooms the compiler finds on both sides of the opening (0 of 996 openings; connects is document-only, a model never writes it). Every wall names a wall type, every opening an opening type and every component a component type, factored from the rounded values (factor_types: equal thickness, role and load bearing share a wall type; equal category, operation, width and height an opening type; equal model, size and parameters a component type), and the types come first. It must validate (14 needed a lint repair after rounding, so that lint leaves every canonical plan unchanged), and writing it in the image frame and ingesting the answer gives the stored document back exactly (15 plans are not exactly in project north, so ingest leaves them unstraightened: 11 lie on an image turned by a few degrees, so their nodes are on the turned image's grid, a sub-pixel off the plan axes; the other 4 keep a skew the bridge or lint left). Every encoding below is generated from that canonical plan, writes its coordinates to the same grid and carries the same walls, openings, rooms, components and metadata with the same non-geometric fields (the absolute and v0 encodings write each room's outline, holes included, where bldg writes its seed point, and they have no types: each wall and opening carries its type's values, as a raw answer does). That content is what a model writes in the extract LLM task (emit_dict(plan, "extract", frame="image"), the task llm_schema asks for): no document-only fields (sources and their scale, evidence, document id, connects, a component's derived room) and no storey / sill / head heights, which the stored plan, its profile defaults and ingest supply. Components are observations (family model, size and parameters inline; ingest turns equal specs into one component type). Door hinge and swing are the strings none / unknown rather than null. Tokens are counted with tiktoken o200k_base on minified JSON.
Same content, checked: the absolute-coordinate encoding is decoded back to bldg (wall ends at identical coordinates become one node, each opening's reference end, offset and width come from its jambs, hinge and swing from the leaves, a room outline that is a closed ring of walls becomes a seed point, any other a node ring) and compared with the canonical plan after ingest(frame="image", length_unit="mm") with the stored plan as document context (the decode order of a model's answer; each wall and opening gets its type back from the stored plan by its values): ids, types, heights, both jambs within 1.5 image steps along the wall (jambs are written to one step), hinge, swing including both, connects, rooms (fields, seed or ring, outline and holes), groups and components. 55 of 55 plans decode to the canonical plan; every bldg encoding, short keys, the plan frame and the one without types included, ingests to exactly the stored canonical document.
| encoding | total tokens | vs absolute coordinates |
|---|---|---|
| absolute-coordinate JSON (raw VLM style, image px) | 181,356 | baseline |
| floorplan/v0 (centreline + outline polygons, image px) | 256,484 | 41% more |
| bldg (extract task, image frame px: defaults omitted, rooms by seed point) | 157,041 | 13% fewer |
| bldg, plan frame (mm, 0.01 mm) | 156,055 | 14% fewer |
| bldg without types (each wall / opening writes its thickness, role, category, operation, width) | 151,710 | 16% fewer |
| bldg, short keys (wire dialect) | 155,912 | 14% fewer |
| bldg, strict mode (every key present) | 176,258 | 3% fewer |
bldg stored document (canonical_dict, mm, component types, document-only fields; not a model output) | 165,711 | 9% fewer |
Median per-plan change for bldg: 18% fewer than absolute coordinates, and 40% fewer than floorplan/v0.
Types: the 2,095 walls name 360 wall types, the 996 openings 878 opening types and the 18 components 18 component types. The traced datasets give each door and window its own traced width (0.1 px), so few openings share a type: on these plans the types add 3.5% (157,041 tokens, against 151,710 with the values written on every element). A drawing with standard sizes (one M0921 for every 900 x 2100 door) shares them; types are there for BIM, schedules and editing (change a type, every instance follows), not for tokens.
Encoding under coordinate noise (mean per plan, 5 seeds)
This measures what each encoding does with imprecise coordinates, not a model's accuracy. Every written coordinate gets independent Gaussian noise with standard deviation σ in image pixels, clamped to the image (bldg's nodes and seeds are mapped to the image, moved there and mapped back to the plan). The absolute encoding is scored as written: an open corner is a corner (one position shared by several wall ends, counted once) whose copies end up more than 1/4 of the wall thickness apart, and an opening is off its wall when its jamb midpoint is more than 0.35 × thickness from the centreline (room outlines are not scored). bldg gets the same noise on its nodes and room seed points and then runs the normal lint and compile; its corners are shared node ids and cannot open, an opening is a distance from a corner of its wall, so it moves with that corner, and a room is the ring of walls around its seed. Everything that pipeline loses or distorts counts, against what was written: walls dropped or collapsed, openings dropped, openings built more than 0.25 × thickness narrower than declared or with the centre moved that far, rooms dropped, rooms built from another ring of walls than the one written (a seed pushed across a wall). σ = 0 is the pipeline's own baseline (openings that run into a wall junction are clipped there).
Topological defects
| noise σ | abs: open corners | abs: openings off wall | abs total | bldg: walls lost | bldg: openings dropped | bldg: openings shrunk | bldg: openings moved | bldg: rooms dropped or moved | bldg total |
|---|---|---|---|---|---|---|---|---|---|
| 0 px | 0.0 | 0.0 | 0.0 | 0.02 | 0.00 | 0.02 | 0.00 | 0.00 | 0.04 |
| 3 px | 15.4 | 1.2 | 16.6 | 0.97 | 0.05 | 0.33 | 0.40 | 0.10 | 1.87 |
| 6 px | 19.8 | 3.9 | 23.7 | 0.96 | 0.08 | 0.78 | 0.66 | 0.17 | 2.65 |
| 12 px | 21.7 | 8.8 | 30.5 | 0.88 | 0.17 | 1.40 | 1.04 | 0.29 | 3.76 |
Absolute coordinates after repair
The open corners of an absolute encoding can be closed after the fact, which is the step that has to rebuild the topology bldg writes. The repair here joins every two wall ends within f × the thinner of their walls (single linkage, so joins chain) and moves each end to the mean of its cluster; openings keep their written host wall, so they can be put back on it. A corner is joined wrongly when its copies end up in more than one cluster (split, still open) or in a cluster with another corner's copies (merged, two corners welded into one). f is chosen per σ from 0.25, 0.375, 0.5, 0.625, 0.75, 1, 1.5 as the one with the fewest wrong joins on these plans, so the repair gets its best case.
| noise σ | best f | abs after repair: corners merged | corners split | wrong joins | bldg topological defects |
|---|---|---|---|---|---|
| 0 px | 0.25 | 0.1 | 0.0 | 0.1 | 0.04 |
| 3 px | 0.5 | 1.9 | 5.3 | 6.9 | 1.87 |
| 6 px | 0.625 | 4.9 | 10.3 | 14.6 | 2.65 |
| 12 px | 0.75 | 6.6 | 15.9 | 21.3 | 3.76 |
Geometric error
Mean distance of a corner (a wall end) from the ground truth, per plan, then the mean over plans: bldg's nodes as written and after lint (the nodes lint keeps), the absolute encoding's corners as written (each corner the mean over its copies) and after the repair above. In image px on all 55 plans and in mm on the 27 plans with a calibrated scale.
| noise σ | bldg as written | bldg + lint | abs as written | abs after repair |
|---|---|---|---|---|
| 0 px | 0.0 px · 0 mm | 0.0 px · 0 mm | 0.0 px · 0 mm | 0.0 px · 0 mm |
| 3 px | 3.8 px · 42 mm | 3.9 px · 43 mm | 3.8 px · 42 mm | 3.1 px · 32 mm |
| 6 px | 7.4 px · 81 mm | 7.5 px · 81 mm | 7.5 px · 83 mm | 6.8 px · 71 mm |
| 12 px | 14.9 px · 165 mm | 15.0 px · 166 mm | 14.9 px · 166 mm | 14.4 px · 158 mm |
bldg guarantees topology, not geometric accuracy. Its corners are exactly as far from the truth as the coordinates that were written, and lint does not bring them closer, while the repair of absolute coordinates, by averaging the copies of each corner, ends up slightly closer. What bldg removes is the topological error: a corner is one node and cannot open, a wall joins the corners it names and an opening is on its wall, so nothing has to be re-joined by distance (what lint and the compiler still lose is counted above). Geometric accuracy comes from the extraction model plus constraints (printed dimensions, axes, modules), and those constraints need the correct topology to act on.
3D: segmented extrusion vs CSG
Both pipelines build the same 2,095 walls (including 33 railings) with 996 openings, and both produce triangle meshes. bldg cuts each wall into solid segments at its junctions and openings and extrudes them, with the sills and headers (36,116 vertices, 54,448 triangles); the CSG reference, IfcOpenShell create_shape, extrudes each IfcWall / IfcRailing outline, subtracts the IfcOpeningElement voids and tessellates (0 failures). The two meshes are compared wall by wall:
Agreement, every wall: 2,050 of 2,094 walls have the same mesh volume in both pipelines to a relative 1e-6 and 2,086 to 1e-4 (not compared: 1 of the 2,095 walls, 1 inside a wall junction with no solid of its own); 2,059 have the same volume-weighted centroid to 1e-6 of the wall length (largest difference 1.9e-02). On the 1,186 walls without openings the largest relative volume difference is 4.1e-02.
Walls that differ by more than 1e-4:
- public plan 1 w25: 1.5% volume, centroid 4.7e-03 of the wall length; the wall has no openings, so the solids differ before any boolean; the unvoided IFC body already differs from the outline by 1.5%; the compiler warned: wall w25 segment 0: self-intersecting outline.
- public plan 1 w28: 0.34% volume, centroid 1.3e-03 of the wall length; the wall has no openings, so the solids differ before any boolean; the unvoided IFC body already differs from the outline by 0.34%; the compiler warned: wall w28 segment 0: self-intersecting outline.
- public plan 2 w18: 0.026% volume, centroid 1.6e-04 of the wall length; the wall has no openings, so the solids differ before any boolean; the unvoided IFC body already differs from the outline by 0.026%; the compiler warned: wall w18 segment 0: self-intersecting outline.
- public plan 3 w41: 1.7% volume, centroid 5.8e-03 of the wall length; the wall has no openings, so the solids differ before any boolean; the unvoided IFC body already differs from the outline by 1.7%; the compiler warned: wall w41 segment 0: self-intersecting outline.
- public plan 3 w44: 1.7% volume, centroid 5.8e-03 of the wall length; the wall has no openings, so the solids differ before any boolean; the unvoided IFC body already differs from the outline by 1.7%; the compiler warned: wall w44 segment 0: self-intersecting outline.
- public plan 4 w55: 0.91% volume, centroid 5.7e-03 of the wall length; the wall has no openings, so the solids differ before any boolean; the unvoided IFC body already differs from the outline by 0.91%; the compiler warned: wall w55 segment 0: self-intersecting outline.
- public plan 5 w107: 4.1% volume, centroid 1.9e-02 of the wall length; the wall has no openings, so the solids differ before any boolean; the unvoided IFC body already differs from the outline by 4.1%; the compiler warned: wall w107 segment 0: self-intersecting outline.
- public plan 6 w1: 0.29% volume, centroid 2.6e-03 of the wall length; the wall has no openings, so the solids differ before any boolean; the unvoided IFC body already differs from the outline by 0.29%; the compiler warned: wall w1 segment 0: self-intersecting outline.
IfcOpenShell 0.9.0 for the CSG reference.
The same plan in each format (synthetic_19 ground truth, unfurnished: 26 walls, 21 openings, 8 rooms, 0 components; tokens, o200k_base)
This is the dataset's ground truth of synthetic_19 (the canonical plan of the sections above), not the furnished editor example of the same name: that one, with its furniture, is the synthetic_19 row of the editor examples table, so its ratios differ. The SVG and DXF files are drawn at fine detail (the full drawing, as in the equal-depth section above); the files carry their own semantics and headers, so this column is file size, not the equal-drawing comparison.
| format | tokens | × bldg |
|---|---|---|
| SVG (compiled drawing, fine) | 20.7k | 9.0× |
| DXF (R2010, AIA layers, fine) | 105.3k | 45.8× |
| IFC4 (STEP) | 120.8k | 52.5× |
| GeoJSON | 23.2k | 10.1× |
| floorplan/v0 | 4.2k | 1.8× |
| absolute-coordinate JSON | 3.1k | 1.4× |
| bldg stored document | 2.5k | 1.1× |
| bldg strict | 2.7k | 1.2× |
| bldg without types | 2.3k | 1.0× |
| bldg extract, plan frame (mm) | 2.4k | 1.0× |
| bldg (extract, image frame) | 2.3k | 1.0× |
| bldg short keys | 2.3k | 1.0× |