ensureModelInMemfs memoized materialized refs in a Set<string> and, on a cache
hit, returned `${MODELS_3D_ROOT}/${ref}` — the ref's OWN path. But the wrl→step
fallback writes the body under the .step filename and returns that. So the first
ensure (prescan) wrote M.step and returned it, while the second ensure (the C++
viewer's PCBJAM_3D::EnsureModelFile lazy fallback) hit the early-return and got
back M.wrl — a file never written. KiCad then stat'd the missing .wrl
(S3D_CACHE::checkCache → GetModificationTime), so every component model on a
KiCad-6-vintage (all-.wrl) board failed with "Failed to retrieve file times for
'….wrl' (error 44)" and nothing rendered.
Fix: memoize ref → the ABSOLUTE path actually written (Set<string> →
Map<string,string>) and return that; doEnsure reuses an on-disk body via
analyzePath and records the real target. Regression test: a second ensure of a
.wrl ref returns the .step path, not the .wrl.
JS-only, no pcbnew rebuild. Only bit boards that reference .wrl (the CDN is
STEP-only from 10.x) AND double-ensure via prescan + lazy fallback — which is
why .step-ref boards worked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>