Skip to content

fix(elements): Revit 2008 to 2023 frames with no ElementId of their own take the id of the declared record around them, and 2016 to 2023 leave out the elements outside the export phase and in a non-primary design option (#1261) (RE-315) - #1286

Merged
DrunkOnJava merged 21 commits into
mainfrom
fix/1261-32bit-second-prologue
Nov 3, 2026

Conversation

@DrunkOnJava

@DrunkOnJava DrunkOnJava commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

Revit 2008 to 2023 element frames that carry no ElementId of their own (the second prologue, RE-30) now take the id of the declared partition record around them, as 2024 does (RE-35), and so do Level frames that are a Level with a name block. Measured on one Revit 2018 project; no other release of that layout is measured for it. Revit 2018's ALD26 Before goes from 0 elements to 1,136 of Revit's 1,321, 1,127 of them in Revit's class or IFC4's (9 curtain walls come out as walls), none written that Revit's export lacks, and 7 storeys. On Revit 2016 to 2023 the export phase and non-primary design options are applied now, as 2024 does, which removed the 198 extra elements the held-out ALD26 Project wrote.

Motivation

#1261: a Revit 2018 project exported no building element against Revit's own 1,394, because most of its frames do not open with a declared u32 ElementId, and the 32-bit reader only read frames that do.

Changes

  • src/research/partition_element_records_2023.rs: record_spans walks a partition's records (u32 id, u32 size, ElementHeader tag, count, body, 12-byte trailer echoing the size) on the tag and the echo alone, where the 2024 flag-word test stops at the first record with another flag word, and resumes at the next record that validates (one memmem Finder). scan_records gives a placed-instance frame with no declared id the id of the declared record around it when the frame's reference list names that id. A curtain panel frame whose first word is a count gets none (96 of the 102 on the file are shells of panels Revit does not write). EXPORT_FILTER_RELEASES (2016 to 2023) gates the filters below.
  • src/research/partition_level_records.rs: a standalone placed OST_Levels frame with no declared id takes its enclosing record's id. A Level frame's reference list is empty and its name block lies elsewhere in the stream, so the guard is its class word being the schema's Level tag and a name and elevation block of that id in the stream (a stray Level-category frame cannot add an id with no name and drop the storeys). A frame that opens with its id is read as before.
  • src/research/partition_schema_mvp/records_2023.rs, partition_design_options.rs, native_phases.rs: on 2016 to 2023 a selected instance that PhaseFilter::excludes or DesignOptions::excludes is dropped (rooms never by phase); design options are read with u32 ids (set record, name entry, the u32 26 bytes before a frame's marker); the diagnostics count both as on 2024. 2008 to 2015 read no set and keep every element.
  • src/ifc/mod.rs: the export warning says per release what is applied (the export phase and design options on 2016 to 2023, neither on 2008 to 2015) instead of that design options are not read for every 2008 to 2023 export. partition_design_options.rs searches a file's option sets for 64 at most (OPTION_SETS_MAX): the rest stay unresolved and keep their elements, as a set with no name entry does on 2024; the most any file here has is 2.
  • probes/examples/probe_re315_32bit_record_chain.rs, reports/element-framing/RE-315-32bit-second-prologue-records.md; tools/re/instances_vs_ifc_tags.py accepts the --list Measure passes a probe scorer; docs, support matrix and a changelog fragment.

Testing

Unit tests are banned in this repository; the evidence is Measure on real files.

  • CI at a3d1cd2a: CI passed and 26 other checks pass; the viewer build (not required) was still pending at the last poll
  • Verified end to end on real Revit files, against Revit's own export. Measured head 01b5af37 (the code of the figures below; the later commits change the export warning's text, cap the option sets searched, merge main and change documents, and the final-head runs repeat the byte identity):
    • ALD26 Before (Revit 2018, CC BY 4.0), counts run 41707934595 and probe run 41707939060: main writes 0 elements; the branch writes 1,209 building elements and spaces against Revit's 1,394. 1,136 of Revit's 1,321 elements are written and paired by Tag, 1,127 of them in Revit's class or IFC4's for its IFC2X3 one; 9 IfcCurtainWall are written as IfcWall. None written that Revit's export lacks; 7 storeys, as many as Revit's, and 1,073 of 1,136 elements on Revit's storey. Missing 185: 178 conduits, conduit fittings and gutters (found and attributed to Revit's exact Tags by the probe, not written: categories -2008132, -2008128, -2001391 are not in RECOVERED_CATEGORIES), 4 panel walls, 3 assemblies. Without the Level change (run 41707946855, commit 9ff5e950) 687 of the 1,136 are on no storey.
    • Level guard: the schema's Level tag and the class word of the Level frames agree on Before (2130: 7 standalone, 16 container members; 2131 the type symbol) and on the standalone Level frames of rst_basic_sample_project 2018 to 2023 (2130, 2164, 2198, 2199, 2299, 2377; 54 of 54); all 7 of Before's survive the check.
    • Held-out ALD26 Project, counts only (same run 41707934595): main writes 0; the first reading of the branch, before the filters (run 41713761592, commit 8fc583a1), wrote 1,534 elements and spaces, 198 of them elements Revit's export lacks (95 pipe segments, 90 pipe fittings, 8 proxies, 2 roofs, 2 windows, 1 wall); with the filters it writes 1,336 (1,263 elements and 73 spaces) against Revit's 1,520, no element Revit's export lacks, 1,255 of its 1,263 elements in Revit's class or IFC4's (8 curtain walls as walls), 1,181 on Revit's storey. The counts cannot tell which filter removed the 198.
    • Evidence for the filters. Phase: only Project's counts; Before and all 33 Autodesk samples of 2015 to 2023 have no element outside their last phase. Design options: eight saves of one model (rst_basic_sample_project, 2016 to 2023), 1 set and 4 frames in a non-primary option each, a Mass Revit does not export, so no export effect is observed; the exported IFC of all eight is byte for byte main's and only the diagnostics change (a skipped item with 1 Mass, the Mass leaves production_class_counts, production_walker_elements falls by 1).
    • Frigento, Gdynia, Warmond and the held-out BIM Whale AdvancedProject: IFC byte for byte main's, BIM Whale's counts lines main's. Frigento (22) and Gdynia (3) still write elements Revit's export lacks, from another cause.
    • Byte identity, run 41707942875 (-f samples=true -f nordic=true -f r2025=true): 92 models, 0 differ from main's IFC, the 33 Autodesk samples of 2015 to 2023 among them; the only diagnostics that change are the eight rst_basic saves of 2016 to 2023
    • Final head 68ae7904 (the merge of main 53e06c02 into the code): unfiltered held-out run 41722201615 (-f holdout=true -f base=main, counts only for the held-out): the IFC of 23 of 25 models is main's; the two that move are the two Vitoria saves, and the counts.txt of the other 17 held-out models, modelo_bim (2023) among them, is main's line for line; identity run 41722205132: 92 models, 0 differ from main's IFC (the diagnostics change on the 33 samples of 2015 to 2023, the warning's text, and the skipped items on the eight rst_basic saves).
    • Reproduce: the commands in RE-315 §8.
  • If this touches the IFC exporter: witness verdicts replay: no Core Interior export moved (Corpus health and the IfcOpenShell job pass)
  • If this is a performance change: N/A

Audit-honesty checklist

  • README / docs reflect the current state after this PR: docs/evidence.md, docs/supported-profile.md, docs/support-matrix.json (still experimental), docs/reference-models.md, docs/export-diagnostics.md, docs/status.md, docs/format.md, docs/rooms.md, README.md, ROADMAP.md and W2 of docs/release-1.0-criteria.md (two projects now have a same-release export, 2015 and 2018, and every statement that said one now says two)
  • Changelog fragment changelog.d/fixed/1261-32bit-second-prologue.md
  • Limitations stated here, in the report (§9, §10) and in the code: only Revit 2018's ALD26 Before is measured against Revit's export for this kind of frame; conduits, conduit fittings and gutters not exported; panel shells and 4 panel walls; 3 assemblies; 9 curtain walls written as walls; the phase filter's only evidence is the held-out counts; design options are measured on Autodesk's rst_basic samples only, with no Revit export of them; 2008 to 2015 apply neither filter
  • Conventional Commits, no emojis, no private paths
  • Reverse-engineering finding: probe under probes/examples/ and report RE-315

Legal and contribution hygiene

  • Apache-2.0. Clean-room: only public byte observations of a CC BY 4.0 model, Autodesk's published sample projects and Revit's own IFC exports; no Autodesk SDK, disassembly or NDA material.

…wn take the id of the declared record around them (#1261) (RE-315)

A 32-bit partition is runs of records (u32 id, u32 size, the ElementHeader tag, a count, the body and a 12-byte trailer that echoes the size) with stretches between them that hold none. The 2024 walk's flag test stops at the first record with another flag word. record_spans walks them on the tag and the echo alone and resumes at the next record that validates; scan_records gives a placed-instance frame with no declared id of its own the id of the declared record around it when the frame's reference list names that id.
…s own takes the id of the declared record around it (#1261) (RE-315)

All 7 Levels of Revit 2018's ALD26 Before are standalone placed frames that do not open with their declared id, so the storey set was never recovered and 687 of its 1,136 elements sat on no storey. A Level's name and elevation block carries the Level's own id, which the join on the enclosing record's id is checked against. The probe also prints the Level counts, and the tag scorer accepts the --list Measure passes it.
…on, declared or not, and the run they form (#1261) (RE-315)
…it's 1,321 elements and its 7 storeys (#1261) (RE-315)
…2025 run export as main does but one roof that main's #1276 moved (#1261) (RE-315)
…nd §9 names the phase hypothesis for ALD26 Project's extra elements (#1261) (RE-315)
…r in a non-primary design option are left out, as on 2024 (#1261) (RE-315)

The 32-bit record path never applied the phase filter that RE-281 reads for 2015 to 2023, nor design options, which 2024 applies through without_non_primary_options. recover_2023_records now drops a selected instance that PhaseFilter::excludes or DesignOptions::excludes, and the diagnostics count them as on 2024. Design options are read on 32-bit as on 2024 with u32 ids: the set record, its name entry and the u32 26 bytes before a frame's marker. The probe prints the phase filter and the options.
DrunkOnJava added a commit that referenced this pull request Nov 3, 2026
… the 2018 to 2021 pairs (#1260) (#1261) (#1293)

## Summary

`docs/rooms.md`'s held-out rows, re-measured on `main` now that #1281
(the space scorer) and #1288 (2026 and 2027 rooms from their stored
solid) have both landed: Measure
[41680717786](https://github.com/DrunkOnJava/rvt-rs/actions/runs/41680717786),
`main` at 21cb2ef, held-out models, counts only.

## Changes

- **2026 and 2027 row:** stored solid, as on 2025. The 16 held-out
rectangles are 16 of 16 in area, boundary and outline.
- **Held-out 2026 house:** area within 0.1% on 10 of 12 (was 7 as
boxes). Boundary and outline 0 of 12.
- **New rows for the 2021 pairs held out since #1284:**
  - Gdynia: 18 of 19 spaces paired; area 18, boundary 0.
  - Warmond: 179 paired; area, boundary and outline 174.
- **Spaces written for none of Revit's:** Vitoria 2018 (73), until
#1286; BIM Whale 2021 (8), whose spaces are Areas.
- **Removed:** "2021 projects decode with no rooms". The 2021 pairs read
theirs, and BIM Whale has no rooms.

## Testing

Docs only. Every figure is a count line of the cited run.
DrunkOnJava added a commit that referenced this pull request Nov 3, 2026
…ement (#408) (#1294)

## Summary

W5 now states how a held-out pair counts, as Griffin decided on
2026-11-03 ("first measurement decides").

A pair counts for W5 only if its first measurement, made before any fix
is made for it, meets W4, W6 and W7. A pair whose first measurement does
not is used up for W5. It stays in Measure as a held-out regression
check: counts only, never developed on, and its counts may be quoted in
reports.

## Motivation

W5 counted a pair only if it was "used in no RE report". RE-313, RE-314
and RE-315 quote held-out counts lines, and RE-315's extras on Vitoria's
`ALD26 Project` point at #1286's next fix. Read strictly, `ALD26
Project` had already failed at its first measurement (0 of 1,520
elements), so the old wording left it unclear what a failed pair is for.

## Changes

`docs/release-1.0-criteria.md`, the W5 row:
- the requirement states the rule;
- the holdout pack's first measurements were not recorded against W4, W6
and W7;
- all six 2018 to 2021 pairs fail W4 at their first measurement (run
41658225355):
  - both Vitoria saves miss every element;
  - Gdynia misses 4, and BIM Whale 16, its 8 Areas among them;
- Frigento writes 22 elements and Warmond 6 spaces that Revit's export
leaves out.

So they are used up for W5, and every release needs fresh pairs.

## Testing

Docs only. Every figure is a count line of run 41658225355: the
class-set tables and the GlobalId line for Frigento's 22.
…vit 2015 to 2023 remove the 198 extra elements of ALD26 Project (#1261) (RE-315)
…only, and a Level frame takes an enclosing id only as a Level with a name block (#1261) (RE-315)

Design options and the export phase are applied, and design option sets read, on the releases measured (2016 to 2023), where 2008 to 2015 keep every element. A Level frame has an empty reference list and its name block lies elsewhere in the stream, so the guard for an enclosing id is its class word, the schema's Level, and a name and elevation block of that id in the same stream; a stray Level-category frame cannot add an id with no name and drop the storeys. record_spans builds its memmem Finder once. The probe prints Level's tag and each Level frame's class word.
…t 1,127 of 1,136, the two projects with a same-release export, and BIM Whale left to its counts (#1261) (RE-315)

The status text that said no 2016 to 2018 export is measured, or that only one 2015 project is, now says two projects; the class claim names the 9 curtain walls written as walls; Project's 1,534 and 1,336 are written, not paired, and the counts cannot tell the phase from the design options; the phase filter's evidence is Project's counts only, and the design options' eight saves of one model with a Mass Revit does not export. The runs cited are new ones, with BIM Whale on the held-out path.
…hile spent, and RE-315's work read its records, with no rule taken from it (#1261)
…he export phase and design options, and a file's option sets are searched for 64 at most (#1261) (RE-315)

The warning said design options were not read for every 2008 to 2023 export; they are read and applied on 2016 to 2023, and neither the phase nor options are on 2008 to 2015, which it now says per release. Each option set costs a search of every partition for its name entry, so past 64 sets, by ElementId, a set is left unresolved: its options are neither primary nor non-primary and its elements are kept, as for a set with no name entry, the direction 2024 already takes. The most any file here has is 2. The release ranges are stated once per rule: the enclosing id on 2008 to 2023, measured on 2018, the filters on 2016 to 2023. Five statements that only one 2015 project has a same-release export name the 2018 project too, and docs/rooms.md gives Vitoria's spent project its 73 spaces.
…final head, 23 of 25 exports and 17 of 18 held-out counts files main's (#1261) (RE-315)
@DrunkOnJava DrunkOnJava changed the title fix(elements): Revit 2015 to 2023 frames with no ElementId of their own take the id of the declared record around them (#1261) (RE-315) fix(elements): Revit 2008 to 2023 frames with no ElementId of their own take the id of the declared record around them, and 2016 to 2023 leave out the elements outside the export phase and in a non-primary design option (#1261) (RE-315) Nov 3, 2026
@DrunkOnJava
DrunkOnJava marked this pull request as ready for review November 3, 2026 21:52
@DrunkOnJava
DrunkOnJava merged commit c28ff05 into main Nov 3, 2026
28 checks passed
@DrunkOnJava
DrunkOnJava deleted the fix/1261-32bit-second-prologue branch November 3, 2026 21:52
DrunkOnJava added a commit that referenced this pull request Nov 3, 2026
…5 and kept as regression checks, and Vitoria's counts are RE-315's (#1261) (#1299)

## Summary

The evidence page's held-out paragraph still said both Revit 2018 saves
of the Vitoria building export no element. It now gives that as the
first measurement, says all six 2018 to 2021 pairs are used up for W5
and stay as counts-only regression checks, and gives `ALD26 Before`'s
and `ALD26 Project`'s counts since RE-315 (#1286).

## Motivation

#1261 and #1286. W5 (docs/release-1.0-criteria.md) has said since #1294
that a pair whose first measurement fails is used up; this page had not
caught up, and #1286 changed the 2018 counts.

## Changes

- `docs/evidence.md`: the held-out sentence on the six 2018 to 2021
projects.

## Testing

N/A: docs-only PR. The figures are those of RE-315 and docs/rooms.md on
main (Measure runs 41707934595 and 41722201615): Before 1,136 of Revit's
1,321 elements and all 73 spaces; Project 1,336 elements and spaces
against Revit's 1,520, none extra; first measurement from run
41658225355.

## Audit-honesty checklist

- [x] README / docs reflect the current state after this PR
- [x] No user-visible change, so no changelog fragment
- [x] No emojis, private paths or internal jargon

## Legal and contribution hygiene

- [x] I agree this work is licensable under Apache-2.0
- [x] No Autodesk proprietary source, decompiled internals, or NDA'd SDK
content

## Breaking changes

None.

## Related issues

Refs #1261, #408.
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 added a commit that referenced this pull request Nov 4, 2026
…ment records (#1261) (RE-319) (#1306)

## Summary

Conduits, conduit fittings and gutters are exported from their element
records, on every release the exporter reads (RE-319). On the one file
with a Revit export that holds any of them, BIM-Speed Vitoria `ALD26
Before` (Revit 2018, IFC2X3), all 178 instances the RE-21 rule selects
are elements of Revit's export by `Tag`, none outside it: 1,136 to 1,314
of its 1,321 elements written. Cable trays and cable tray fittings are
not recovered (no file with a Revit export holds one), and neither are
the conduit centerline categories Revit's export leaves out.

## Motivation

#1261: RE-315 left 178 conduits, conduit fittings and gutters of `ALD26
Before` as "the next category census". This is that census, on every
development file and every release the exporter reads, and the
categories it shows Revit's export holds.

## Changes

- `partition_element_records.rs`: `OST_CONDUITS` (-2008132),
`OST_CONDUIT_FITTING` (-2008128) and `OST_GUTTER` (-2001391), Autodesk's
published `BuiltInCategory` values, in `PRODUCT_RECORD_CATEGORIES` (42)
and `RECOVERED_CATEGORIES` (49).
- `category_map.rs`: conduits as `IfcCableCarrierSegment`, conduit
fittings as `IfcCableCarrierFitting`, gutters as `IfcPipeSegment`, the
entities Revit's own type objects name in that export (102 of 102, 74 of
74, 2 of 2); `PredefinedType` unset.
- `partition_schema_mvp/fields.rs`: `Gutter` placed by elevation
(`ELEVATION_PLACED_CLASSES`).
- `export_content.rs`: no `Constraints` Level parameter for the three
classes (no export measures its name).
- `rvt_ifc.rs`, `viewer/src/main.ts`: display names.
- `probes/examples/probe_re319_category_census.rs`,
`tools/re/category_census_vs_ifc.py`: the census and its scorer.
- `reports/element-framing/RE-319-conduits-conduit-fittings-gutters.md`,
`changelog.d/added/1261-conduits-gutters.md`, `docs/status.md`,
`docs/support-matrix.json`, `docs/supported-profile.md`,
`docs/evidence.md`, `docs/reference-models.md`,
`docs/release-1.0-criteria.md`.

## Testing

Unit tests are banned in this repository: none added. No local build
(the Mac does not compute here); everything ran on GitHub's hosted
runners through Measure.

- [ ] `tools/check-local.sh` passes: not run locally (no builds on this
Mac); CI runs it on this PR
- [x] Verified end to end on real Revit files, with the probe and the
scorers that reproduce it (below)
- [x] If this touches the IFC exporter: Core Interior, RE1 and every
other model but the 15 below are byte for byte main's, so the witness
verdicts are unchanged
- [ ] Performance change: N/A

Measure run 41741114199 (`-f base=main -f holdout=true -f samples=true
-f nordic=true -f r2025=true -f bimcollab=true`, every scorer; main's
side `4ad28023`, head `46da221f`; later commits change documents only):
112 models exported on both sides, 97 byte for byte main's IFC, 15
moved:

| `ALD26 Before` (Revit 2018, spent) | main | head | Revit's export |
|---|---:|---:|---:|
| elements paired by `Tag` | 1,136 | 1,314 | 1,321 |
| written that Revit's export lacks | 0 | 0 | |
| in Revit's class or IFC4's for its IFC2X3 one | 1,127 | 1,305 | |
| elements on Revit's storey | 1,073 | 1,251 | |
| validation, EXPRESS rules, property sets | 0 findings | 0 findings | |
| psets: different, values Revit does not write | 0, 20 | 0, 20 | |

The 7 still missing are 4 curtain-panel walls and 3 assemblies. The 15
models that moved: `ALD26 Before`, the held-out `ALD26 Project` (counts
only: 1,336 to 1,514 of Revit's 1,520 elements and spaces,
`IfcFlowSegment` and `IfcFlowFitting` absent 104 and 74 to 0, no element
Revit lacks, values Revit does not write 18 to 18), Autodesk
`rme_advanced` and `rme_basic` of 2015, 2019 and 2024 to 2027 (19 or 20
conduits and 14 fittings each), and Snowdon Towers Electrical (530 and
322). The samples have no Revit export: counted, not scored. Every other
model is byte for byte main's, among them Core Interior, RE1, the
BIM4LCA files, the 24 R2025 models, BIMcollab and the 17 other held-out
pairs.

Census (run 41726416015, probe `probe_re319_category_census`): the only
file with a Revit export that holds any of the five categories is `ALD26
Before`; RE1 Electrical, Mechanical and Plumbing, Core Interior, the
four BIM4LCA files, the 24 R2025 models and Einhoven hold none, and
every element of those exports is among the instances the probe selects.

Reproduce:

```bash
gh workflow run measure.yml --ref main -f ref=3a81189c6dde0783004c41d3d2e4daf27930e1e8 -f base=none -f holdout=true -f samples=true -f nordic=true -f r2025=true -f bimcollab=true -f models="spent-* core-interior einhoven re1-* nordic-office-* r2025-* bimcollab-*" -f scorers=none -f probe=probe_re319_category_census -f probe_scorer=category_census_vs_ifc
gh workflow run measure.yml --ref main -f ref=46da221fc89f259d7dd535e06e8f49a7ac0286a3 -f base=main -f holdout=true -f samples=true -f nordic=true -f r2025=true -f bimcollab=true
```

## Audit-honesty checklist

- [x] README / docs reflect the current state after this PR: no claim
beyond what is measured (one Revit 2018 file with an export; the samples
counted, not scored; cable trays not exported)
- [x] A user-visible change has its fragment
`changelog.d/added/1261-conduits-gutters.md`
- [x] If a limitation was resolved, the docs say so: RE-315's 185
missing is now 7
- [x] New limitations are in the PR body, the report (§9) and the
support matrix: cable trays and cable tray fittings are not recovered;
conduit and gutter names and `Reference` are not read on Revit 2018
(Name differs, as for pipes); no `Constraints` Level parameter for the
three classes; no `PredefinedType` (Revit's gutter type objects carry
`GUTTER`, 2 of 2; left unset, a decision); bodies are boxes; no conduit
fitting ports
- [x] Commit messages describe what changed and why
- [x] No emojis in code, docs or commit messages
- [x] No private paths, personal email addresses or internal jargon in
committed files
- [x] Reverse-engineering finding: a reproducible probe under
`probes/examples/` (`probe_re319_category_census.rs`) and the dated
report RE-319 are included

## Legal and contribution hygiene

- [x] I agree this work is licensable under Apache-2.0
- [x] This PR contains no Autodesk proprietary source, decompiled
internals, or NDA'd SDK content
- [x] No new test fixtures

Clean-room declaration (CLEANROOM.md): the changes in this PR are based
on public byte observations of Revit files (BIM-Speed's CC BY 4.0
Vitoria pair, MIT-licensed RE1 models, Autodesk's published sample
projects) and of Revit's own IFC exports of them (accepted source 1),
Autodesk's published `BuiltInCategory` constants (source 2, the public
API enumeration), the buildingSMART IFC4 specification (source 4), and
Autodesk's open-source exporter `revit-ifc`, read for what Revit's
export writes and cited by file and class (`IFCCompatibilityType.cs`,
`ElementFilteringUtil.cs`, `FamilyInstanceExporter.cs`, `IFC4Enums.cs`;
source 5). I have not used Autodesk SDK source, ODA SDK source, leaked
internal documentation, NDA material, or decompiled internal Revit
implementation code beyond public symbol exports, and I have not copied
or translated code from revit-ifc.

## Breaking changes

None. `PRODUCT_RECORD_CATEGORIES` and `RECOVERED_CATEGORIES` are
`unstable-research` constants whose array lengths change (39 to 42, 46
to 49).

## Screenshots or output samples

IFC output changed: a conduit is
`IFCCABLECARRIERSEGMENT('...',#5,'Conduit-745533',$,$,#...,#...,'745533',$)`,
a fitting `IFCCABLECARRIERFITTING('...',#5,'M_Codo de tubo -
Aluminio:Est\X2\00E1\X0\ndar:745566',$,'M_Codo de tubo -
Aluminio:Est\X2\00E1\X0\ndar',#...,#...,'745566',$)`, a gutter an
`IFCPIPESEGMENT` with `$` for its `PredefinedType`.

## Related issues

Refs #1261. Follows #1286 (RE-315).
@DrunkOnJava

Copy link
Copy Markdown
Owner Author

Automated Claude Code review (the maintainer's review agent; it read the code, the report and the Measure runs, over two rounds): three blocking items in the first round, fixed in 01b5af3, b036c93 and 2869ecd. The status pages still called the second-prologue ids and design options 2024 and 2025 only; the class claim said every element was in Revit's class where 9 curtain walls are written as IfcWall (1,127 of 1,136); and the report quoted counts from a held-out model beyond its counts lines. The filters were also limited to Revit 2016 to 2023, and a Level frame takes an enclosing id only when its class is the file's own Level class. Two blocking items in the second round, fixed in c04c92d: five pages still said only one 2015 project has its release's own export, and every 2008 to 2023 export warned that design options are not read, which is no longer true on 2016 to 2023.

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