boardui turns an IPC-2581 file into a glTF board and shows it in 3D. The board is a standard glTF 2.0 file that any glTF viewer can open, and it carries metadata for every component, pin, net, pad and trace, so a viewer can highlight elements and attach widgets to them.
- Converter (Rust). IPC-2581 in, GLB out. Copper, soldermask, silkscreen, dielectric, drills, barrels and slots are extruded from 2D regions; components get placeholder bodies or your own glTF models. It runs as a command-line tool and as WebAssembly in the browser, so boards never have to be uploaded anywhere.
- Viewer (TypeScript + three.js).
<board-viewer>, a framework-independent web component: orbit, top/bottom/iso views, layer toggles, x-ray, hover and selection, net highlighting, and HTML widgets anchored to board elements. - Demo, in React and Angular. Drop an IPC-2581 file, view it, download the GLB. The conversion runs in your browser.
The original Angular/SVG viewer (2023 bachelor thesis) lives at the v1-angular tag and branch.
Prebuilt binaries are attached to every release:
| Platform | Archive |
|---|---|
| Linux x86_64 (static) | boardui-<version>-x86_64-unknown-linux-musl.tar.gz |
| Linux ARM64 (static) | boardui-<version>-aarch64-unknown-linux-musl.tar.gz |
| macOS Apple silicon | boardui-<version>-aarch64-apple-darwin.tar.gz |
| macOS Intel | boardui-<version>-x86_64-apple-darwin.tar.gz |
| Windows x86_64 | boardui-<version>-x86_64-pc-windows-msvc.zip |
Each archive holds the boardui binary, LICENSE, LICENSING.md and this README; SHA256SUMS lists their checksums. For example, on Linux:
v=1.1.1 t=x86_64-unknown-linux-musl
curl -LO https://github.com/midub/boardui/releases/download/v$v/boardui-$v-$t.tar.gz
curl -LO https://github.com/midub/boardui/releases/download/v$v/SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS
tar -xzf boardui-$v-$t.tar.gz
sudo install boardui-$v-$t/boardui /usr/local/bin/
boardui --versionThe macOS binaries are not signed: if macOS blocks one that was downloaded with a browser, run xattr -d com.apple.quarantine boardui. To build the CLI from source instead: cargo install --locked --path crates/boardui (see Building from source).
boardui convert board.xml -o board.glb # IPC-2581 in, boardui GLB out
boardui convert board.xml -o board.glb --models models.json # your glTF models for component bodies
boardui validate board.glb # check a GLB against the profileconvertoptions:--models <file>maps packages or parts to glTF models (schema, spec §6.9),--step <name>picks a step of a multi-step file,--tolerance <mm>sets the maximum chord deviation of arcs,--plating <mm>the barrel wall thickness,--validatechecks the output after writing it, and--all-warningsprints every warning instead of the first 20.validatechecks the profile rules (spec §10). It also runs the Khronos glTF validator whengltf_validatoris onPATH(--no-khronosskips it). It exits with status 1 if the file is invalid.-vprints the time of each pipeline step,-vvdebug details,-qonly errors.boardui help <command>lists everything.
The GLB opens in any glTF viewer. The samples in spec/samples double as the conformance suite.
<board-viewer> (packages/viewer) shows a boardui GLB, and with loadIpc2581 it converts IPC-2581 in a Web Worker first (packages/converter):
<board-viewer src="board.glb" style="height: 600px"></board-viewer>
<script type="module">
import '@boardui/viewer';
const viewer = document.querySelector('board-viewer');
viewer.addEventListener('bui-select', (e) => console.log(e.detail)); // { id, kind, properties } | null
// await viewer.loadIpc2581(file); // convert an IPC-2581 File locally, then show it
// viewer.highlight({ net: 'net/GND' }, { color: '#ffcc00' });
</script>In React, @boardui/react wraps the element: props for its settings, typed event handlers, ref to the element for its methods, and <Widget> to render React children as widgets that follow board elements:
import { BoardViewer, type BoardViewerElement } from '@boardui/react';
const viewer = useRef<BoardViewerElement>(null);
<BoardViewer ref={viewer} src="board.glb" style={{ height: 600 }} onSelect={(e) => console.log(e.detail)} />;
// await viewer.current.loadIpc2581(file);In Angular, @boardui/angular does the same with a standalone component: signal inputs for the settings, typed outputs for the events, element for the methods, and <bui-widget> for Angular content in widgets:
import { BoardViewer, type ElementInfo } from '@boardui/angular';
@Component({
imports: [BoardViewer],
template: `<bui-board-viewer src="board.glb" style="height: 600px" (select)="selected.set($event)" />`,
})
export class Board {
readonly selected = signal<ElementInfo | null>(null);
readonly viewer = viewChild.required(BoardViewer);
// await this.viewer().element.loadIpc2581(file);
}v1 publishes nothing to npm, so build the packages from this repository and install them as tarballs:
git clone https://github.com/midub/boardui && cd boardui
pnpm install && pnpm build # needs Rust and wasm-bindgen-cli, see below
pnpm --filter @boardui/converter --filter @boardui/viewer pack --pack-destination ../boardui-packages
cd ../my-app
npm install three ../boardui-packages/boardui-converter-1.1.1.tgz ../boardui-packages/boardui-viewer-1.1.1.tgzFor React, also pack @boardui/react (--filter @boardui/react) and install its tarball; for Angular, @boardui/angular; for runtime 3D models (KiCad's libraries, your own model server, STEP and OBJ), @boardui/models (how).
three (^0.186) is a peer dependency. The packages are ES modules for a bundler such as Vite; the converter's WebAssembly module and worker are referenced with new URL(…, import.meta.url), which Vite and other modern bundlers pick up. The Angular CLI's application builder doesn't, so an Angular app copies them next to its scripts (how). The demos, packages/demo-react and packages/demo-angular, are complete examples.
How a board is represented in glTF, which metadata it carries and how element IDs work: glTF profile spec, with JSON schemas and sample boards. Design background: architecture, roadmap, decision records.
- Browser conversion: the WebAssembly converter is 32-bit, so a conversion can use at most 4 GiB of memory. Boards up to about 250,000 features convert in the browser (a 238k-feature board needs 3.1 GB); larger ones need the CLI.
- Not modelled (profile 0.9): embedded components, cavities, rigid-flex. Panels (
StepRepeat) convert with a full copy of every board they place. Unknown IPC-2581 elements are skipped with a warning; data that exporters commonly write but that neither shapes the board nor describes its parts (roles, statuses, electrical properties of the stack-up, pick-up points, pin types) is skipped silently. - Component bodies are the package outline extruded to the component's height (estimated when the file has none), unless you map them to glTF models.
- The viewer needs WebGPU or WebGL2.
| Path | Contents |
|---|---|
crates/boardui-ipc2581 |
IPC-2581 reader |
crates/boardui-geom |
board geometry |
crates/boardui-gltf |
glTF profile writer |
crates/boardui-convert |
conversion pipeline and validation |
crates/boardui |
boardui command-line tool |
crates/boardui-wasm |
WebAssembly bindings |
packages/converter |
@boardui/converter: converter in a Web Worker |
packages/viewer |
@boardui/viewer: <board-viewer> |
packages/react |
@boardui/react: React wrapper of <board-viewer> |
packages/models |
@boardui/models: runtime model sources (KiCad's libraries, model mapping files) and STEP/OBJ loaders |
packages/angular |
@boardui/angular: Angular wrapper of <board-viewer> |
packages/demo-react |
demo app in React, deployed to GitHub Pages |
packages/demo-angular |
the same demo app in Angular, deployed to GitHub Pages |
packages/demo-shared |
the demos' framework-independent code, build helpers and e2e tests |
spec/ |
profile spec, JSON schemas, sample boards |
Prerequisites: rustup (the toolchain version is pinned in rust-toolchain.toml and installed automatically), Node.js 24 and pnpm (corepack enable). pnpm build also builds the WebAssembly converter, which needs wasm-bindgen-cli at the version of the wasm-bindgen crate in Cargo.lock (cargo install wasm-bindgen-cli --version 0.2.129) and, optionally, wasm-opt from binaryen.
# Rust
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo build -p boardui-wasm --target wasm32-unknown-unknown --release
cargo build -p boardui --release # → target/release/boardui
# Web
pnpm install
pnpm lint
pnpm build
pnpm test
pnpm site # the Pages site in site/: the demos and the redirect
pnpm e2e # Playwright tests of the demos (see packages/demo-shared)The demos run locally as Pages serves them with pnpm build && pnpm site && node packages/demo-shared/dist/build/cli.js serve site at http://127.0.0.1:4173/boardui/, or one at a time with pnpm --filter @boardui/demo-react preview (after pnpm build) and pnpm --filter @boardui/demo-angular dev.
CI (.github/workflows/ci.yml) runs all of the above plus the conformance suite. Pushes to master deploy the demos to GitHub Pages (pages.yml: the React demo at /boardui/react/, the Angular demo at /boardui/angular/, and https://midub.github.io/boardui/, which redirects to the React demo), and v* tags build the release binaries (release.yml).
Issues and pull requests are welcome; pull requests need a signed Contributor License Agreement. See CONTRIBUTING.md.
boardui is dual-licensed: AGPL-3.0-only, or a commercial licence for use without the AGPL's obligations, for example in a closed-source product. For a commercial licence, write to michal@michaldub.cz. LICENSING.md explains both. Releases up to v1.1.1 were published under the MIT licence and stay available under it.
Third-party parts keep their own licences (listed in LICENSING.md), and so do the sample boards in spec/samples that say so:
kicad-royalblue54l-feather: CERN-OHL-P v2, see itsLICENSE.ipc-testcases: the IPC-2581 Consortium's test cases are not in the repository, the demo or the release archives; the repository and the demo link to them on https://www.ipc2581.com.python3 spec/samples/ipc-testcases/fetch.pyfetches them as test data.

