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>
117 lines
4.2 KiB
JavaScript
117 lines
4.2 KiB
JavaScript
// Host shims for the occ_service worker module (emscripten --pre-js).
|
|
//
|
|
// KiCad's wxWidgets wasm port reads/writes settings through wxConfig, which
|
|
// bridges to JS hooks (getConfigEntryLength, …). The web editor provides those
|
|
// via wx.js, backed by the browser's localStorage. The service runs in a
|
|
// dedicated Worker (or Node for unit runs) with no localStorage and needs no
|
|
// persisted settings, so we back the same hooks with an in-memory store: every
|
|
// read returns "absent" → KiCad falls back to defaults; writes live only for
|
|
// the module lifetime. Semantics mirror wxwidgets/build/wasm/wx.js exactly
|
|
// (sans persistence). Same shim as wasm/cli/sym_convert_pre.js.
|
|
(function( g ) {
|
|
if( !g.localStorage )
|
|
{
|
|
var store = new Map();
|
|
g.localStorage = {
|
|
get length() { return store.size; },
|
|
key: function( i ) { var k = Array.from( store.keys() )[i]; return k === undefined ? null : k; },
|
|
getItem: function( k ) { return store.has( k ) ? store.get( k ) : null; },
|
|
setItem: function( k, v ) { store.set( String( k ), String( v ) ); },
|
|
removeItem: function( k ) { store.delete( k ); },
|
|
clear: function() { store.clear(); },
|
|
};
|
|
}
|
|
|
|
var ls = g.localStorage;
|
|
|
|
// Only reached once a stored value exists; with the empty in-memory store these
|
|
// never run, but keep them correct in case settings are written then read back.
|
|
var s2u = function( str, buf, len ) {
|
|
var f = g.stringToUTF8 || ( g.Module && g.Module.stringToUTF8 );
|
|
f( str, buf, len );
|
|
};
|
|
|
|
g.hasConfigEntry = function( key ) { return ls.getItem( key ) !== null; };
|
|
|
|
g.hasConfigGroup = function( key ) {
|
|
for( var i = 0; i < ls.length; i++ ) if( ls.key( i ).startsWith( key ) ) return true;
|
|
return false;
|
|
};
|
|
|
|
g.getConfigEntryCount = function( prefix, recurse ) {
|
|
var n = 0;
|
|
for( var i = 0; i < ls.length; i++ ) {
|
|
var key = ls.key( i );
|
|
if( key.startsWith( prefix ) ) {
|
|
var end = key.indexOf( '/', prefix.length );
|
|
if( end == -1 || recurse ) ++n;
|
|
}
|
|
}
|
|
return n;
|
|
};
|
|
|
|
g.getConfigEntryIndex = function( prefix, index ) {
|
|
var n = 0;
|
|
for( var i = 0; i < ls.length; i++ ) {
|
|
var key = ls.key( i );
|
|
if( key.startsWith( prefix ) ) {
|
|
var end = key.indexOf( '/', prefix.length );
|
|
if( end == -1 ) { if( n >= index ) return i; else ++n; }
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
g.getConfigGroupCount = function( prefix, recurse ) {
|
|
var c = new Set();
|
|
for( var i = 0; i < ls.length; i++ ) {
|
|
var key = ls.key( i );
|
|
if( key.startsWith( prefix ) ) {
|
|
var end = key.indexOf( '/', prefix.length );
|
|
if( end != -1 ) { if( recurse ) end = key.lastIndexOf( '/' ); c.add( key.substring( prefix.length, end ) ); }
|
|
}
|
|
}
|
|
return c.size;
|
|
};
|
|
|
|
g.getConfigGroupIndex = function( prefix, index ) {
|
|
var c = new Set();
|
|
for( var i = 0; i < ls.length; i++ ) {
|
|
var key = ls.key( i );
|
|
if( key.startsWith( prefix ) ) {
|
|
var end = key.indexOf( '/', prefix.length );
|
|
if( end != -1 ) {
|
|
var child = key.substring( prefix.length, end );
|
|
if( !c.has( child ) ) { if( c.size >= index ) return i; else c.add( child ); }
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
g.getConfigKeyLength = function( index ) { var k = ls.key( index ); return k ? k.length : 0; };
|
|
g.getConfigKey = function( index, buf, len ) { s2u( ls.key( index ), buf, len ); };
|
|
|
|
g.getConfigEntryLength = function( key ) { var v = ls.getItem( key ); return v === null ? -1 : v.length; };
|
|
g.getConfigEntry = function( key, buf, len ) {
|
|
var v = ls.getItem( key );
|
|
if( v !== null ) { s2u( v, buf, len ); return true; }
|
|
return false;
|
|
};
|
|
|
|
g.setConfigEntry = function( key, value ) { ls.setItem( key, value ); };
|
|
|
|
g.renameConfigGroup = function( oldG, newG ) {
|
|
var keys = [];
|
|
for( var i = 0; i < ls.length; i++ ) {
|
|
var key = ls.key( i );
|
|
if( key.startsWith( oldG ) ) keys.push( key );
|
|
else if( key.startsWith( newG ) ) return false;
|
|
}
|
|
for( var j = 0; j < keys.length; j++ ) {
|
|
var nk = newG + keys[j].substring( oldG.length );
|
|
ls.setItem( nk, ls.getItem( keys[j] ) ); ls.removeItem( keys[j] );
|
|
}
|
|
return keys.length > 0;
|
|
};
|
|
})( typeof globalThis !== 'undefined' ? globalThis : this );
|