feat(wasm): occ-split — lazy occ_service worker; kicad_editor drops OCC (−31%)
Move OpenCASCADE out of the merged editor image into occ_service: a separate
emscripten module (-sASYNCIFY=0, MODULARIZE, in-container -Oz finalize, 2N+8
pre-warmed pthread pool) booted lazily in a dedicated Web Worker on the first
STEP export or STEP/IGES model parse. kicad_editor.wasm ~190 MB -> 130 MB;
sessions that never touch OCC never fetch its 57 MB. STEP export works in the
browser for the first time: the unchanged desktop dialog runs EXPORTER_STEP,
whose wasm shadow suspends into globalThis.occService and the export bytes go
straight to a browser download (never entering the editor heap). STEP/IGES 3D
models parse in the worker via the oce shadow (S3D WriteCache/ReadCache wire).
- wasm/occ-service/: service CMake target (hooked from the kicad fork's
top-level CMakeLists, wasm/editor pattern), embind entry
(occExport/occLoadModel), wxConfig pre-js.
- wasm/stubs/{exporter_step,oce_plugin}_stub.cpp: EM_ASYNC_JS worker bridges
(callee-shadowing; no caller #ifdefs).
- web/standalone: provider installed whenever the kicad_editor bundle boots
(cross-face safe); ONE shared worker-boot source occ-worker.js (vite ?raw;
the e2e stub reads the same file) — blob worker with locateFile absolutized
against the glue URL; export download-name guard.
- deps: OCC builds with RapidJSON so its glTF/GLB writer exists — pinned to
the vcpkg master snapshot 2025-02-26 (24b5e7a8b27f), the same code official
KiCad consumes via vcpkg.json's opencascade[rapidjson]; rapidjson's latest
tag (v1.1.0, 2016) is ill-formed under modern clang.
- tests: occ-export dialog e2e (lazy-fetch boundary + STEP download bytes),
occ-probe incl. a 9-format matrix (step/stpz/brep/xao/ply/stl/glb/u3d/pdf),
3d-viewer-models hard-asserts the worker parse; occ provider stub installed
ambiently by the kicad fixtures.
Validated against desktop kicad-cli 10.0.4: geometric exact equality (bbox
delta 0 um, volume delta 0.0000%) for STEP/GLB/STL/BREP/STPZ across three
boards and option sweeps — with desktop OCC 7.9 vs wasm OCC 7.8; PLY/XAO/PDF
structurally equal; U3D same-size (quantizer float LSBs differ). Full kicad
e2e green on Firefox and Chromium; standalone verified end to end (lazy fetch
only on the Export click; export.step 60,628 B ISO-10303-21; loadModel 700 KB
STEP -> 569 KB scenegraph cache).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:39:58 +02:00
|
|
|
import { downloadBytes } from "@/lib/download";
|
feat(occ): ship board 3D model bodies with STEP/GLB exports (3d-models 0007)
The occ_service export worker has its own MEMFS — the editor's lazily-fetched
lib models were invisible there, so every export was a bare board (54
"Could not add 3D model" warnings on pic_programmer, 2 STEP products).
- models-bridge: collectBoardModelFiles(boardText) — scan refs, ensure via
the 0004 sparse source (IDB/R2, wrl->step fallback), read staged bytes
back, dedupe by real staged path.
- occ-service.ts: attach the collected models to every export request
(best-effort — prefetch failure still exports, misses reported by the
exporter); transfer the body buffers.
- occ-worker.js (shared app/harness): pass req.models through to occExport.
- occ_service_main.cpp: occExport(board, params, models) stages each entry
under PCBJAM_3D::MODELS_MEMFS_ROOT (path-sanitized) for the exporter's
staged-model probe (kicad 83645275ac), removed again after the export.
- tests: harness occ stub mirrors the prefetch against the page kicadLibs
provider + captures report/productCount; new occ-export-models.spec.ts
guards the delivery (green companion pins preconditions; guard asserts 0
missing lib models + component PRODUCTs). pic_programmer: 17/17 staged,
87 products @ 13.3 MB (was 2 @ 402 KB). models-bridge unit tests 13/13.
Known remainder (0007 step 4): project-local ${KIPRJMOD} refs still drop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UjpnviP3ZDxTM1Ap63Sqv
2026-07-10 08:44:55 +02:00
|
|
|
import { collectBoardModelFiles, type BoardModelFile } from "./libs/models-bridge";
|
feat(wasm): occ-split — lazy occ_service worker; kicad_editor drops OCC (−31%)
Move OpenCASCADE out of the merged editor image into occ_service: a separate
emscripten module (-sASYNCIFY=0, MODULARIZE, in-container -Oz finalize, 2N+8
pre-warmed pthread pool) booted lazily in a dedicated Web Worker on the first
STEP export or STEP/IGES model parse. kicad_editor.wasm ~190 MB -> 130 MB;
sessions that never touch OCC never fetch its 57 MB. STEP export works in the
browser for the first time: the unchanged desktop dialog runs EXPORTER_STEP,
whose wasm shadow suspends into globalThis.occService and the export bytes go
straight to a browser download (never entering the editor heap). STEP/IGES 3D
models parse in the worker via the oce shadow (S3D WriteCache/ReadCache wire).
- wasm/occ-service/: service CMake target (hooked from the kicad fork's
top-level CMakeLists, wasm/editor pattern), embind entry
(occExport/occLoadModel), wxConfig pre-js.
- wasm/stubs/{exporter_step,oce_plugin}_stub.cpp: EM_ASYNC_JS worker bridges
(callee-shadowing; no caller #ifdefs).
- web/standalone: provider installed whenever the kicad_editor bundle boots
(cross-face safe); ONE shared worker-boot source occ-worker.js (vite ?raw;
the e2e stub reads the same file) — blob worker with locateFile absolutized
against the glue URL; export download-name guard.
- deps: OCC builds with RapidJSON so its glTF/GLB writer exists — pinned to
the vcpkg master snapshot 2025-02-26 (24b5e7a8b27f), the same code official
KiCad consumes via vcpkg.json's opencascade[rapidjson]; rapidjson's latest
tag (v1.1.0, 2016) is ill-formed under modern clang.
- tests: occ-export dialog e2e (lazy-fetch boundary + STEP download bytes),
occ-probe incl. a 9-format matrix (step/stpz/brep/xao/ply/stl/glb/u3d/pdf),
3d-viewer-models hard-asserts the worker parse; occ provider stub installed
ambiently by the kicad fixtures.
Validated against desktop kicad-cli 10.0.4: geometric exact equality (bbox
delta 0 um, volume delta 0.0000%) for STEP/GLB/STL/BREP/STPZ across three
boards and option sweeps — with desktop OCC 7.9 vs wasm OCC 7.8; PLY/XAO/PDF
structurally equal; U3D same-size (quantizer float LSBs differ). Full kicad
e2e green on Firefox and Chromium; standalone verified end to end (lazy fetch
only on the Export click; export.step 60,628 B ISO-10303-21; loadModel 700 KB
STEP -> 569 KB scenegraph cache).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:39:58 +02:00
|
|
|
// The worker-side wrapper as text (vite ?raw): one shared source of truth,
|
|
|
|
|
// also injected by the e2e harness stub (tests/kicad/utils/occ-service.ts).
|
|
|
|
|
import occWorkerSource from "./occ-worker.js?raw";
|
|
|
|
|
import { resolveWasmBase } from "./wasm-assets";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* `globalThis.occService` — the lazy OpenCASCADE 3D service provider.
|
|
|
|
|
*
|
|
|
|
|
* pcbnew.wasm carries no OCC (docs/features/occ-split/): its two OCC-backed
|
|
|
|
|
* paths suspend via EM_ASYNC_JS bridges (wasm/stubs/{exporter_step,oce_plugin}_stub.cpp)
|
|
|
|
|
* and land here:
|
|
|
|
|
* { kind: "export", board, jobJson, fileName } → STEP/GLB/… export; the
|
|
|
|
|
* resulting bytes are delivered straight to the browser download path and
|
|
|
|
|
* only { ok, report } goes back to the editor.
|
|
|
|
|
* { kind: "loadModel", bytes, ext } → STEP/IGES parse + tessellation; returns
|
|
|
|
|
* the SCENEGRAPH serialized in KiCad's binary cache format, which the
|
|
|
|
|
* C++ stub rebuilds with S3D::ReadCache.
|
|
|
|
|
*
|
|
|
|
|
* The occ_service module (own emscripten instance, `-sASYNCIFY=0`) boots in a
|
|
|
|
|
* dedicated Worker on the FIRST request — a pcbnew session that never exports
|
|
|
|
|
* and never views STEP models never fetches it. Same cross-origin worker rules
|
|
|
|
|
* as the pthread workers (boot.ts): a same-origin blob wrapper importScripts
|
|
|
|
|
* the (possibly CDN) glue; the module's own pthread children reuse the trick
|
|
|
|
|
* via mainScriptUrlOrBlob.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
interface OccExportRequest {
|
|
|
|
|
kind: "export";
|
|
|
|
|
board: Uint8Array;
|
|
|
|
|
jobJson: string;
|
|
|
|
|
fileName: string;
|
feat(occ): ship board 3D model bodies with STEP/GLB exports (3d-models 0007)
The occ_service export worker has its own MEMFS — the editor's lazily-fetched
lib models were invisible there, so every export was a bare board (54
"Could not add 3D model" warnings on pic_programmer, 2 STEP products).
- models-bridge: collectBoardModelFiles(boardText) — scan refs, ensure via
the 0004 sparse source (IDB/R2, wrl->step fallback), read staged bytes
back, dedupe by real staged path.
- occ-service.ts: attach the collected models to every export request
(best-effort — prefetch failure still exports, misses reported by the
exporter); transfer the body buffers.
- occ-worker.js (shared app/harness): pass req.models through to occExport.
- occ_service_main.cpp: occExport(board, params, models) stages each entry
under PCBJAM_3D::MODELS_MEMFS_ROOT (path-sanitized) for the exporter's
staged-model probe (kicad 83645275ac), removed again after the export.
- tests: harness occ stub mirrors the prefetch against the page kicadLibs
provider + captures report/productCount; new occ-export-models.spec.ts
guards the delivery (green companion pins preconditions; guard asserts 0
missing lib models + component PRODUCTs). pic_programmer: 17/17 staged,
87 products @ 13.3 MB (was 2 @ 402 KB). models-bridge unit tests 13/13.
Known remainder (0007 step 4): project-local ${KIPRJMOD} refs still drop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UjpnviP3ZDxTM1Ap63Sqv
2026-07-10 08:44:55 +02:00
|
|
|
/** Board lib model bodies, prefetched here (R2/IDB) and staged worker-side
|
|
|
|
|
* under its MEMFS model root — the export worker has no delivery of its own. */
|
|
|
|
|
models?: BoardModelFile[];
|
feat(wasm): occ-split — lazy occ_service worker; kicad_editor drops OCC (−31%)
Move OpenCASCADE out of the merged editor image into occ_service: a separate
emscripten module (-sASYNCIFY=0, MODULARIZE, in-container -Oz finalize, 2N+8
pre-warmed pthread pool) booted lazily in a dedicated Web Worker on the first
STEP export or STEP/IGES model parse. kicad_editor.wasm ~190 MB -> 130 MB;
sessions that never touch OCC never fetch its 57 MB. STEP export works in the
browser for the first time: the unchanged desktop dialog runs EXPORTER_STEP,
whose wasm shadow suspends into globalThis.occService and the export bytes go
straight to a browser download (never entering the editor heap). STEP/IGES 3D
models parse in the worker via the oce shadow (S3D WriteCache/ReadCache wire).
- wasm/occ-service/: service CMake target (hooked from the kicad fork's
top-level CMakeLists, wasm/editor pattern), embind entry
(occExport/occLoadModel), wxConfig pre-js.
- wasm/stubs/{exporter_step,oce_plugin}_stub.cpp: EM_ASYNC_JS worker bridges
(callee-shadowing; no caller #ifdefs).
- web/standalone: provider installed whenever the kicad_editor bundle boots
(cross-face safe); ONE shared worker-boot source occ-worker.js (vite ?raw;
the e2e stub reads the same file) — blob worker with locateFile absolutized
against the glue URL; export download-name guard.
- deps: OCC builds with RapidJSON so its glTF/GLB writer exists — pinned to
the vcpkg master snapshot 2025-02-26 (24b5e7a8b27f), the same code official
KiCad consumes via vcpkg.json's opencascade[rapidjson]; rapidjson's latest
tag (v1.1.0, 2016) is ill-formed under modern clang.
- tests: occ-export dialog e2e (lazy-fetch boundary + STEP download bytes),
occ-probe incl. a 9-format matrix (step/stpz/brep/xao/ply/stl/glb/u3d/pdf),
3d-viewer-models hard-asserts the worker parse; occ provider stub installed
ambiently by the kicad fixtures.
Validated against desktop kicad-cli 10.0.4: geometric exact equality (bbox
delta 0 um, volume delta 0.0000%) for STEP/GLB/STL/BREP/STPZ across three
boards and option sweeps — with desktop OCC 7.9 vs wasm OCC 7.8; PLY/XAO/PDF
structurally equal; U3D same-size (quantizer float LSBs differ). Full kicad
e2e green on Firefox and Chromium; standalone verified end to end (lazy fetch
only on the Export click; export.step 60,628 B ISO-10303-21; loadModel 700 KB
STEP -> 569 KB scenegraph cache).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:39:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface OccLoadModelRequest {
|
|
|
|
|
kind: "loadModel";
|
|
|
|
|
bytes: Uint8Array;
|
|
|
|
|
ext: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type OccRequest = OccExportRequest | OccLoadModelRequest;
|
|
|
|
|
|
|
|
|
|
export interface OccResponse {
|
|
|
|
|
ok: boolean;
|
|
|
|
|
report?: string;
|
|
|
|
|
fileName?: string;
|
|
|
|
|
bytes?: Uint8Array;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
|
// eslint-disable-next-line no-var
|
|
|
|
|
var occService: { request(req: OccRequest): Promise<OccResponse> } | undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Assemble the worker blob: a one-line prelude carrying the glue URL, then the
|
|
|
|
|
* shared wrapper source (occ-worker.js), which reads `self.OCC_GLUE_URL`.
|
|
|
|
|
*/
|
|
|
|
|
export function occWorkerBlobParts(glueHref: string): string[] {
|
|
|
|
|
return [
|
|
|
|
|
`self.OCC_GLUE_URL = ${JSON.stringify(glueHref)};\n`,
|
|
|
|
|
occWorkerSource,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function installOccService(log: (msg: string) => void): void {
|
|
|
|
|
if (globalThis.occService) return;
|
|
|
|
|
|
|
|
|
|
let nextId = 1;
|
|
|
|
|
const pending = new Map<number, (res: OccResponse) => void>();
|
|
|
|
|
let workerP: Promise<Worker> | null = null;
|
|
|
|
|
|
|
|
|
|
const ensureWorker = (): Promise<Worker> => {
|
|
|
|
|
if (!workerP) {
|
|
|
|
|
workerP = (async () => {
|
|
|
|
|
// occ_service is a Bundle (a published delivery artifact), not a Tool —
|
|
|
|
|
// resolveWasmBase accepts either and looks the bundle up directly.
|
|
|
|
|
const base = await resolveWasmBase("occ_service");
|
|
|
|
|
const glue = new URL(`${base}/occ_service.js`, window.location.href).href;
|
|
|
|
|
log(`[occ] booting occ_service from ${base}`);
|
|
|
|
|
|
|
|
|
|
const worker = new Worker(
|
|
|
|
|
URL.createObjectURL(
|
|
|
|
|
new Blob(occWorkerBlobParts(glue), { type: "text/javascript" }),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
worker.onmessage = (e) => {
|
|
|
|
|
const { id, res } = e.data ?? {};
|
|
|
|
|
if (typeof id !== "number") return;
|
|
|
|
|
const resolve = pending.get(id);
|
|
|
|
|
if (resolve) {
|
|
|
|
|
pending.delete(id);
|
|
|
|
|
resolve(res as OccResponse);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
|
|
|
const onFirst = (e: MessageEvent) => {
|
|
|
|
|
if (e.data?.ready) {
|
|
|
|
|
worker.removeEventListener("message", onFirst);
|
|
|
|
|
resolve();
|
|
|
|
|
} else if (e.data?.bootError) {
|
|
|
|
|
reject(new Error(e.data.bootError));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
worker.addEventListener("message", onFirst);
|
|
|
|
|
worker.onerror = (e) => reject(new Error(`occ_service worker: ${e.message}`));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
log("[occ] occ_service ready");
|
|
|
|
|
return worker;
|
|
|
|
|
})().catch((e) => {
|
|
|
|
|
workerP = null; // a failed boot must stay retryable
|
|
|
|
|
throw e;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return workerP;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const post = (worker: Worker, req: OccRequest): Promise<OccResponse> => {
|
|
|
|
|
const id = nextId++;
|
|
|
|
|
const transfer: Transferable[] =
|
feat(occ): ship board 3D model bodies with STEP/GLB exports (3d-models 0007)
The occ_service export worker has its own MEMFS — the editor's lazily-fetched
lib models were invisible there, so every export was a bare board (54
"Could not add 3D model" warnings on pic_programmer, 2 STEP products).
- models-bridge: collectBoardModelFiles(boardText) — scan refs, ensure via
the 0004 sparse source (IDB/R2, wrl->step fallback), read staged bytes
back, dedupe by real staged path.
- occ-service.ts: attach the collected models to every export request
(best-effort — prefetch failure still exports, misses reported by the
exporter); transfer the body buffers.
- occ-worker.js (shared app/harness): pass req.models through to occExport.
- occ_service_main.cpp: occExport(board, params, models) stages each entry
under PCBJAM_3D::MODELS_MEMFS_ROOT (path-sanitized) for the exporter's
staged-model probe (kicad 83645275ac), removed again after the export.
- tests: harness occ stub mirrors the prefetch against the page kicadLibs
provider + captures report/productCount; new occ-export-models.spec.ts
guards the delivery (green companion pins preconditions; guard asserts 0
missing lib models + component PRODUCTs). pic_programmer: 17/17 staged,
87 products @ 13.3 MB (was 2 @ 402 KB). models-bridge unit tests 13/13.
Known remainder (0007 step 4): project-local ${KIPRJMOD} refs still drop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UjpnviP3ZDxTM1Ap63Sqv
2026-07-10 08:44:55 +02:00
|
|
|
req.kind === "export"
|
|
|
|
|
? [req.board.buffer, ...(req.models ?? []).map((m) => m.bytes.buffer)]
|
|
|
|
|
: [req.bytes.buffer];
|
feat(wasm): occ-split — lazy occ_service worker; kicad_editor drops OCC (−31%)
Move OpenCASCADE out of the merged editor image into occ_service: a separate
emscripten module (-sASYNCIFY=0, MODULARIZE, in-container -Oz finalize, 2N+8
pre-warmed pthread pool) booted lazily in a dedicated Web Worker on the first
STEP export or STEP/IGES model parse. kicad_editor.wasm ~190 MB -> 130 MB;
sessions that never touch OCC never fetch its 57 MB. STEP export works in the
browser for the first time: the unchanged desktop dialog runs EXPORTER_STEP,
whose wasm shadow suspends into globalThis.occService and the export bytes go
straight to a browser download (never entering the editor heap). STEP/IGES 3D
models parse in the worker via the oce shadow (S3D WriteCache/ReadCache wire).
- wasm/occ-service/: service CMake target (hooked from the kicad fork's
top-level CMakeLists, wasm/editor pattern), embind entry
(occExport/occLoadModel), wxConfig pre-js.
- wasm/stubs/{exporter_step,oce_plugin}_stub.cpp: EM_ASYNC_JS worker bridges
(callee-shadowing; no caller #ifdefs).
- web/standalone: provider installed whenever the kicad_editor bundle boots
(cross-face safe); ONE shared worker-boot source occ-worker.js (vite ?raw;
the e2e stub reads the same file) — blob worker with locateFile absolutized
against the glue URL; export download-name guard.
- deps: OCC builds with RapidJSON so its glTF/GLB writer exists — pinned to
the vcpkg master snapshot 2025-02-26 (24b5e7a8b27f), the same code official
KiCad consumes via vcpkg.json's opencascade[rapidjson]; rapidjson's latest
tag (v1.1.0, 2016) is ill-formed under modern clang.
- tests: occ-export dialog e2e (lazy-fetch boundary + STEP download bytes),
occ-probe incl. a 9-format matrix (step/stpz/brep/xao/ply/stl/glb/u3d/pdf),
3d-viewer-models hard-asserts the worker parse; occ provider stub installed
ambiently by the kicad fixtures.
Validated against desktop kicad-cli 10.0.4: geometric exact equality (bbox
delta 0 um, volume delta 0.0000%) for STEP/GLB/STL/BREP/STPZ across three
boards and option sweeps — with desktop OCC 7.9 vs wasm OCC 7.8; PLY/XAO/PDF
structurally equal; U3D same-size (quantizer float LSBs differ). Full kicad
e2e green on Firefox and Chromium; standalone verified end to end (lazy fetch
only on the Export click; export.step 60,628 B ISO-10303-21; loadModel 700 KB
STEP -> 569 KB scenegraph cache).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:39:58 +02:00
|
|
|
return new Promise<OccResponse>((resolve) => {
|
|
|
|
|
pending.set(id, resolve);
|
|
|
|
|
worker.postMessage({ id, req }, transfer);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const request = async (req: OccRequest): Promise<OccResponse> => {
|
feat(occ): ship board 3D model bodies with STEP/GLB exports (3d-models 0007)
The occ_service export worker has its own MEMFS — the editor's lazily-fetched
lib models were invisible there, so every export was a bare board (54
"Could not add 3D model" warnings on pic_programmer, 2 STEP products).
- models-bridge: collectBoardModelFiles(boardText) — scan refs, ensure via
the 0004 sparse source (IDB/R2, wrl->step fallback), read staged bytes
back, dedupe by real staged path.
- occ-service.ts: attach the collected models to every export request
(best-effort — prefetch failure still exports, misses reported by the
exporter); transfer the body buffers.
- occ-worker.js (shared app/harness): pass req.models through to occExport.
- occ_service_main.cpp: occExport(board, params, models) stages each entry
under PCBJAM_3D::MODELS_MEMFS_ROOT (path-sanitized) for the exporter's
staged-model probe (kicad 83645275ac), removed again after the export.
- tests: harness occ stub mirrors the prefetch against the page kicadLibs
provider + captures report/productCount; new occ-export-models.spec.ts
guards the delivery (green companion pins preconditions; guard asserts 0
missing lib models + component PRODUCTs). pic_programmer: 17/17 staged,
87 products @ 13.3 MB (was 2 @ 402 KB). models-bridge unit tests 13/13.
Known remainder (0007 step 4): project-local ${KIPRJMOD} refs still drop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UjpnviP3ZDxTM1Ap63Sqv
2026-07-10 08:44:55 +02:00
|
|
|
if (req.kind === "export") {
|
|
|
|
|
// Ship the board's lib model bodies with the request: the worker's
|
|
|
|
|
// EXPORTER_STEP resolves them from its own MEMFS (delivery gap doc:
|
|
|
|
|
// docs/features/3d-models/0007). Best-effort — an export without
|
|
|
|
|
// models still succeeds, each miss reported by the exporter.
|
|
|
|
|
try {
|
|
|
|
|
req.models = await collectBoardModelFiles(
|
|
|
|
|
new TextDecoder().decode(req.board),
|
|
|
|
|
);
|
|
|
|
|
if (req.models.length)
|
|
|
|
|
log(`[occ] shipping ${req.models.length} board model(s) with the export`);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
log(`[occ] model prefetch failed (exporting without models): ${e}`);
|
|
|
|
|
req.models = [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(wasm): occ-split — lazy occ_service worker; kicad_editor drops OCC (−31%)
Move OpenCASCADE out of the merged editor image into occ_service: a separate
emscripten module (-sASYNCIFY=0, MODULARIZE, in-container -Oz finalize, 2N+8
pre-warmed pthread pool) booted lazily in a dedicated Web Worker on the first
STEP export or STEP/IGES model parse. kicad_editor.wasm ~190 MB -> 130 MB;
sessions that never touch OCC never fetch its 57 MB. STEP export works in the
browser for the first time: the unchanged desktop dialog runs EXPORTER_STEP,
whose wasm shadow suspends into globalThis.occService and the export bytes go
straight to a browser download (never entering the editor heap). STEP/IGES 3D
models parse in the worker via the oce shadow (S3D WriteCache/ReadCache wire).
- wasm/occ-service/: service CMake target (hooked from the kicad fork's
top-level CMakeLists, wasm/editor pattern), embind entry
(occExport/occLoadModel), wxConfig pre-js.
- wasm/stubs/{exporter_step,oce_plugin}_stub.cpp: EM_ASYNC_JS worker bridges
(callee-shadowing; no caller #ifdefs).
- web/standalone: provider installed whenever the kicad_editor bundle boots
(cross-face safe); ONE shared worker-boot source occ-worker.js (vite ?raw;
the e2e stub reads the same file) — blob worker with locateFile absolutized
against the glue URL; export download-name guard.
- deps: OCC builds with RapidJSON so its glTF/GLB writer exists — pinned to
the vcpkg master snapshot 2025-02-26 (24b5e7a8b27f), the same code official
KiCad consumes via vcpkg.json's opencascade[rapidjson]; rapidjson's latest
tag (v1.1.0, 2016) is ill-formed under modern clang.
- tests: occ-export dialog e2e (lazy-fetch boundary + STEP download bytes),
occ-probe incl. a 9-format matrix (step/stpz/brep/xao/ply/stl/glb/u3d/pdf),
3d-viewer-models hard-asserts the worker parse; occ provider stub installed
ambiently by the kicad fixtures.
Validated against desktop kicad-cli 10.0.4: geometric exact equality (bbox
delta 0 um, volume delta 0.0000%) for STEP/GLB/STL/BREP/STPZ across three
boards and option sweeps — with desktop OCC 7.9 vs wasm OCC 7.8; PLY/XAO/PDF
structurally equal; U3D same-size (quantizer float LSBs differ). Full kicad
e2e green on Firefox and Chromium; standalone verified end to end (lazy fetch
only on the Export click; export.step 60,628 B ISO-10303-21; loadModel 700 KB
STEP -> 569 KB scenegraph cache).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:39:58 +02:00
|
|
|
let worker: Worker;
|
|
|
|
|
try {
|
|
|
|
|
worker = await ensureWorker();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return { ok: false, report: `occ_service unavailable: ${e}` };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const res = await post(worker, req);
|
|
|
|
|
|
|
|
|
|
if (req.kind === "export") {
|
|
|
|
|
// Deliver the export straight to the user; the editor gets status only
|
|
|
|
|
// (the bytes never enter pcbnew's heap).
|
|
|
|
|
if (res.ok && res.bytes?.length) {
|
|
|
|
|
// The dialog can hand over an extension-only name (".step" — its
|
|
|
|
|
// default filename field is empty in the browser); Chromium mangles a
|
|
|
|
|
// bare dotfile download to "step.txt", so give it a real stem while
|
|
|
|
|
// keeping the format extension the user picked.
|
|
|
|
|
const raw = req.fileName || res.fileName || "";
|
|
|
|
|
const name = !raw || raw.startsWith(".") ? `export${raw || ".step"}` : raw;
|
|
|
|
|
downloadBytes(name, res.bytes);
|
|
|
|
|
log(`[occ] export downloaded: ${name} (${res.bytes.length} bytes)`);
|
|
|
|
|
}
|
|
|
|
|
return { ok: res.ok, report: res.report, fileName: res.fileName };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
globalThis.occService = { request };
|
|
|
|
|
log("[occ] occ_service provider installed (lazy)");
|
|
|
|
|
}
|