Skip to content

fix: snap cutter faces lying just off a parallel operand face before differences - #2282

Merged
beachtom merged 2 commits into
ThatOpen:mainfrom
rihokirss:codex/pr-snap-flush-cutter-faces
Oct 21, 2026
Merged

beachtom merged 2 commits into
ThatOpen:mainfrom
rihokirss:codex/pr-snap-flush-cutter-faces

Conversation

@rihokirss

@rihokirss rihokirss commented Oct 19, 2026 •

Copy link
Copy Markdown

Fixes the walls still broken in #2269.

Revit exports in #2269 place openings 0.02–0.14 mm off the wall faces and tilt them by about 1.8e-5 rad. Because of the tilt, the distance between an opening face and the wall face varies along the face, for example 0.998e-4 … 1.38e-4 m, which straddles the 1e-4 coplanarity tolerance. Some vertices of the face count as lying on the wall plane and others don't. Depending on the wall, the difference comes out open, keeps the opening's flipped face as a cap across the hole, or skips the cut. One wall fails with offsets of 0.02–0.07 mm, fully inside the tolerance, because the tilted face is still classified inconsistently.

Before each difference, this PR moves cutter faces that lie within 2× TOLERANCE_PLANE_DEVIATION of a parallel operand face exactly onto that face's plane (SnapToOperandPlanes in IfcGeometryProcessor.cpp):

  • Cutter positions are welded first, so every copy of a vertex moves together. A vertex on faces snapped to several planes goes to the closest point on all of them.
  • The stored vertex normals of a snapped face follow it. buildPlanes prefers them over the triangle normal and would otherwise tilt the face back.
  • Only planes that carry operand faces are used. Results of earlier differences can still list planes of former cutters, including non-finite ones.
  • A face within reach of parallel planes at different offsets is left alone, because the operand has a sliver thinner than the snap distance and the target is ambiguous.
  • Open cutters are left alone. The snapped cutter is used only if it is still closed after its planes are fitted.

Validation (element by element, with and without the change, against IfcOpenShell 0.8.5):

  • The reduced model from [Bug]: Incorrect boolean subtraction creates triangular surfaces across wall openings #2269, on main dd44aa7 with COORDINATE_TO_ORIGIN on and off:

    wall main this branch expected
    0jk7VvYE9D3uZXbw6Ypv5R open, 104.73 m², cap across the back of the hole 13.166 m³, 96.34 m² 13.165 m³, 96.33 m² (IfcOpenShell)
    2so2LXaZX1ZQ6s1VN2AI1S open, 56.99 m² 6.839 m³, 54.55 m² 6.839 m³, 54.55 m² (IfcOpenShell)
    3kzyxbyJn9FgaTZBR8AJx8 openings not cut, open 12.049 m³, 86.76 m² 12.050 m³ (wall minus openings; IfcOpenShell keeps a 0.1 mm skin)

    The other 48 elements are unchanged.

  • tests/ifcfiles/public: 8 files change, and 11 elements get better:

    • 7 now match IfcOpenShell: 1 in ISSUE_159_kleine_Wohnung_R22.ifc (open mesh → closed, 2.4275 m³) and 6 in S_Office_Integrated Design Archi.ifc;
    • 4 get closer to it: 2 walls in AC20-FZK-Haus.ifc (also in ISSUE_005_haus.ifc) and 2 more in S_Office.

    The rest change triangulation only. One S_Office element (#396617) turns from open to closed with the same volume; IfcOpenShell differs from both by 2.6 m³ and 20 m².

  • Six larger models, mostly private, with about 135,000 elements: 25 elements change. Seven are better: five now match IfcOpenShell and two are closer. The rest change triangulation only or by less than 0.01 %. None got worse.

  • tests/functional/BooleanRobustness.spec.js gets three synthetic metre-unit walls with misaligned extruded and faceted brep openings. All three fail on main and pass here. The expected values are the nominal sizes, because IfcOpenShell keeps a skin for two of them.

  • Jest: all 324 tests pass on this branch's freshly built Node WASM. npm run regression: all 30 models pass.

  • Cost: about +3 % total geometry time on a 640 MB model.

git clone --single-branch --branch codex/pr-snap-flush-cutter-faces https://github.com/rihokirss/engine_web-ifc.git web-ifc-snap-flush-cutter-faces
cd web-ifc-snap-flush-cutter-faces
npm ci
npm run build-release
npm test -- --runTestsByPath tests/functional/BooleanRobustness.spec.js

Build environment: Node 24.20.0, Emscripten 4.0.23.

Notes for review:

  • Behaviour change: a cutter face (or half-space plane) closer than 2× TOLERANCE_PLANE_DEVIATION to a parallel operand face (0.2 mm in metre models) now ends exactly on that face, so skins thinner than that are no longer kept.
  • The step is self-contained before the kernel call, so it is easy to put behind a setting if you prefer it opt-in.

…differences

Openings exported a tenth of a millimetre off the wall faces and tilted by
about 1e-5 rad straddle the 1e-4 coplanarity tolerance: some vertices of the
opening's face count as on the wall's plane, others as off it. The difference
then comes out open, caps the hole with the opening's flipped face, or skips
the cut altogether.

Before each difference, cutter faces within twice the plane deviation
tolerance of a parallel operand face are moved exactly onto that plane:

- cutter positions are welded first so every copy of a vertex moves together
- a vertex on faces snapped to several planes goes to the closest point on
  all of them
- the stored vertex normals follow the face, since buildPlanes prefers them
  and would otherwise tilt the face back
- a face within reach of parallel planes at different offsets (an operand
  sliver thinner than the snap distance) is left alone
- open cutters are left alone, and the snapped cutter is used only if it is
  still closed once its planes are fitted

Adds three synthetic metre-unit walls with misaligned extruded and faceted
brep openings to the boolean robustness tests.
Matches how file-local helpers are declared elsewhere in web-ifc. No functional change; the built WASM is byte-identical.
@beachtom
beachtom merged commit 10f5bae into ThatOpen:main Oct 21, 2026
3 checks passed
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.

2 participants