/* * WASM shadow of the oce 3D plugin's flat-C surface (oce3d_*) — the editor * build links this INSTEAD of s3d_plugin_oce (no OCC in pcbnew.wasm; see * docs/features/occ-split/README.md). The static plugin registry * (kicad/3d-viewer/3d_cache/pcbjam_static_3d_plugins.cpp) consumes exactly * this surface, so the plugin manager, S3D_CACHE, and the scene build compile * untouched — .step/.iges footprint models keep loading, their parse just runs * in the occ_service worker. * * oce3d_Load: the model file is already in MEMFS (materialized by * PCBJAM_3D::EnsureModelFile before the plugin dispatch). Ship its bytes to * the worker (EM_ASYNC_JS suspend — legal here: EnsureModelFile already * suspends on this same S3D_CACHE::load path), get back the SCENEGRAPH * serialized in KiCad's own binary cache format, and rebuild it with * S3D::ReadCache. * * Metadata (extensions/filters/versions) mirrors plugins/3d/oce/oce.cpp and * include/plugins/3d/3d_plugin.h so the manager's handshake and extension map * are identical to the real plugin's. */ #include #include #include #include #include #include #include #include class SCENEGRAPH; namespace { // Non-Windows extension/filter lists from plugins/3d/oce/oce.cpp (FILE_DATA). const std::vector k_extensions = { "stp", "STP", "stpZ", "stpz", "STPZ", "step", "STEP", "stp.gz", "STP.GZ", "step.gz", "STEP.GZ", "igs", "IGS", "iges", "IGES" }; const std::vector k_filters = { "STEP (*.stp;*.STP;*.stpZ;*.stpz;*.STPZ;*.step;*.STEP;*.stp.gz;*.STP.GZ;*.step.gz;" "*.STEP.GZ)|*.stp;*.STP;*.stpZ;*.stpz;*.STPZ;*.step;*.STEP;*.stp.gz;*.STP.GZ;" "*.step.gz;*.STEP.GZ", "IGES (*.igs;*.IGS;*.iges;*.IGES)|*.igs;*.IGS;*.iges;*.IGES" }; bool acceptAnyCacheTag( const char*, void* ) { // The cache blob just crossed the worker boundary from our own writer — // there is no plugin-version drift to guard against. return true; } } // namespace // Suspends pcbnew while the worker parses + tessellates the model. JS returns // a malloc'd path string: the scenegraph-cache file it wrote into this // module's MEMFS ("" on failure). EM_ASYNC_JS( char*, js_occLoadModelRequest, ( const char* aModelPath ), { const modelPath = UTF8ToString( aModelPath ); let cachePath = ''; try { const hook = globalThis.occService; if( !hook || typeof hook.request !== 'function' ) { console.error( '[pcbjam-occ] loadModel: occ_service provider not installed' ); } else { const bytes = FS.readFile( modelPath ); // Uint8Array copy — transferable const dot = modelPath.lastIndexOf( '.' ); const ext = dot >= 0 ? modelPath.slice( dot + 1 ) : 'step'; const res = await hook.request( { kind: 'loadModel', bytes, ext } ); if( res && res.ok && res.bytes && res.bytes.length ) { cachePath = '/tmp/pcbjam_occ_model_cache.3dc'; FS.writeFile( cachePath, res.bytes ); } else if( res && res.report ) { console.error( '[pcbjam-occ] loadModel failed:', res.report ); } } } catch( e ) { console.error( '[pcbjam-occ] loadModel request failed:', e ); cachePath = ''; } const n = lengthBytesUTF8( cachePath ) + 1; const p = _malloc( n ); stringToUTF8( cachePath, p, n ); return p; } ) extern "C" { // --- class handshake (include/plugins/3d/3d_plugin.h semantics) ------------- char const* oce3d_GetKicadPluginClass( void ) { return "PLUGIN_3D"; } void oce3d_GetClassVersion( unsigned char* Major, unsigned char* Minor, unsigned char* Patch, unsigned char* Revision ) { if( Major ) *Major = 1; if( Minor ) *Minor = 0; if( Patch ) *Patch = 0; if( Revision ) *Revision = 0; } bool oce3d_CheckClassVersion( unsigned char Major, unsigned char, unsigned char, unsigned char ) { return Major == 1; } // --- plugin identity (plugins/3d/oce/oce.cpp values) ------------------------- const char* oce3d_GetKicadPluginName( void ) { return "PLUGIN_3D_OCE"; } void oce3d_GetPluginVersion( unsigned char* Major, unsigned char* Minor, unsigned char* Patch, unsigned char* Revision ) { if( Major ) *Major = 1; if( Minor ) *Minor = 4; if( Patch ) *Patch = 2; if( Revision ) *Revision = 0; } int oce3d_GetNExtensions( void ) { return (int) k_extensions.size(); } char const* oce3d_GetModelExtension( int aIndex ) { if( aIndex < 0 || aIndex >= (int) k_extensions.size() ) return nullptr; return k_extensions[aIndex].c_str(); } int oce3d_GetNFilters( void ) { return (int) k_filters.size(); } char const* oce3d_GetFileFilter( int aIndex ) { if( aIndex < 0 || aIndex >= (int) k_filters.size() ) return nullptr; return k_filters[aIndex].c_str(); } bool oce3d_CanRender( void ) { return true; } SCENEGRAPH* oce3d_Load( char const* aFileName ) { if( !aFileName ) return nullptr; char* cachePath = js_occLoadModelRequest( aFileName ); if( !cachePath || !*cachePath ) { std::free( cachePath ); return nullptr; } // ReadCache returns the top-level SCENEGRAPH as its SGNODE base. SGNODE* node = S3D::ReadCache( cachePath, nullptr, &acceptAnyCacheTag ); wxRemoveFile( wxString::FromUTF8( cachePath ) ); std::free( cachePath ); return reinterpret_cast( node ); } } // extern "C"