Skip to content

fix(saved-graphics): mesh a cylinder whose saved basis is left-handed, its normal turned as on a plane (#1247) (RE-301) - #1252

Merged
DrunkOnJava merged 4 commits into
mainfrom
fix/1247-left-handed-cylinders
Nov 2, 2026
Merged

DrunkOnJava merged 4 commits into
mainfrom
fix/1247-left-handed-cylinders

Conversation

@DrunkOnJava

@DrunkOnJava DrunkOnJava commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

Saved meshes now read a cylinder whose saved basis is left-handed (m_xVec × m_yVec = −m_zVec). The tessellator rejected such a face, and RE-224's gate then kept the whole element's box. It is the same cylinder: only ∂P/∂u × ∂P/∂v turns inward, so the normals take that sign, as a plane's x × y does. Vertex positions and winding are unchanged (RE-301).

Motivation

#1247 reports that v0.12.0 draws as 12-triangle boxes many elements v0.6.1 drew from saved meshes. It is reproduced on our files (Measure 41537426493: Snowdon Towers electrical 1,420 → 907 bodies, architectural 754 boxes, office 357). Every element lost is RE-224's partial gate. Left-handed cylinders are the largest cause that needs nothing else: 526 elements, 484 blocked by them alone, in the census of Measure 41540910235.

Changes

  • src/research/native_saved_mesh.rs, cylinder_face: the left-handed rejection is gone; the normals take the basis's handedness.
  • reports/element-framing/RE-301-left-handed-cylinders.md and changelog.d/fixed/1247-left-handed-cylinders.md.

Testing

Unit tests are banned in this repository: do not add them. Show the change working end to end on real files instead.

  • tools/check-local.sh: CI.
  • Verified end to end on real Revit files: Measure 41541977595, --saved-meshes, against main.
    • Office as built: 10,781 → 10,881 bodies drawn from saved meshes. Scored against Revit's export, 1,352 → 1,452; exact to 0.001 ft 1,294 → 1,360; within 0.01 ft 1,345 → 1,438.
    • Office at permit: +102.
    • Office doors drawn from saved meshes (the default export draws them too): 48 → 114 scored as built (46 → 114 at permit), all exact.
    • The new bodies outside 0.01 ft are seven kitchen sinks at 0.0106 ft (volume within 1.3%).
    • Core Interior and RE1 Electrical, Mechanical and Plumbing are unchanged; Snowdon Towers architectural gains 172.
    • Schema check unchanged.
  • Measure 41543761135: this change (148f4cd) against main, default flags, unfiltered, with the office, R2025 and held-out models.
    • Every IFC hash differs only because the branch predates the 0.12.1 version bump (witness_version 0.12.0 against 0.12.1 is the only Core Interior change).
    • The scorers move only on the BIM4LCA office: 66 doors as built and 68 at permit go from record box to saved mesh, and all 114 scored doors are exact (mesh_bodies_vs_ifc, saved_meshes_vs_ifc, box_bodies_vs_ifc).
    • Properties, quantities, openings, RE1, R2025 and the held-out counts are unchanged.
  • Size: with --saved-meshes the office as built goes from 331,302 to 447,784 triangles (IFC 70.6 → 75.1 MB) and Snowdon Towers architectural from 648,933 to 974,573 (+12.8 MB). The default export's growth is in the unfiltered run.
  • Review (Sonnet 5.5 xhigh, automated Claude Code review): no blocking findings. Its notes are applied: corrected 0.01 ft and door counts (an awk sum had taken a header line), the size cost above, outward renamed handed, and the reconnaissance addendum. 9ae1285 only renames a variable after the gate's head 148f4cd.

Audit-honesty checklist

  • README / docs reflect the current state after this PR
  • A user-visible change has its entry as a fragment under changelog.d/
  • Commit messages describe what changed and why
  • No emojis in code, docs, or commit messages
  • No private paths, personal email addresses, or internal jargon in committed files
  • Reverse-engineering finding: report with measured counts against Revit's own export

Legal and contribution hygiene

  • Apache-2.0
  • No Autodesk proprietary content
  • No fixtures

Contributor declaration (CLEANROOM.md): based on the saved graphics' own fields read from public files and on Revit's IFC exports of them; no Autodesk SDK source, disassembly or NDA material.

Breaking changes

None.

@DrunkOnJava DrunkOnJava added area:geometry Work in the geometry area. area:ifc Work in the ifc area. type:bug Correctness defect or regression. labels Nov 2, 2026
@DrunkOnJava
DrunkOnJava enabled auto-merge (squash) November 2, 2026 23:39
@DrunkOnJava
DrunkOnJava merged commit ca389bb into main Nov 2, 2026
28 checks passed
@DrunkOnJava
DrunkOnJava deleted the fix/1247-left-handed-cylinders branch November 2, 2026 23:45
DrunkOnJava added a commit that referenced this pull request Nov 3, 2026
… is not written as its opening alone (#1247) (RE-299) (#1249)

## Summary

A door or window is no longer written as its opening alone
(`IfcOpeningElement`) when its type draws geometry in no material of its
own. RE-84 decides "this type only cuts its host" from a byte scan of
the type's value block, and that scan misses a geometry-material map
whose values are all unset or name a material whose name it did not
read. The type's native `m_geomTag2MaterialId` now decides where it
holds entries (RE-299).

## Motivation

#1247: on a Revit 2024 project, `rvt-ifc --large-model` writes 73
`AR_DOR_` doors as `IfcOpeningElement`, the other 361 as `IfcDoor`. The
RE-84 path is unchanged since v0.6.1 (the reporter's v0.6.1 could not
read that file), so this is a standing misread rather than a regression.

## Changes

- `src/research/native_type_materials.rs`: `geometry_maps`, which
`geometry_materials` now wraps, also returns the types whose native map
holds any entry. Same single native pass.
- `src/research/partition_schema_mvp/materials.rs`:
`refine_type_materials` drops an empty scanned list for such a type, so
it reads as "materials not read" rather than "draws no geometry". Its
doors and windows then also get the sizing and naming other doors and
windows get, which the flag gated (RE-215 cut-loop sizes, opening-index
sizes, RE-154's type name). No type material is written for it: what
Revit gives such a door is not measured. One case keeps the flag: a map
whose every value is −1, of a family whose own document shows none of
that geometry (RE-204: every such form hidden or void, or none). No
measured file has one.
- `probes/examples/probe_re299_unset_map_doors.rs`,
`tools/re/void_only_vs_ifc.py` (counts the probe's further columns).
- `reports/element-framing/RE-299-native-map-void-only-doors.md`, an
update line in RE-84, the reconnaissance addendum,
`changelog.d/fixed/1247-native-map-void-only-doors.md`.

## Testing

Unit tests are banned in this repository: do not add them. Show the
change working end to end on real files instead.

- [ ] `tools/check-local.sh`: CI.
- [x] Verified end to end on real Revit files. Measure
[41538550670](https://github.com/DrunkOnJava/rvt-rs/actions/runs/41538550670)
(the probe on every reference model, the BIM4LCA office, the R2025
models and Autodesk's 36 samples): every door and window Revit exports
as its opening alone (RE1 Architecture's CasedOpening; Snowdon Towers' 9
Door-Opening and 38 Schematic Opening Cut) has an empty native map and
no saved graphics. The byte scan's all-unset signal also fires on
CasedOpening and Door-Opening, so the native map is the discriminator.
No other door or window in those files has a type the scan reads as
mapless.
- [x] Measure
[41548319465](https://github.com/DrunkOnJava/rvt-rs/actions/runs/41548319465)
(this change at 705c4aa, merged with `main` after #1252, against `main`
at ca389bb, unfiltered, with the office, R2025, held-out models and
samples): all 103 exports byte-identical (`ifc.sha256`), including the
58 sample projects and 13 held-out models, and the scorer diff is empty.
Earlier gates were cancelled when the head changed: 41541177152 (first
version) and 41545726999 (f832c25, before the merge).
- Review (Sonnet 5.5 xhigh, automated Claude Code review): no blocking
findings. Its notes are applied: the sizing and naming effect stated,
the all −1 hidden-or-void case guarded, the `refine_type_materials`
docs, the report's reproduce line.
- [ ] The positive case is #1247's private project: the reporter was
asked (with the maintainer's approval) to run `candidate/1247` at
429781b, which carries this change, and report counts.
- N/A: performance (the same native pass).

## Audit-honesty checklist

- [x] README / docs reflect the current state after this PR
- [x] A user-visible change has its entry as a fragment under
`changelog.d/`
- [x] Resolved limitations: RE-84 carries the update
- [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: probe, report, reconnaissance
addendum

## Legal and contribution hygiene

- [x] Apache-2.0
- [x] No Autodesk proprietary content
- [x] No fixtures

Contributor declaration (CLEANROOM.md): this finding rests on byte and
record observations of public files and on Revit's own IFC exports of
them; no Autodesk SDK source, disassembly or NDA material was used.

## Breaking changes

None. `geometry_maps` is new under `rvt::research` (unstable).
DrunkOnJava added a commit that referenced this pull request Nov 3, 2026
…instead of keeping its fixture's box (#1247) (RE-300) (#1255)

## Summary

Saved meshes leave a light fixture's light source out of its body,
instead of keeping the fixture's box. The light source is saved as a
`GFilter` of its own. It holds a `GImposter` (a transform and a
rendering `Asset` of scalar properties, no surface) with the `Geometry`,
curves and sometimes `GPolyMesh` that draw the light. Revit's export
draws none of it. The traversal now leaves that filter out, as it leaves
out a filter of curves (RE-300).

## Motivation

#1247: a light fixture of 18,944 triangles in v0.6.1 is a 12-triangle
box in v0.12.0. Reproduced on our files: Snowdon Towers electrical went
from 1,420 saved meshes to 907, with 511 light fixtures kept as boxes by
RE-224's partial gate. `GImposter` is the class behind 1,813 of the
3,020 partial meshes in the census (Measure 41540910235). The maintainer
chose (2026-11-02) to keep boxes for meshes that do not decode whole and
to close decoder gaps instead; this closes this gap without drawing
anything partial.

## Changes

- `src/research/native_graphics_traversal.rs`: a `GFilter` holding a
`GImposter` and otherwise only leaves (`Geometry`, `GPolyMesh`, curves)
is a branch with no surface. All 281 observed filters are such. A filter
with any other child, and a `GImposter` anywhere else, are walked as
before (a decode failure, so the element keeps its box).
- `probes/examples/probe_re300_graphics_imposters.rs`,
`reports/element-framing/RE-300-light-source-filters.md`, the
reconnaissance addendum,
`changelog.d/fixed/1247-light-source-filters.md`.

## Testing

Unit tests are banned in this repository: do not add them. Show the
change working end to end on real files instead.

- [ ] `tools/check-local.sh`: CI.
- [x] Verified end to end on real Revit files. Measure
[41550667212](https://github.com/DrunkOnJava/rvt-rs/actions/runs/41550667212)
(6689533 against `main` at a8ad9dd, which reads left-handed cylinders
since #1252, `--saved-meshes`):
- Office as built: 10,881 → 10,924 bodies drawn from saved meshes (322 →
279 boxes). Scored against Revit's export 1,452 → 1,495; exact to 0.001
ft 1,360 → 1,403. Every new body is exact: 35 wall-mounted and 8
ceiling-mounted "LEX Exit Sign". At permit the same, +43.
  - RE1 Electrical: +2, both exact.
- Snowdon Towers electrical: 907 → 1,324 (556 → 139 boxes).
Architectural: 1,971 → 2,303. rme samples: +410 each.
- Core Interior and RE1 Architecture, Mechanical and Plumbing are
unchanged.
- Schema check: only its count of swept solids changes (office 2,404 →
2,361). Snowdon electrical's `--saved-meshes` export passes 30 MB, so
IfcOpenShell's full validation skips it, as for any export over that
size.
- Size: the office as built goes from 447,784 to 473,681 triangles,
Snowdon electrical from 41,376 to 815,408.
- Before #1252, Measure 41547683034 measured the any-child version of
the rule: +39 office bodies, all exact.
- [x] A first reading that only skipped the `GImposter` (Measure
41544207306) drew the light's shape with the fixture, at 12 times
Revit's volume on the office's exit signs. It is withdrawn; RE-300 §2
records it.
- [x] Measure
[41550677314](https://github.com/DrunkOnJava/rvt-rs/actions/runs/41550677314)
(6689533 against `main` at a8ad9dd, default flags, unfiltered, with
the office, R2025, held-out models and samples): all 103 exports
byte-identical and the scorer diff empty. No door or window's saved
graphics on these files holds a light source, so the change shows only
with `--saved-meshes`. 41549448369, at 6afb244, was cancelled when the
rule was narrowed. The commit after 6689533 (1f9bb08) changes the
report, changelog and addendum only.
- Review (Sonnet 5.5 xhigh, automated Claude Code review): no blocking
findings. Its notes are applied:
  - the rule fails closed, applying only to filters of leaves;
  - the figures are re-measured on `main` with RE-301;
  - the changelog says `--saved-meshes`;
- the samples' new bodies are stated as consistent with light fixtures,
not measured;
  - the report's counts and class list are corrected.
- N/A: performance (the same pass, fewer branches).

## Audit-honesty checklist

- [x] README / docs reflect the current state after this PR
- [x] A user-visible change has its entry as a fragment under
`changelog.d/`
- [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: probe, report, measured counts
against Revit's own export, reconnaissance addendum

## Legal and contribution hygiene

- [x] Apache-2.0
- [x] No Autodesk proprietary content
- [x] No fixtures

Contributor declaration (CLEANROOM.md): based on the saved graphics' own
records read from public files (the MIT RE1 models, the CC BY-SA BIM4LCA
office, Autodesk's published sample projects) and Revit's IFC exports of
them; no Autodesk SDK source, disassembly or NDA material.

## Breaking changes

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

Labels

area:geometry Work in the geometry area. area:ifc Work in the ifc area. type:bug Correctness defect or regression.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant