From 681de6f817a1630f87b62ebf9c06da2fa955bec7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 1 Jul 2026 09:21:33 +1000 Subject: [PATCH] ifcviewer-web: pick logs the object's IFC GUID via the on-demand deferred fetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First real consumer of the v15 deferred property block, and an end-to-end demonstration that on-demand property loading works. On a left-click pick, logSelectedObjectGuidWeb ensures the owning model's deferred block is loaded (loadDeferredMetadataWeb — a network fetch the FIRST time, cached after) and logs the picked object's GUID to the console. Fix uncovered while wiring it: applyCachedModel rebases instance object_ids to a per-model global base (object_id_base) to keep them unique across models, but the deferred elements carry the sidecar's original local ids — so a lookup by the picked (global) id missed. Store object_id_base on the model and rebase the elements by it when the deferred block loads. Verified: with a streamed model, the deferred block is fetched ONLY after the first pick (not at load), and the pick logs a valid 22-char IFC GUID. 111/111 unit + 6/6 web smoke pass. Co-Authored-By: Claude Opus 4.8 --- src/ifcviewer-web/main_web.cpp | 3 +++ src/ifcviewer/ModelGpuData.h | 4 ++++ src/ifcviewer/ViewportCore.cpp | 31 +++++++++++++++++++++++++++++++ src/ifcviewer/ViewportCore.h | 8 +++++++- 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/ifcviewer-web/main_web.cpp b/src/ifcviewer-web/main_web.cpp index 1d463dd38c..031ceb6803 100644 --- a/src/ifcviewer-web/main_web.cpp +++ b/src/ifcviewer-web/main_web.cpp @@ -131,6 +131,9 @@ EM_BOOL onMouseUp(int, const EmscriptenMouseEvent* e, void* user) { const bool remove = e->ctrlKey; app->core.pickObjectAtAsync(px, py, [app, add, remove](std::uint32_t id) { app->core.applyPickToSelection(id, add, remove); + // Demo the v15 on-demand deferred fetch: log the picked object's + // IFC GUID (first pick fetches the property block off the network). + if (id != 0) app->core.logSelectedObjectGuidWeb(id); app->host.requestFrame(); }); } diff --git a/src/ifcviewer/ModelGpuData.h b/src/ifcviewer/ModelGpuData.h index 7726491e46..272bff4b51 100644 --- a/src/ifcviewer/ModelGpuData.h +++ b/src/ifcviewer/ModelGpuData.h @@ -299,6 +299,10 @@ struct ModelGpuData { uint64_t deferred_meta_offset = 0; uint64_t deferred_meta_bytes = 0; bool deferred_meta_loaded = false; + // applyCachedModel rebases instance object_ids by this base to keep them + // globally unique across models; deferred elements carry the sidecar's + // original (local) ids, so they're rebased by the same amount on load. + uint32_t object_id_base = 0; // For each mesh in meshes[], the chunk it lives in plus the chunk-local // offsets into that chunk's vertex_storage and index_buffer. Populated diff --git a/src/ifcviewer/ViewportCore.cpp b/src/ifcviewer/ViewportCore.cpp index 8552ec6ccf..8927960a30 100644 --- a/src/ifcviewer/ViewportCore.cpp +++ b/src/ifcviewer/ViewportCore.cpp @@ -3008,6 +3008,7 @@ void ViewportCore::applyCachedModel(std::uint32_t model_id, inst_gpu.push_back(ig); } next_object_id_ = object_id_base + max_local_id + 1; + m.object_id_base = object_id_base; // deferred elements rebase to match const std::size_t inst_storage_bytes = inst_gpu.size() * sizeof(InstanceGpu); m.instance_storage = createBufferWithData( device_, queue_, @@ -3603,12 +3604,42 @@ void ViewportCore::loadDeferredMetadataWeb(std::uint32_t model_id, } mit->second.elements = std::move(tmp.elements); mit->second.string_table = std::move(tmp.string_table); + // Rebase element object_ids to the model's global id space so they + // match the (already-rebased) instance ids used by pick/selection. + const std::uint32_t base = mit->second.object_id_base; + for (auto& e : mit->second.elements) e.object_id += base; mit->second.deferred_meta_loaded = true; Log::info() << "ifcviewer-web: loaded deferred metadata (" << mit->second.elements.size() << " elements)"; if (done) done(true); }); } + +void ViewportCore::logSelectedObjectGuidWeb(std::uint32_t object_id) { + InstanceCompose::InstanceLookup lk; + if (!findInstance(object_id, lk)) return; // empty pick / unknown id + const std::uint32_t model_id = lk.model_id; + loadDeferredMetadataWeb(model_id, [this, object_id, model_id](bool ok) { + if (!ok) { + Log::warn() << "pick: deferred property fetch failed for object " << object_id; + return; + } + auto it = models_gpu_.find(model_id); + if (it == models_gpu_.end()) return; + const ModelGpuData& m = it->second; + for (const auto& e : m.elements) { + if (e.object_id != object_id) continue; + std::string guid = + (e.guid_length > 0 && + std::size_t(e.guid_offset) + e.guid_length <= m.string_table.size()) + ? m.string_table.substr(e.guid_offset, e.guid_length) + : std::string("(none)"); + Log::info() << "pick: object " << object_id << " GUID " << guid; + return; + } + Log::info() << "pick: object " << object_id << " not in element table"; + }); +} #endif // __EMSCRIPTEN__ void ViewportCore::streamingProgress(int& resident_chunks, int& total_chunks) const { diff --git a/src/ifcviewer/ViewportCore.h b/src/ifcviewer/ViewportCore.h index 646f3e66ff..c4dd303e49 100644 --- a/src/ifcviewer/ViewportCore.h +++ b/src/ifcviewer/ViewportCore.h @@ -375,10 +375,16 @@ public: // table) for a web-streamed model — what a UI (object tree / selected-name // / search) needs, fetched only when asked so first paint never waits on // it. Populates ModelGpuData.elements/string_table; fires done(ok). At most - // one fetch per model. Currently unwired (no consumer yet) but complete. + // one fetch per model. void loadDeferredMetadataWeb(std::uint32_t model_id, std::function done = {}); + // Demo consumer of the deferred fetch: on pick, ensure the owning model's + // property block is loaded (loadDeferredMetadataWeb — fetched once, on + // demand), then log the picked object's IFC GUID. The first pick triggers + // the network fetch; later picks reuse the cached element table. + void logSelectedObjectGuidWeb(std::uint32_t object_id); + // Kick off the async read of one chunk's vertex + index byte ranges (from // the active web source). applyStreamedChunk runs in the JS completion // callback; c.is_loading is held until then. No-op if the model/chunk