Summary
brepkit-wasm 2.89.2 produces tessellated meshes with non-manifold edges for dovetail-connector baseplate exports that are watertight under brepjs-opencascade (OCCT). PR #692's compound-boolean variance fix is in this release and behaves correctly — but the tessellation/winding pathology is independent and still present.
The downstream consumer is gridfinity-layout-tool; the failing tests live in src/features/generation/worker/generators/baseplateGenerator.scenario.dovetail.test.ts (originally written against issue gridfinity-layout-tool#1407).
Observed under 2.89.2
| Scenario |
Non-manifold edges |
| 5×4 middle-column tile (3 join edges, tongue protrusion) |
142 |
| 4×4 interior tile (4 join edges) |
73 |
OCCT (default brepjs kernel) for the same params: 0.
The non-manifold count comes from edge-shared-by-≥3-triangles in the binary STL. The diagnostic also surfaces in stderr as [repairMeshWinding] Skipped non-manifold edges during winding repair { nonManifoldEdges: N, triangleCount: M } from the consumer's defensive winding-repair pass (which is a no-op under OCCT — see src/shared/generation/repairMeshWinding.ts).
Minimal repro
The full repro path is exportBaseplate(params, 'stl') → tessellate → binary STL. The BaseplateParams shapes that fail:
// 5×4 middle-column — 142 non-manifold edges
{
width: 5, depth: 4, gridUnitMm: 42,
magnetHoles: false,
paddingLeft: 0, paddingRight: 0, paddingFront: 0, paddingBack: 0,
fractionalEdgeX: 'end', fractionalEdgeY: 'end',
lightweight: true,
connectorNubs: true,
edges: { left: 'exterior', right: 'join', front: 'join', back: 'join' },
}
// 4×4 interior — 73 non-manifold edges
{
width: 4, depth: 4, gridUnitMm: 42,
magnetHoles: false,
paddingLeft: 0, paddingRight: 0, paddingFront: 0, paddingBack: 0,
fractionalEdgeX: 'end', fractionalEdgeY: 'end',
lightweight: true,
connectorNubs: true,
edges: { left: 'join', right: 'join', front: 'join', back: 'join' },
}
To reproduce in the consumer repo:
git checkout main # brepkit-wasm pinned at ^2.87.1 — bump to ^2.89.2 first
BREPJS_KERNEL=brepkit pnpm exec vitest run \
src/features/generation/worker/generators/baseplateGenerator.scenario.dovetail.test.ts
Expected: all 9 tests pass.
Actual under brepkit-wasm@2.89.2: at least 2 (middle-column tile, interior tile) fail with non-manifold edges: expected 142 to be +0 // Object.is equality. Older 2.89.1 run failed all 9.
Hypothesis
The 9-fail signature in 2.89.1 narrowed to 2-fail (early in the file) in 2.89.2, but the type of failure is unchanged: triangle winding emitted across face boundaries is inconsistent, causing edges to appear shared by ≥3 triangles in the merged mesh. PR #683 introduced deterministic face order in face_components; PR #692 fixed HashMap iteration in the GFA pipeline. Either:
- A remaining non-deterministic iteration site in the tessellation/edge-emission path is producing different winding for adjacent faces, or
- The boolean output topology is non-manifold (T-junction or shared coplanar face residue), which the tessellator then faithfully passes through.
The gridfinity-layout-tool fix that motivated these tests (TONGUE_PROTRUSION — extending the tongue's base edge a small amount INTO the slab to give the fuse shared volume rather than a degenerate coplanar face) was specifically to avoid hypothesis (2) under OCCT. If brepkit still produces non-manifold output even with the protrusion, hypothesis (1) or a separate residue case in the boolean is more likely.
Companion observation: perf
PR #692's variance fix landed cleanly — the same consumer's 4×4 with lip bench went from max 142ms / RME ±51.7% on 2.87.1 to max 21.5ms / RME ±4.0% on 2.89.2. Multi-boolean determinism is solved.
Independent of variance, the 2.89.1 test run had two outlier wall-times that are worth a sanity-check on 2.89.2 once the manifold issue is fixed:
| Test |
Wall-time on 2.89.1 |
binExporter.split-noexport > exports successfully after split preview — magnet+screw base with lip |
1,139 s |
baseplateGenerator.scenario.winding > magnet baseplate with lightweight floor — winding consistent |
649 s |
Both are likely consequences of the failure path (broken topology cascading into expensive mesh repair), so they should clear up alongside the manifold fix — flagging here in case they don't.
Environment
brepkit-wasm@2.89.2 via npm
brepjs@18.5.3 with BrepkitAdapter from brepjs.registerKernel('brepkit', new BrepkitAdapter(kernel))
- Node 24.11.0, vitest 4.1.6, Linux x86_64
Summary
brepkit-wasm2.89.2 produces tessellated meshes with non-manifold edges for dovetail-connector baseplate exports that are watertight underbrepjs-opencascade(OCCT). PR #692's compound-boolean variance fix is in this release and behaves correctly — but the tessellation/winding pathology is independent and still present.The downstream consumer is gridfinity-layout-tool; the failing tests live in
src/features/generation/worker/generators/baseplateGenerator.scenario.dovetail.test.ts(originally written against issue gridfinity-layout-tool#1407).Observed under 2.89.2
OCCT (default brepjs kernel) for the same params: 0.
The non-manifold count comes from edge-shared-by-≥3-triangles in the binary STL. The diagnostic also surfaces in stderr as
[repairMeshWinding] Skipped non-manifold edges during winding repair { nonManifoldEdges: N, triangleCount: M }from the consumer's defensive winding-repair pass (which is a no-op under OCCT — seesrc/shared/generation/repairMeshWinding.ts).Minimal repro
The full repro path is
exportBaseplate(params, 'stl')→ tessellate → binary STL. TheBaseplateParamsshapes that fail:To reproduce in the consumer repo:
Expected: all 9 tests pass.
Actual under
brepkit-wasm@2.89.2: at least 2 (middle-column tile,interior tile) fail withnon-manifold edges: expected 142 to be +0 // Object.is equality. Older 2.89.1 run failed all 9.Hypothesis
The 9-fail signature in 2.89.1 narrowed to 2-fail (early in the file) in 2.89.2, but the type of failure is unchanged: triangle winding emitted across face boundaries is inconsistent, causing edges to appear shared by ≥3 triangles in the merged mesh. PR #683 introduced deterministic face order in
face_components; PR #692 fixed HashMap iteration in the GFA pipeline. Either:The
gridfinity-layout-toolfix that motivated these tests (TONGUE_PROTRUSION— extending the tongue's base edge a small amount INTO the slab to give the fuse shared volume rather than a degenerate coplanar face) was specifically to avoid hypothesis (2) under OCCT. If brepkit still produces non-manifold output even with the protrusion, hypothesis (1) or a separate residue case in the boolean is more likely.Companion observation: perf
PR #692's variance fix landed cleanly — the same consumer's
4×4 with lipbench went frommax 142ms / RME ±51.7%on 2.87.1 tomax 21.5ms / RME ±4.0%on 2.89.2. Multi-boolean determinism is solved.Independent of variance, the 2.89.1 test run had two outlier wall-times that are worth a sanity-check on 2.89.2 once the manifold issue is fixed:
binExporter.split-noexport > exports successfully after split preview — magnet+screw base with lipbaseplateGenerator.scenario.winding > magnet baseplate with lightweight floor — winding consistentBoth are likely consequences of the failure path (broken topology cascading into expensive mesh repair), so they should clear up alongside the manifold fix — flagging here in case they don't.
Environment
brepkit-wasm@2.89.2via npmbrepjs@18.5.3withBrepkitAdapterfrombrepjs.registerKernel('brepkit', new BrepkitAdapter(kernel))