Skip to content

feat(rooms): a room whose stored solid the walk does not close takes the outline its solid's faces make, labelled stored_solid_faces or stored_solid_approximate (#90) (RE-316) - #1292

Merged
DrunkOnJava merged 38 commits into
mainfrom
feat/rooms-unclosed-solids
Nov 3, 2026

Conversation

@DrunkOnJava

@DrunkOnJava DrunkOnJava commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

A room whose stored solid RE-101's exact walk does not close on its record box is read again from the faces its solid lists, up to the solid's own end, and labelled apart in SpaceOutlineSource: stored_solid_faces where the faces' edges close as the walk reads them, stored_solid_approximate where a column overlaps a wall (its area depends on whether Revit's export takes the column off) or the outline falls up to 0.02 ft short of the box (Griffin, 2026-11-03: close them, labelled). stored_solid stays the exact walk's. On the BIM4LCA office as built all 13 rooms that kept their box take an outline (9 stored_solid_faces, 4 stored_solid_approximate); at building permit 11 of 12 do (all stored_solid_approximate).

Motivation

Rooms first (#1260): a room written as its record box where it is not a rectangle is the failure that matters. RE-313 left 12 rooms per office file on their box with the cause not found; RE-316 finds it (a record longer than 0x200 bytes between two faces, walls on lines up to 0.0015 ft apart, a column overlapping a wall, a solid longer than the 64 KB window).

Round 3 of the review found room 1671995 at 323% over Revit's area (350% at permit) and asked for a guard. The 323% is the scorer's: it compares the faces of Revit's mesh that point up, and Revit's ceiling over that room is mostly not flat (1218.33 ft² in 9 patches). Revit's floor under it is 5177.37 ft² and ours 5154.12, 0.45% under; the same metric reads RE-313's 66% room (2611271) as 66% off where its floor agrees to 0.1%. See RE-316 section 3.

Changes

  • src/research/partition_room_boundaries.rs: the span reads its edges two ways. As the walk reads them (stored_solid_faces: one outer loop, nested loops, no crossing, and a span bounded by the solid's list of objects and a gap of 1,024 bytes) or as the boundary of the region they wind around (stored_solid_approximate). Column-involved rooms are no longer labelled as the exact reading.
  • tools/re/space_overlaps_vs_ifc.py (new scorer, registered in Measure's IFC scorers; its count lines are kept for held-out models by tools/ci/scrub-holdout-results.sh): each space's upward-face, floor and every-face plan areas against Revit's, and how much of a room lies inside the walls and rooms around it.
  • Docs and labels: docs/rooms.md, docs/export-diagnostics.md (+ schema), docs/status.md, docs/supported-profile.md, docs/evidence.md, changelog.d/changed/1260-room-solids-resolved.md, reports/element-framing/RE-316-room-solids-resolved.md; the closure probe says why each resolved room's edges do or do not close.

Testing

Unit tests are banned in this repository: none added. End to end on Measure (commands in RE-316 section 6).

  • tools/check-local.sh passes: not run on this Mac (no local compute); CI's fmt, clippy, doc, MSRV, real-file and IfcOpenShell jobs ran green on this branch
  • Verified end to end on real Revit files, against Revit's own IFC4 exports: the BIM4LCA office (CC BY-SA 4.0) as built and at permit with space_profiles_vs_ifc and space_overlaps_vs_ifc, room by room in RE-316 section 3; Core Interior and RE1 Architecture unchanged (116 of 116, 11 of 11); a held-out Revit 2021 project, counts only
  • Unfiltered Measure against main: 41728355227 (-f base=main -f nordic=true -f r2025=true -f samples=true -f holdout=true, head a00e705, base 7fd339e): scorer output differs on five files, the exports' hashes on the office, the held-out project and one rme room per file; nothing else
  • N/A: not a performance change

Rooms per outline source (export diagnostics):

stored_solid stored_solid_faces stored_solid_approximate room record_box
Office as built 221 9 4 0
Office at permit 222 0 11 1 (3095831)

Area within 0.1% of Revit's export, the scorer's upward faces (before to after): office as built 221 to 230, at permit 222 to 232, Warmond (held out, counts only) 174 to 179 of 179; Core Interior and RE1 unchanged. By Revit's floor: stored_solid 221 of 221 and 222 of 222; stored_solid_faces 9 of 9 (and 6 of 6 on Warmond); stored_solid_approximate 0 of 4 as built (0.45% to 0.68% under) and 10 of 11 at permit (1671995, 0.45% under).

The review's four guards (wall overlap, shell closure, volume, containment) were measured on every room the span reads. None rejects 1671995 and keeps the rooms that agree: it covers 0.03% of itself with walls (the agreeing rooms 0.10% to 7.59%, the walk's rooms up to 23%) and 0.00% of other rooms (0.00% on every room); a closed shell and the volume need the floor and ceiling faces, which face_at reads on 3 of the 13 rooms as built; containment holds on all. No guard is applied, no room is refused for a guard's reason. The residual (a closed loop of another element's faces inside the box and within 1,024 bytes of the last listed face) is not closed by these either; a foreign face that crosses a wall makes the room stored_solid_approximate.

Where this differs from the round-3 instruction: the fallback (withhold the faces reading) would turn 1671995 and the 9 exact rooms back into boxes, which are 1.0% over Revit's floor for 1671995 and 3% to 67% over for the nine. Instead the exact label is narrowed to rooms whose edges close as the walk reads them, and every column-involved room is stored_solid_approximate with its measured error. Withholding it is a small change to resolved_outline_of_solids if the coordinator wants it.

Audit-honesty checklist

  • README / docs reflect the current state after this PR
  • A changelog fragment: changelog.d/changed/1260-room-solids-resolved.md
  • Limitations resolved and introduced are in RE-316 sections 3 to 5 and docs/rooms.md: the approximate rooms, the scorer's upward-face limit, the unproven foreign-face bound, the six rac_advanced rooms with round columns, 49 of 50 rme rooms, Snowdon Towers' seven and room 3095831 keep their box
  • Commit messages describe what and why
  • No emojis
  • No private paths, personal email addresses or internal jargon in committed files
  • A reproducible probe or scorer under probes/examples/ and tools/re/, and a dated report

Legal and contribution hygiene

  • Apache-2.0
  • No Autodesk proprietary source, decompiled internals or NDA'd SDK content: byte observations on files Measure fetches
  • No fixtures

Breaking changes

None to the API outside rvt::research. SpaceOutlineSource gains two values; an importer that matched stored_solid exactly now sees stored_solid_faces and stored_solid_approximate on rooms that were record_box before.

Screenshots or output samples

IFC output changes on 13 + 11 office rooms, the held-out project's 6 and one rme room per file: their IfcSpace body is the outline where it was the box. Room by room in RE-316 section 3.

Related issues

Refs #90 (rooms still drawn as their box: this resolves 13 office rooms as built and 12 at permit, and lists those that keep it: office 3095831 at permit, six rac_advanced rooms with round columns, 49 of 50 rme rooms, Snowdon's seven). #1260 (closed by #1288) is the rooms-first report that started this.

…span and anchor and says which closes (#1260) (RE-316)
…to the next solid's tag give the room's outline as the boundary of the region its directed plan edges wind around (#1260) (RE-316)
…th the walk on the rooms RE-101 kept (#1260) (RE-316)
…resolved, and a face outside the room's box rules the solid out (#1260) (RE-316)
…s are resolved, and a failed resolution says where the pieces do not link (#1260) (RE-316)
…all 0.02 ft short of the record box (#1260) (RE-316)
…arries no prism marker, its volume being unmeasured (#1260) (RE-316)
…lving them gives against Revit's export, and what keeps its box (#1260) (RE-316)
…lved, and a solid with a round column is left to its box, since Revit's export does not always take either off (#1260) (RE-316)
… Revit's area and the rooms that keep their box (#1260) (RE-316)
… held-out Revit 2021 project's six rooms (#1260) (RE-316)
…m it does not close is resolved apart with its own profile source, and the work of a resolution is bounded (#1260) (RE-316)
…nts and at a gap of 1024 bytes, and the work of a resolution is bounded and measured on crafted solids (#1260) (RE-316)
…d's list of objects, resolved rooms are labelled stored_solid_faces and stored_solid_approximate and counted, and the measured misses are stated (#1260) (RE-316)
… inside the walls and rooms around it, to tell a resolved outline Revit disagrees with (#1260) (RE-316)
…and every-face plan area, ours and Revit's, to test whether Revit's top surface is the room's plan (#1260) (RE-316)
…re one simple loop; a column subtracted or a shortfall makes it stored_solid_approximate (#1260) (RE-316)
… walk reads them, and stored_solid_approximate where a column overlaps a wall or the outline falls short (#1260) (RE-316)
…how far Revit's floor area is from them, and the scrub keeps the overlap scorer's counts (#1260) (RE-316)
…s do or do not close as the walk reads them (#1260) (RE-316)
…r, the scorer's upward faces read it and RE-313's 66% room as a fraction of themselves, the four guards were measured and none separates it, and the resolved outlines are labelled by how their edges close (#1260) (RE-316)
…ther edge, named everywhere; the span's looser join stated; the room schedule note and the held-out scrub of every-face lines corrected (#90) (RE-316)
@DrunkOnJava DrunkOnJava changed the title feat(rooms): a room whose stored solid the walk did not close takes the outline of the faces up to the next solid, resolved to a boundary (#1260) (RE-316) feat(rooms): a room whose stored solid the walk does not close takes the outline its solid's faces make, labelled stored_solid_faces or stored_solid_approximate (#90) (RE-316) Nov 3, 2026
@DrunkOnJava
DrunkOnJava marked this pull request as ready for review November 3, 2026 23:17
@DrunkOnJava
DrunkOnJava enabled auto-merge (squash) November 3, 2026 23:17
@DrunkOnJava
DrunkOnJava merged commit 4ad2802 into main Nov 3, 2026
32 checks passed
@DrunkOnJava
DrunkOnJava deleted the feat/rooms-unclosed-solids branch November 3, 2026 23:25
DrunkOnJava added a commit that referenced this pull request Nov 4, 2026
…, from its Level to the next storey (#763) (RE-317) (#1298)

## Summary

An Area's `IfcSpace` gets the region that the lines of its area scheme
on its Level enclose around its point, its holes the loops directly
inside, extruded from its Level up to the Level above it, as Revit's
export draws it. On the BIM4LCA office all 30 Areas, as built and at
building permit, equal Revit's in area (within 0.1%), outline (within
0.01 ft) and base and top (within 0.01 ft); no other export moved
(RE-317). An Area whose boundary is not proven keeps no body and says
why.

## Motivation

Rooms and spaces are the first workflow made dependable (rooms first,
#1260). The office's 30 Areas were written as `IfcSpace` with number,
name and storey and no body (RE-191), the last 30 of Revit's 308 spaces
rvt-rs did not draw. Refs #763.

## Changes

- `probes/examples/probe_re317_area_boundaries.rs`,
`tools/re/area_boundaries_vs_ifc.py`: where an Area's boundary lives
(its scheme's `OST_AreaSchemeLines` elements, whose boxes are the lines;
a point in its data object at byte 107) and how it maps to Revit's
profile; each Level's Building Story setting.
- `src/research/partition_area_boundaries.rs` (new): the planar
arrangement of straight lines along the plan axes (splits at T joints
and crossings, free ends left out, nested loops), capped at 2048 lines
and 200,000 points, ring polygons built once, free ends pruned by a
worklist (linear); `face_at`, the region around a point, fail closed.
- `src/research/partition_area_records.rs`: the Area's point, scheme,
named lines, Level and the Level above. The fail-closed contract, after
review:
- every declared area boundary line of the file must have been returned
by the scan or recorded as a stray; one that was not, or whose height is
not a number, may belong to any group, so no Area of the file is drawn;
  - a line whose copies disagree taints both copies' Levels;
- the Area's Level and the Level above are both read as building
stories, with no other Level between them or at either elevation, or the
Area has no body (the Level's story above is not read);
- an Area whose region another placed Area of its scheme and Level
shares gets none, whatever stops that Area from being drawn (every Area
with a point and a scheme and Level gets its region first);
- the lines an Area's record names are checked against the declared
census, so a named line that is conflicted, unkeyed or in a non-primary
option refuses it (`named_lines_not_in_scheme_and_level`);
- a line in a non-primary design option makes its group unreadable
instead of being skipped (no such line exists in the office or the 2024
samples, so Revit's handling is not measured);
  - line ids below 1000 are not read as an Area's named lines;
- the reason for each refusal is
`RvtElementRecordGeometry.AreaBodyRefusal` and is counted in
`exported.area_body_refusals` (schema and docs updated).
- `src/ifc/export_content.rs`: the body, `SpaceOutlineSource`
`area_boundary`, `BodySource` / `ProfileSource`
`partition_area_boundary_lines`, `BodyHeightSource`
`area_level_to_next_storey`; an Area is left out of the spaces that hold
a room's furniture; the room schedule gives an Area its area.
- `docs/export-diagnostics.md` and the schema: `area_body_refusals` is
empty when every Area is drawn or the export has no geometry;
`status.md` names it; `evidence.md` wording fixed; the report has the
reproduce command for the samples probe.
- `tools/re/space_profiles_vs_ifc.py` counts Areas as their own kind and
scores the lowest and highest vertex of each body against Revit's
(`plan_profiles_vs_ifc.py` can return the heights).
- Docs: `docs/rooms.md` (what is not measured now names the top storey,
non-story Levels, the story above, Areas bounded partly by walls and the
other releases), `export-diagnostics.md`, `status.md`, `evidence.md`,
`supported-profile.md`, `support-matrix.json`, `user-guide.md`,
`format-reconnaissance.md`,
`reports/element-framing/RE-317-area-boundaries.md`, changelog fragment.

## Testing

Unit tests are banned: end to end on real files only. No local build;
GitHub's runners did all of it.

- [x] Verified end to end on real Revit files, measured against Revit's
own export, with the scorer and probe that reproduce it (commands with
SHAs in the report). The branch has `main` merged in (37816e3: #1286,
#1292, the door widths), so the numbers below are against `main` as it
now is:
- Measure unfiltered on the merged head, `-f base=main -f nordic=true -f
r2025=true -f samples=true -f holdout=true`, every scorer, run
41740909229. `space_profiles_vs_ifc` on the BIM4LCA office as built and
at permit: spaces without a body 30 -> 0; "rvt-rs area area boundary:
30, area within 0.1% 30, outline within 0.01 ft 30, both 30, boundary
within 0.01 ft 30" and its height line "30, lowest and highest vertex
within 0.01 ft 30" on each file; spaces in both 276 -> 306 and 278 ->
308; area within 0.1% 230 -> 260 and 232 -> 262, outline 220 -> 250 and
225 -> 255; every space's base and top within 0.01 ft (306 of 306 and
308 of 308); the room and family-instance lines are the ones `main`
prints.
- Of 111 models and samples (every release 2015 to 2027), the IFC hash
moved on four: the two office files and the two 2024 samples with Areas.
The compare job's scorer diff is 68 lines: the space lines, and in
`schema.txt` the 30 (office) and 3 (`rac_advanced`) new swept solids
with a Position. Property sets, quantities, plan profiles, class sets,
room names and storeys, and the WHERE rules print the same. No held-out
model is in the scorer diff: every held-out `space profiles:` count line
is identical to `main`'s (BIM Whale 2021: 0 of Revit's 8 written, as
before).
- The office's Levels are all building stories (probe, run 41721498984);
the probe's point check: the point lies in Revit's profile on 30 of 30
on both files (run 41713925390).
- Autodesk's samples (no Revit export to score): `rac_advanced` 3 of 25
Areas gain a body, 18 are refused as `boundary_lines_not_readable` (a
slanted line or arc in their group) and 4 as `point_in_no_single_region`
(their point lies in no loop of the lines; possibly Areas bounded partly
by walls, the main gap, not examined further); `rst_basic`'s one Area is
refused as `next_storey_not_proven`; every other sample is
byte-identical.
- [x] CI: fmt, clippy, rustdoc, MSRV 1.87, i686, IfcOpenShell validates
exports (Core Interior 116 of 116, RE1 11 of 11), Corpus health, PII
guard on the final head; the merge resolved nine conflicts keeping both
sides (main's room outline sources and counters beside the Area rows,
refusals and `area_body_refusals`; schedule rooms take their area per
main's rule, Areas per this change's). `tools/check-local.sh` was not
run: no local builds here.
- [x] IFC exporter touched; the changed observations: none of the
witness reference models changed (Core Interior, RE1 byte-identical), so
no witness refresh.

## Audit-honesty checklist

- [x] README / docs reflect the state after this PR
- [x] Changelog fragment under `changelog.d/added/`
- [x] The limitations are documented in the PR body, in `docs/rooms.md`
"What is not measured" and in the report: the top storey, Levels that
are not building stories, the story above (never read), Areas bounded
partly by walls, boundary lines that are not along the plan axes, the
Area's base offset, releases other than 2024, and
`Qto_SpaceBaseQuantities.Height`, which Revit writes on an Area and
rvt-rs does not yet
- [x] Commit messages describe what and why; no emojis; no private paths
- [x] Reverse-engineering finding: probe under `probes/examples/`, the
dated RE-317 report and the reconnaissance addendum

## Legal and contribution hygiene

- [x] Apache-2.0; no Autodesk proprietary source (revit-ifc read for
what Revit writes, `SpatialElementExporter.GetScaledHeight` and
`LevelUtil.CalculateDistanceToNextLevel`, cited and not copied); no
fixtures added.

## Breaking changes

None to the stable API.
`partition_area_records::areas_from_partition_chain_records` (behind
`unstable-research`) takes the recovered Levels as a fourth argument;
`ExportedModelDiagnostics` gains `area_body_refusals` (serde default).

## Screenshots or output samples

The Measure counts above. An Area now reads `SpaceOutlineSource`
`area_boundary`, `BodySource` `partition_area_boundary_lines`,
`ProfileVertexCount`, `ProfileVoidCount` and `BodyHeightSource`
`area_level_to_next_storey`; an Area without a body reads
`AreaBodyRefusal`.

## Related issues

Refs #1194 (its Areas item: Revit 2024 Areas now get a body; Areas on
other releases, slanted and arc boundary lines, and Story Above stay
open), #1260 (rooms first). #763 (closed) was the earlier family-space
work on the same office.
@DrunkOnJava

Copy link
Copy Markdown
Owner Author

Automated Claude Code review (the maintainer's review agent; it read the code, the scorers and the Measure runs, over three rounds). Round 1: the PR body held another PR's text, a solid's faces had no bound and no cap against hostile input, and the column-overlap slack had no label of its own; fixed. Round 2: no blocking item after a check that office room 1671995, which had read 323% over Revit's by its upward faces, is 0.45% under Revit's floor area (Revit's ceiling over it is not flat); the labels were narrowed so only rooms whose edges close as the walk reads them are stored_solid_faces. Round 3: no blocking items; the third cause of an approximate outline, the looser join, the room schedule note and a held-out scrub pattern were fixed in e55ff36.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant