Kernels and withKernel
brepjs is not married to OpenCascade. The library sits behind a small abstraction — the KernelInterface — that lets multiple WASM-based geometry kernels back the same API. Today there are two: OpenCascade (production) and brepkit (experimental Rust). This chapter explains how to pick one, how to swap, and what withKernel(...) does for advanced use cases.
The two kernels
| Kernel | Status | Backend | Install | Strengths |
|---|---|---|---|---|
| OpenCascade | Production | C++ via Emscripten | brepjs-opencascade (or occt-wasm for the lower-level package) | Mature, complete operation set, STEP/IGES, decades of CAD heritage |
| brepkit | Active development | Rust via wasm-bindgen | brepkit-wasm | Smaller, faster, simpler API surface, no JS heritage |
The OpenCascade kernel is the default. brepkit is a drop-in alternative for environments where binary size or performance matters more than the long tail of operations OpenCascade supports.
Selecting a kernel at init
There are three init paths from Install & Initialize. Each picks a kernel:
| Init style | Kernel chosen |
|---|---|
import 'brepjs/quick' | Whatever's installed; prefers brepjs-opencascade |
await init() | Auto-detect: occt if brepjs-opencascade is installed, else brepkit |
await opencascade(); initFromOC(oc) | OpenCascade explicitly |
For brepkit explicitly:
import init, { Brepkit } from 'brepkit-wasm';
import { registerKernel, BrepkitAdapter, withKernel } from 'brepjs';
await init();
const bk = new Brepkit();
registerKernel('brepkit', new BrepkitAdapter(bk));
withKernel('brepkit', () => {
// brepjs operations here run against brepkit
});After registerKernel, the kernel is available globally — withKernel(id, fn) switches the active kernel inside a synchronous block.
withKernel — switching mid-program
If you've registered multiple kernels (the typical use case is testing or A/B comparison), withKernel(id, fn) runs fn with that kernel active:
import { withKernel, box, measureVolume } from 'brepjs';
const occtVolume = withKernel('occt', () => measureVolume(box(10, 10, 10)));
const brepkitVolume = withKernel('brepkit', () => measureVolume(box(10, 10, 10)));
console.log({ occtVolume, brepkitVolume });The active kernel reverts when fn returns. Outside withKernel, the default kernel (whichever was init'd last) is used.
Sync only — the trap
withKernel is synchronous. Async callbacks silently use the wrong kernel after the first await:
// WRONG — second await runs against whichever kernel is "current",
// not necessarily 'brepkit'.
withKernel('brepkit', async () => {
await someAsyncOp();
await anotherAsyncOp(); // may use the wrong kernel
});
// CORRECT — for async work, use getKernel() directly.
import { getKernel } from 'brepjs';
const k = getKernel('brepkit');
await someAsyncOpWith(k);
await anotherAsyncOpWith(k);This is a real footgun. The pattern checker (npm run check:patterns) flags async withKernel(...) callbacks. Production code that needs both kernels and async should use getKernel(id) directly and pass it through.
Why have a kernel abstraction?
Three reasons:
- Future-proofing. brepkit may eventually replace OpenCascade as the default. The abstraction means user code doesn't change when that happens.
- Testing. Every operation in brepjs runs against both kernels in CI (
TEST_KERNEL=occt npm test,TEST_KERNEL=brepkit npm test). Bugs that show in one kernel and not the other surface immediately. - Custom kernels. If you have your own geometry library, you can implement
KernelInterfaceand plug it in. See Writing a Custom Kernel.
The kernel interface is intentionally minimal — only the methods brepjs actually calls. New methods are added when new operations are added; the interface is segregated by concern (booleans, mesh, IO, etc.) so partial-conformance kernels are possible.
What "the active kernel" actually means
A handful of brepjs functions call getKernel() to dispatch:
// Internal — not user-facing.
function measureVolume(s: AnyShape): number {
return getKernel().measureVolume(s.wrapped);
}getKernel() returns the current kernel (or throws if none is registered). User code never calls it directly — the dispatch is hidden inside the library. You only encounter the kernel system when you choose to switch (withKernel) or when you write a custom adapter.
When you might not need this chapter
If you install brepjs-opencascade, run import 'brepjs/quick', and never think about kernels again, that's fine — that's the intended path for 90% of users. This chapter exists for the other 10%: people writing kernel adapters, dual-kernel test suites, or apps that need to compare results between kernels.
Next steps
- Tolerance and Validity — what
BRepCheckvalidates and how tolerance interacts with kernels - Writing a Custom Kernel — implementing
KernelInterfacefor your own backend - Kernel Conformance Suite — verifying a custom kernel against the brepjs test suite