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
This commit is contained in:
parent
9315f56760
commit
720cff54ba
8 changed files with 557 additions and 10 deletions
|
|
@ -7,10 +7,15 @@
|
|||
* RPC; this module never suspends, so it builds -sASYNCIFY=0.
|
||||
*
|
||||
* Two embind entry points, both batch/synchronous:
|
||||
* occExport(boardSexpr, paramsJson) -> { ok, report, fileName, bytes }
|
||||
* occExport(boardSexpr, paramsJson, models) -> { ok, report, fileName, bytes }
|
||||
* Parse the board text (KICAD_SEXP), map the official JOB_EXPORT_PCB_3D
|
||||
* JSON onto EXPORTER_STEP_PARAMS (the pcbnew_jobs_handler mapping) and
|
||||
* run EXPORTER_STEP — STEP/STEPZ/BREP/XAO/GLB/PLY/STL out.
|
||||
* run EXPORTER_STEP — STEP/STEPZ/BREP/XAO/GLB/PLY/STL out. `models` is
|
||||
* an array of { path, bytes } lib model bodies the host prefetched for
|
||||
* this board (R2/IDB via the editor's models-bridge); they are staged
|
||||
* under the shared MEMFS model root so the exporter's staged-model
|
||||
* probe (pcbjam_model_fetch.h FindStagedModel) resolves them, and
|
||||
* removed again after the export.
|
||||
* occLoadModel(bytes, ext) -> { ok, report, bytes }
|
||||
* Feed a STEP/IGES model to the (statically linked) oce plugin loader
|
||||
* and return the resulting SCENEGRAPH serialized with S3D::WriteCache —
|
||||
|
|
@ -48,6 +53,7 @@
|
|||
#include <pcb_io/pcb_io_mgr.h>
|
||||
#include <exporters/step/exporter_step.h>
|
||||
|
||||
#include <3d_cache/pcbjam_model_fetch.h> // PCBJAM_3D::MODELS_MEMFS_ROOT
|
||||
#include <plugins/3dapi/ifsg_api.h>
|
||||
|
||||
class SCENEGRAPH;
|
||||
|
|
@ -154,7 +160,70 @@ BOARD* loadBoardFromSexpr( const std::string& aSexpr, wxString* aErr )
|
|||
}
|
||||
|
||||
|
||||
emscripten::val occExport( std::string aBoardSexpr, std::string aParamsJson )
|
||||
// Stage the host-prefetched lib model bodies under the shared MEMFS model
|
||||
// root (PCBJAM_3D::MODELS_MEMFS_ROOT) where EXPORTER_STEP's staged-model
|
||||
// probe looks. Paths arrive as lib-relative refs ("<lib>.3dshapes/<n>.<ext>");
|
||||
// anything absolute or traversing is skipped defensively. Returns the staged
|
||||
// absolute paths so the caller can remove them after the export.
|
||||
std::vector<wxString> stageModelFiles( const emscripten::val& aModels )
|
||||
{
|
||||
std::vector<wxString> staged;
|
||||
|
||||
if( aModels.isNull() || aModels.isUndefined() || !aModels["length"].as<bool>() )
|
||||
return staged;
|
||||
|
||||
const size_t count = aModels["length"].as<size_t>();
|
||||
|
||||
for( size_t i = 0; i < count; ++i )
|
||||
{
|
||||
emscripten::val entry = aModels[i];
|
||||
|
||||
if( entry.isNull() || entry.isUndefined() )
|
||||
continue;
|
||||
|
||||
const std::string rel = entry["path"].as<std::string>();
|
||||
emscripten::val bytes = entry["bytes"];
|
||||
|
||||
if( rel.empty() || rel.front() == '/' || rel.find( ".." ) != std::string::npos
|
||||
|| bytes.isNull() || bytes.isUndefined() )
|
||||
{
|
||||
std::fprintf( stderr, "[occ_service] models: skipping bad entry '%s'\n",
|
||||
rel.c_str() );
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t len = bytes["byteLength"].as<size_t>();
|
||||
std::vector<uint8_t> buf( len );
|
||||
|
||||
emscripten::val view =
|
||||
emscripten::val( emscripten::typed_memory_view( len, buf.data() ) );
|
||||
view.call<void>( "set", bytes );
|
||||
|
||||
const wxString path = wxString::FromUTF8(
|
||||
std::string( PCBJAM_3D::MODELS_MEMFS_ROOT ) + "/" + rel );
|
||||
|
||||
if( !wxFileName( path ).Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
|
||||
{
|
||||
std::fprintf( stderr, "[occ_service] models: mkdir failed for '%s'\n",
|
||||
rel.c_str() );
|
||||
continue;
|
||||
}
|
||||
|
||||
if( writeFile( path, buf.data(), buf.size() ) )
|
||||
staged.push_back( path );
|
||||
else
|
||||
std::fprintf( stderr, "[occ_service] models: write failed for '%s'\n",
|
||||
rel.c_str() );
|
||||
}
|
||||
|
||||
std::fprintf( stderr, "[occ_service] models: staged %zu/%zu\n", staged.size(),
|
||||
count );
|
||||
return staged;
|
||||
}
|
||||
|
||||
|
||||
emscripten::val occExport( std::string aBoardSexpr, std::string aParamsJson,
|
||||
emscripten::val aModels )
|
||||
{
|
||||
emscripten::val ret = emscripten::val::object();
|
||||
ret.set( "ok", false );
|
||||
|
|
@ -208,6 +277,10 @@ emscripten::val occExport( std::string aBoardSexpr, std::string aParamsJson )
|
|||
return ret;
|
||||
}
|
||||
|
||||
// Host-prefetched lib model bodies → the shared MEMFS model root, where
|
||||
// the exporter's staged-model probe resolves resolver-miss refs.
|
||||
const std::vector<wxString> stagedModels = stageModelFiles( aModels );
|
||||
|
||||
std::fprintf( stderr, "[occ_service] export: board parsed, running EXPORTER_STEP (%s)\n",
|
||||
params.GetFormatName().utf8_string().c_str() );
|
||||
|
||||
|
|
@ -248,6 +321,12 @@ emscripten::val occExport( std::string aBoardSexpr, std::string aParamsJson )
|
|||
|
||||
wxRemoveFile( exporter.m_outputFile );
|
||||
wxRemoveFile( wxString::FromUTF8( TMP_BOARD ) );
|
||||
|
||||
// Staged bodies are per-request (the host re-ships from its IDB/MEMFS
|
||||
// cache); don't let them accumulate in the worker across exports.
|
||||
for( const wxString& staged : stagedModels )
|
||||
wxRemoveFile( staged );
|
||||
|
||||
delete brd;
|
||||
|
||||
std::fprintf( stderr, "[occ_service] export %s (%zu bytes)\n",
|
||||
|
|
|
|||
Loading…
Reference in a new issue