OCCT Worker Protocol Specification
The Bitbybit OCCT Kernel in Bitbybit Runners is loaded in a separate Web Worker at runtime. It is not compiled into the main runner bundle, so the runner can talk to a worker of your own that speaks the same protocol, hosted with the rest of the assets and reached through cdnUrl.
This document specifies the complete message protocol between the runner (main thread) and the OCCT worker, for building or debugging a compatible worker.
To run the kernel with a modified OpenCASCADE, you relink the kernel, then use a worker that works with the glue your link produced (the Runner's own worker modified for your own use, or one built around your glue), since the Runner's worker bundles the glue of our kernel: the Runner Licensing page says how to get the materials and how the Runner then loads your kernel.
Architecture Overview
Main Thread (Runner) OCCT Web Worker
┌─────────────────────┐ ┌─────────────────────────┐
│ │ postMessage │ │
│ Runner init │ ──────────────►│ Load WASM │
│ │ {type: │ Load fonts │
│ │ "initialise"}│ Init the CAD kernel │
│ │ │ │
│ │◄────────────── │ postMessage │
│ State: initialised │ "occ- │ ("occ-initialised") │
│ │ initialised" │ │
│ │ │ │
│ API call │ ──────────────►│ Execute function │
│ {action, uid} │ │ Return result │
│ │◄────────────── │ {uid, result} │
│ │ │ │
└─────────────────────┘ └─────────────────────────┘
The worker is spawned as an ES module worker:
const workerUrl = URL.createObjectURL(
new Blob([`import "${cdnUrl}/workers/bitbybit-dev-occt-webworker.js";`],
{ type: "text/javascript" })
);
const worker = new Worker(workerUrl, { type: "module", name: "OCC_WORKER" });
Message Protocol
1. Initialization
Main thread → Worker:
worker.postMessage({
type: "initialise",
loadFonts: ["Roboto", "Tektur"] // array of font family names, or []
});
| Field | Type | Description |
|---|---|---|
type | string | Must be exactly "initialise" (British spelling) |
loadFonts | string[] | Font families to load for CAD text operations. Pass [] to skip font loading. |
Worker → Main thread (on success):
postMessage("occ-initialised")
The worker must post the exact string "occ-initialised" when the WASM kernel is loaded, initialized, and ready to accept API calls. The runner will not send any API calls until this message is received.
2. API Calls
Main thread → Worker:
worker.postMessage({
action: {
functionName: "shapes.wire.createCircleWire",
inputs: {
radius: 5,
center: [0, 0, 0],
direction: [0, 1, 0]
}
},
uid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
});
| Field | Type | Description |
|---|---|---|
action.functionName | string | Dot-path identifying the OCCT function (e.g., "shapes.wire.createCircleWire", "operations.extrude", "io.saveShapeSTEP") |
action.inputs | object | Key-value pairs matching the function's parameters. Shape references use the format described below. |
uid | string | Unique identifier for correlating request/response. Any unique string (e.g., a UUID). |
3. Responses
For every API call, the worker posts two messages:
Step 1 - Busy notification:
postMessage("busy")
The exact string "busy" signals that computation has started.
Step 2a - Success:
postMessage({
uid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", // echoed from request
result: { type: "occ-shape", hash: "abc123" } // or any serializable value
})
Step 2b - Error:
postMessage({
uid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
result: undefined,
error: "OCCT computation failed. <Error message> While executing function - shapes.wire.createCircleWire. Input values were: {radius: 5}. "
})
| Field | Type | Description |
|---|---|---|
uid | string | Must match the request uid |
result | any | The function's return value. undefined on error. |
error | string | Error description. Present only on failure. |
4. Shape References
Shapes (TopoDS_Shape objects) cannot be sent across the worker boundary directly. They are represented as references:
// Shape reference (TopoDS_Shape and subclasses)
{ type: "occ-shape", hash: "abc123def" }
// Entity reference (documents, assembly nodes, non-shape OCCT objects)
{ type: "occ-entity", hash: "xyz789" }
- When the main thread sends a shape reference in
inputs, the worker must resolve it to the actual cached shape object. - When the worker returns a shape, it must return a reference (not the raw object).
- The
hashis an opaque identifier managed by the worker's internal cache.
5. Reserved Functions
These function names have special behavior and bypass the standard function dispatch:
| Function Name | Purpose |
|---|---|
shapeToMesh | Convert a shape to mesh data for rendering |
shapesToMeshes | Convert multiple shapes to mesh data |
docToMesh | Convert an assembly document to mesh data |
docToMeshes | Convert several assembly documents to mesh data |
deleteShape | Remove a single shape from the internal cache |
deleteShapes | Remove multiple shapes from the internal cache |
deleteDocument | Remove an assembly document handle from the cache |
startedTheRun | Signal a new computation run; may clean cache if too large |
cleanAllCache | Clear all cached shapes and entities, and any meshes kept on them |
setMeshRetention | Set how many triangles meshing may keep on shapes for reuse ({ triangles }, 0 keeps none); answers with what is kept |
addOc | Add OpenCascade dependencies or plugins |
saveShapeSTEP | Export a shape to STEP format |
6. Worker State Lifecycle
loading → initialised → loaded ↔ computing
| State | Meaning |
|---|---|
loading | Worker is spawned but WASM not yet initialized |
initialised | WASM loaded, fonts loaded, ready for API calls |
computing | Processing an API call (triggered by "busy" message) |
loaded | Idle, no pending calls in the queue |
Replacing the OCCT Worker
Using cdnUrl (Recommended)
The simplest way to run your own worker (and a relinked kernel) is to host the worker and WASM files yourself, maintaining the expected directory structure:
https://your-cdn.com/bitbybit/
├── workers/
│ └── bitbybit-dev-occt-webworker.js ← your custom worker
└── wasm/
└── bitbybit-dev-occt.*.wasm ← your custom WASM binary
Then initialize the runner with your CDN URL:
const runner = window.bitbybitRunner.getRunnerInstance();
await runner.run({
canvasId: 'myCanvas',
enableOCCT: true,
cdnUrl: 'https://your-cdn.com/bitbybit/'
});
The runner constructs worker URLs by appending fixed file names to the cdnUrl. Your replacement files must use the same names as the originals (e.g., bitbybit-dev-occt-webworker.js).
WASM Resolution
Inside the worker, the WASM binary path is resolved via Emscripten's locateFile hook:
locateFile(path, scriptDirectory) {
if (path.endsWith(".wasm")) {
return cdnUrl + "/wasm/bitbybit-dev-occt.<hash>.wasm";
}
return scriptDirectory + path;
}
If you relink the kernel (Runner Licensing), update locateFile in your worker to point to your binary (or keep the original file name).
Building a Compatible Worker
Your custom worker must:
- Listen for
"initialise"messages and load your WASM kernel - Post
"occ-initialised"when ready - Listen for API call messages with
{ action: { functionName, inputs }, uid }format - Post
"busy"before each computation - Post
{ uid, result }or{ uid, result: undefined, error }responses for each call - Handle shape references (
{ type: "occ-shape", hash }) in inputs and outputs - Implement the reserved functions listed above
Worker Variants
Three OCCT worker variants exist, selectable via the occtArch option:
| Option | Worker File | WASM | Notes |
|---|---|---|---|
"32" (default) | bitbybit-dev-occt-webworker.js | bitbybit-dev-occt.*.wasm | Single-threaded, 32-bit |
"64" | bitbybit-dev-occt-64-bit-webworker.js | bitbybit-dev-occt-64-bit.*.wasm | Single-threaded, 64-bit |
"64-mt" | bitbybit-dev-occt-64-bit-mt-webworker.js | bitbybit-dev-occt-64-bit-mt.*.wasm | Multi-threaded (pthreads), 64-bit |
The multi-threaded variant additionally spawns pthread sub-workers. If replacing the 64-bit MT worker, you must also handle mainScriptUrlOrBlob for cross-origin pthread loading.