fix(geometry): gate opening classification on triangle count, not vertex-buffer length - #4178
fix(geometry): gate opening classification on triangle count, not vertex-buffer length#4178BIMvoice wants to merge 2 commits into
Conversation
… count, not vertex-buffer length `classify_openings_impl`'s >100-vertex fallback gate (issue #635) read `opening_mesh.positions.len() / 3`, which also counts per-`IfcFace` vertex duplication the faceted-brep mesher emits. Welding (#4103) merges those duplicate vertex slots without touching `indices`, so the same cutter geometry could land on either side of the threshold depending purely on how redundantly its vertices happened to be stored — measured on a real corpus in the issue: 7 of 618 openings moved branch from welding alone, with zero triangles changed. Switch the gate to triangle count (`indices.len() / 3`), which is invariant to vertex duplication and welding. Add `triangle_count` to `OpeningDiagnostic` alongside the existing `vertex_count` (kept for diagnostics only, documented as no longer the classification signal). Regression test (`router/voids/synthesis_tests.rs`) drives the real production path with a faceted-brep opening authored with per-cell duplicate points (8x8 grid, 128 triangles, 256 raw vertices, 81 after weld) and asserts the classifier keeps the welded mesh. Reverting the fix flips the assertion from 81 to 256 vertices (verified). Refs #4119
|
Requesting |
|
Warning Review limit reached
This review includes 5 billable files and costs up to $1.25. Or wait 22 minutes for your next included review. View limit detailsLimit details: You’ve used all 2 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (5)
Comment |
Claude review - no findings for
|
Viewer benchmark✅ No threshold regressions detected. 01_Snowdon_Towers_Sample_Structural(1).ifcBaseline recorded 2026-07-01T20:31:05.538Z on github-actions ubuntu-latest, viewer-benchmark-ci (headless Chrome, SwiftShader ANGLE), production build.
AC20-FZK-Haus.ifcBaseline recorded 2026-07-01T20:30:59.972Z on github-actions ubuntu-latest, viewer-benchmark-ci (headless Chrome, SwiftShader ANGLE), production build.
Refresh the baseline from a CI run: dispatch the Benchmark workflow with |
…atertightness census for the triangle-count gate Two self-inflicted CI failures from the triangle-count classification change: - Module-size ratchet: the new triangle-count comments pushed router/voids/synthesis.rs to 973 lines against its 955-line allowlist budget. Extracted `remove_internal_membrane` (the AC20 glued-cap-to-cap deseaming helper) verbatim into a new sibling module, router/voids/synthesis/membrane.rs (206 lines, no allowlist entry needed). synthesis.rs is now 791 lines. - Geometry watertightness census: the larger CI corpus (1170 void hosts, vs. what's measurable against this repo's own small fixtures) contains two openings this metric switch does reclassify. Both are in ara3d/ISSUE_126_model.ifc (opening ids #98282 and #111997): previously routed to the >100-vertex AABB fallback, they now qualify for exact CSG under the >100-triangle gate. Per the re-run, both hosts went from torn (26 and 27 unmatched edges) to fully watertight (0), with fewer triangles each (220->186, 51->32) - the exact path produced a cleaner cut than the fallback did, not a worse one. Every other host is unchanged: 0 regressed, 0 coverage loss, 0 added, 0 volume-moved, and every corpus total (torn hosts, unmatched/strict edges, genuine defects) moved down or held. Golden re-blessed for exactly these 2 rows. Refs #4119
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Claude review - no findings for
|
Refs #4119
Summary
classify_openings_impl's >100-vertex fallback gate (issue #635's high-vertex AABB fallback) readopening_mesh.positions.len() / 3— the raw position-buffer length. That count also includes per-IfcFacevertex duplication the faceted-brep mesher emits, and welding (#4103) merges duplicate vertex slots without touchingindices. So the same cutter geometry could land on either side of the 100 threshold depending purely on how redundantly its vertices happened to be stored. The issue measured this on a real corpus: 7 of 618 openings moved branch from welding alone, with zero triangles changed.This switches the gate to triangle count (
indices.len() / 3), which is invariant to vertex duplication and welding.OpeningDiagnosticgains atriangle_countfield alongside the existingvertex_count(kept for diagnostics display only, and now documented as not what the classifier reads).Reproduction
Confirmed on
upstream/main—router/voids/synthesis.rsline ~102 readsopening_mesh.positions.len() / 3and gates the >100 branch on it, matching the issue's citation exactly.What's NOT in this PR
The issue also raises a separate, harder question: whether the numeric threshold itself (100) should be retuned once measured in triangle terms rather than vertex terms, since the two units aren't equivalent. The issue explicitly flags that as needing "its own before/after over the corpus and its own review" — I don't have access to the 618-opening corpus the issue measured against, so I kept the threshold value unchanged (100) and only fixed what it measures. Requesting
unqueuedsince this is partial.Note on that: leaving the constant at 100 while switching the metric is not "behavior unchanged at the boundary" — vertex counts typically run 2-3x triangle counts for faceted geometry, so an opening that previously needed roughly 33-50 triangles to hit the
>100fallback now needs 100, making the gate more permissive. That branch exists because those meshes "won't fit through the CSG safety thresholds" (synthesis.rs), so under-classification is the risky direction — a genuinely complex opening that used to get the safe AABB fallback could now attempt exact CSG instead; over-classification is merely conservative. Measured against every.ifcfile in this repo (samples plus geometry test fixtures), the largest opening is 92 vertices / 55 triangles, well under 100 on either metric, so zero openings here change classification and no output geometry differs.CI follow-ups on this branch
Module-size ratchet: the triangle-count comments above pushed
rust/geometry/src/router/voids/synthesis.rsto 973 lines against its 955-line allowlist budget. Extractedremove_internal_membrane(the AC20 glued-cap-to-cap deseaming helper — self-contained, no other state fromsynthesis.rs) verbatim into a new sibling modulerust/geometry/src/router/voids/synthesis/membrane.rs(206 lines, under the 400-line new-file threshold, no allowlist entry needed).synthesis.rsis now 791 lines.Geometry watertightness census (
tests/manifests/watertightness_census.tsv): the larger CI corpus (1170 void hosts vs. the handful measurable locally) contains openings this metric switch does reclassify — the local measurement in "What's NOT in this PR" above only covered this repo's own small fixtures. Two hosts inara3d/ISSUE_126_model.ifc(opening ids#98282and#111997) moved from the >100-vertex AABB fallback to exact CSG and, per CI, went from torn (26 and 27 unmatched edges) to fully watertight (0), with fewer triangles each (220→186, 51→32) — i.e. the exact path produced a cleaner cut than the fallback did, not a worse one. Every other host is unchanged: the CI run shows 0 regressed, 0 coverage loss, 0 added, 0 volume-moved, and every corpus total (torn hosts, unmatched/strict edges, genuine defects) moved down or held. This is what the "What's NOT in this PR" risk note flagged as possible in this larger corpus, confirmed here as an improvement rather than a regression, so the golden is re-blessed for these 2 hosts (IFCLITE_CENSUS_BLESS=1 cargo test -p ifc-lite-geometry --features triangulation-alt --test triangulation_invariance).The golden diff touches only those two rows — nothing else in the 1170-host manifest changed. Both rows go from torn to fully watertight with real enclosed-volume readings now populated (
#98282: 1776930 cm³,#111997: 290818 cm³, both previously blank on the torn/unmeasurable path). Corpus totals all moved down or held: torn hosts 104→102, unmatched edges 2848→2795, strict-rule edges 3752→3699, genuine defects 75→73, collapsed/non-invariant counts unchanged. Void-host population is identical on both sides, 1170/1170 — the improvement isn't fewer things being examined, it's the same population classified more accurately. Verified independently of CI: fetched the full 164-file corpus locally, re-ran withIFCLITE_CENSUS_BLESS=1to produce this diff, then re-ran again without bless to confirm the census now passes clean against it.Test plan
router::voids::synthesis::tests::issue_4119_triangle_count_gate::nonrectangular_opening_keeps_the_welded_mesh_not_the_raw_one(rust/geometry/src/router/voids/synthesis_tests.rs) drives the real production path (GeometryRouter::classify_openings) with a faceted-brep opening authored with per-cell duplicate points (8x8 grid → 128 triangles, 256 raw vertices, 81 unique vertices after the automatic weld insideapply_placement). Asserts the classifier's returnedNonRectangularmesh is the welded one (81 vertices).vertex_count > 100makes the same assertion fail with 256 instead of 81 (welded vertex count already ≤100 skips the fallback branch and falls through to the unwelded per-item path) — confirmed RED, then restored the fix and confirmed GREEN again.cargo test -p ifc-lite-geometry --lib: 860 passed, 0 failed, 1 ignored (pre-existing, unrelated).rust/geometry/tests/(engulfing_solid_void, issue_068/635/832/964/1007/1167/1367, multi_body_void_spike, opening_void_cut_local_frame_test, voids_inline_matrix_test, voids_production_test, voids_submesh_test, wall_opening_cut_regression): all pass, no regressions.cargo clippy -p ifc-lite-geometry --lib --tests: clean.check-module-size.mjs,check-test-wiring.mjs,check-source-text-assertions.mjs: all exit 0.@ifc-lite/wasm(patch).cargo test -p ifc-lite-processing --test module_size_ratchet: 6 passed.cargo test -p ifc-lite-geometry --features triangulation-alt --test triangulation_invariance watertightness_census_and_triangulator_invariance: passes against the re-blessed golden.https://claude.ai/code/session_01QPHChk3Ve9N519A4kY7436