ifcviewer-web: pick logs the object's IFC GUID via the on-demand deferred fetch

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 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-07-01 09:21:33 +10:00
parent 13ec2be807
commit 681de6f817
4 changed files with 45 additions and 1 deletions
+3
View File
@@ -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();
});
}
+4
View File
@@ -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
+31
View File
@@ -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 {
+7 -1
View File
@@ -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<void(bool)> 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