Skip to content

Identical dielectric sheets share one mesh (profile 0.10) - #35

Merged
midub merged 4 commits into
masterfrom
perf/shared-sheet-mesh
Oct 8, 2026
Merged

midub merged 4 commits into
masterfrom
perf/shared-sheet-mesh

Conversation

@midub

@midub midub commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #34 (base perf/dielectric-sheets; retarget to master once #34 is merged).

Problem

testcase1-RevC-full (IPC consortium Testcase1) has 11 dielectric layers with the same sheet (only through holes): 7.0 MB each, 77 MB of the 277 MB GLB, 4.2 M of 17.25 M triangles (perf-findings.md). Spec §4 required identity transforms on layer nodes, and each primitive names its own layer's feature table, so every sheet was stored again. #34 computes the sheet once; this PR stores it once.

Design (profile 0.10)

  • Transform. The first dielectric with a given sheet (in layers order) owns the mesh: identity transform, mesh in board coordinates at its own Z range, exactly as before. A later dielectric with the same sheet (a repeated sheet) uses the same mesh with translation [0, t, 0] and scale [1, s, 1] (s > 0, no rotation, no matrix) that maps the owner's [zMin, zMax] onto its own within float32 precision. Its geometry in board coordinates is the mesh transformed by its node. I rejected a unit-Z-range mesh with a transform on every user: no node would hold board coordinates any more and the shared case would differ from all other layers.
  • Feature table. The repeat shares the owner's feature table too: a primitive can name one propertyTable, and this keeps EXT_mesh_features consistent (every primitive's table is the featureTable of every layer that shows it). The tables were identical anyway (one SHEET row), and feature IDs derive from the layer, not the table (§5), so feat/PREPREG_2/0 stays. The alternative (keep a table per layer and let BOARDUI_board override the primitive) would hand generic EXT_mesh_features readers the other layer's table.
  • Scope: dielectric sheets only. They are the only layers that repeat in practice, and the converter knows they repeat without comparing geometry (the same drills cross them, Compute each distinct dielectric sheet once (same output) #34). Soldermask sheets differ by their openings; copper, paste and drawings never match. Widening the rule later is compatible, narrowing it would not be.
  • Sharing also needs the same material (the material is on the primitive; dielectrics have no source colours today, so this always holds) and a mesh (a sheet without geometry, as in IPC-2581-8-Layer-Design, which has no profile or copper, shares nothing).
  • Recorded in ADR 0016.

Implementation

  • boardui-gltf: LayerAsset::repeats: Option<usize>; the writer gives a repeat a node with the owner's mesh, the Y transform and the owner's table, and panics on an invalid repeat (documented). PROFILE_VERSION 0.10, schema version 0.10, featureTable description.
  • boardui-convert pipeline: a dielectric with the same crossing drills as an earlier one (Compute each distinct dielectric sheet once (same output) #34), the same colour and valid Z ranges is a repeat and isn't extruded; if the owner's sheet failed to mesh, each repeat is extruded on its own as before (so each layer keeps its warning). Stats still count the board's geometry (a shared mesh counts for each layer that shows it), so snapshot counts don't change. LayerMesh::restacked from Compute each distinct dielectric sheet once (same output) #34 is removed again: nothing moves meshes any more.
  • boardui validate: a layer node whose mesh an earlier layer node uses is a repeat; both must be dielectrics, the transform must be exactly the Y scale and translation mapping the Z ranges, and the feature table must be the owner's. Every other non-identity layer/drill transform is still rejected, and a layer or drill mesh used by any other node is now an error. Unit tests for each case.
  • Viewer: no source change needed. mergeLayer already bakes matrixWorld into board coordinates, and three 0.186's GLTFLoader clones a shared mesh per node and keeps their node associations apart. New tests on the new sample: merged geometry and feature boxes of each sheet at its own Z range, describe, shared table, and picking each sheet from the side. Profile ≤ 0.9 GLBs still load (the viewer only checks the major version; the fixture writes 0.2); the version test expects 0.10.
  • New hand-written sample stacked-sheets: 4 copper layers, two through vias and a blind via TOP–IN1. PREPREG_2 repeats the core (scale 0.2), PREPREG_1 has its own sheet.

Spec changes (please check)

  1. §4 (the main one): layer and drill nodes keep identity transforms except repeated sheets; new "Repeated sheets (since 0.10)" rule (owner, transform form and mapping, shared feature table, same material, dielectrics only). New MUST: a layer or drill mesh is used by no other node except repeated sheets (implicit before, now checked).
  2. §6.7: the sheet is the profile minus the holes that cross it; dielectrics with the same holes MAY share it.
  3. §8.1: a repeated sheet's primitives name the shared table. §8.2: the shared table keeps the owner's name; feature IDs still derive from each layer. §8.3: node/featureTable of a repeat; two layers have the same featureTable only then.
  4. §10: what boardui validate checks for repeats.
  5. §11: versions compare as integers (0.10 follows 0.9), and while the profile is a draft a minor version may also relax a rule. Strictly, 0.10 doesn't only "add optional data": a reader that assumed identity transforms on layer nodes would draw a repeat at its owner's height. The boardui viewer always applied node transforms, and generic glTF viewers do. Please confirm a minor bump is what you want (the brief asked for 0.10).
  6. profileVersion 0.10, spec/schema/structural-metadata.json version 0.10, BOARDUI_board.schema.json featureTable description; ADR 0016.

Numbers

GLB size (converter output, byte sizes from the conformance run and the CLI):

board master this PR
testcase1-RevC-full (11 dielectrics → 1 mesh) 277.2 MB 203.2 MB (−74.0 MB, −26.7 %)
royalblue54l-feather (7 → 1) 13.04 MB 11.88 MB
testcase11-rdgflx-RevC-full (5 → 2) 7.63 MB 7.17 MB
blind-buried-vias (23 → 3) 471 KB 396 KB
all other samples +4 bytes ("0.10")

Conversion of testcase1-RevC-full, docker-server (8 cores, load average 6–8 during this series), master b6932ae / #34 / this PR, alternating runs, medians:

master #34 this PR
WASM total (Node 24, first conversion per process, n = 7) 5.66 s 5.06 s 5.10 s
WASM cut and sheets / extrude / write 1.22 / 0.89 / 0.35 s 0.84 / 0.69 / 0.37 s 0.86 / 0.65 / 0.30 s
WASM memory after conversion 1,315 MB 1,282 MB 1,223 MB
native CLI wall (release, rayon, n = 14) 1.50 s 1.43 s 1.27 s
native extrude / write 0.153 / 0.286 s 0.135 / 0.294 s 0.127 / 0.211 s
native peak memory 1,397 MB 1,312 MB 1,141 MB

This PR saves the 10 sheet copies and 74 MB of writing: native −0.16 s and −170 MB, WASM −60 MB memory; its WASM total is within noise of #34 (cut and sheets is the same code in both and varied by 0.12 s between series).

Viewer (same viewer, master GLB vs this PR's GLB): perf-prof.mjs load profiles in headless Chromium were too noisy on the shared machine (load average 18–33, other sessions' builds; the unrelated node-material build varied 1.4–2.1 s between runs of the same GLB), so I timed the viewer's model build in Node instead (loadGltf + BoardModel.fromGltf from packages/viewer/dist, fresh process each, 5 alternating runs): GLB parse 336 → 251 ms (median), model build (mostly mergeLayer) 681 → 667 ms, i.e. unchanged: the repeats are still merged per layer, now with a matrix per vertex. Heap after first frame in Chromium 894 → 793 MB. Both GLBs load, render and become pickable in the browser.

Test evidence

  • In boardui-dev:1: cargo fmt --all --check, cargo clippy --workspace --all-targets --locked -- -D warnings, BOARDUI_REQUIRE_IPC_TESTCASES=1 cargo test --workspace --locked (conformance 52/52 incl. the new sample and the 5 fetched IPC test cases), cargo build -p boardui-wasm --target wasm32-unknown-unknown --release --locked; pnpm install && pnpm lint && pnpm build && pnpm test (viewer 168 tests incl. the 2 new ones).
  • Expected outputs regenerated with INSTA_UPDATE=always BOARDUI_BLESS=1: snapshots of blind-buried-vias, royalblue54l-feather and testcase11 change only by the new " mesh of layer/…" note on repeated sheets (counts, Z ranges and areas unchanged); new stacked-sheets.snap; the hand-written GLBs change by the profile version (and stacked-sheets is new).
  • boardui validate on testcase1-RevC-full from this PR: valid, 0 errors, 0 warnings. Khronos glTF Validator 2.0.0-dev.3.10 (as CI) on testcase1-RevC-full, stacked-sheets, blind-buried-vias, royalblue54l-feather and testcase11: 0 errors, 0 warnings (only the known infos).
  • Demo e2e (both apps, Playwright image, as CI): 56 passed, 14 skipped (the opt-in review specs and live gitlab.com). They convert every sample in the browser, including the three with repeated sheets, without errors.
  • Screenshot (dev page, iso view of stacked-sheets, not committed): the board edge shows the upper prepreg, the core and the shared lower prepreg as a continuous stack with no gap; the lower prepreg (core mesh scaled by 0.2) sits at its own height.

Known gaps / follow-ups

  • The viewer still merges every repeat into its own geometry (positions transformed per vertex), so viewer memory and BVH work don't shrink yet; sharing one BufferGeometry between the dielectric layers (or a slab proxy for picking, perf-findings.md) is a viewer follow-up.
  • The colour and invalid-Z guards can't trigger from IPC input today (dielectrics get no source colours, zero thicknesses get defaults); they keep the writer's invariants if that changes.

@CLAassistant

CLAassistant commented Oct 7, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@midub
midub marked this pull request as ready for review October 7, 2026 16:33
midub added 4 commits October 8, 2026 12:22
… 0.10)

A dielectric whose sheet repeats an earlier dielectric's (same crossing drills, same
colour) references that layer's mesh with a node transform that scales and moves it
along Y, and its feature table. The validator accepts exactly that and still rejects
other transforms and other shared meshes. New hand-written sample stacked-sheets.
@midub
midub force-pushed the perf/shared-sheet-mesh branch from a3924e4 to ffe47e7 Compare October 8, 2026 12:22
@midub
midub changed the base branch from perf/dielectric-sheets to master October 8, 2026 12:22
@midub
midub merged commit 4c1967f into master Oct 8, 2026
5 of 9 checks passed
@midub
midub deleted the perf/shared-sheet-mesh branch October 8, 2026 12:35
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