mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-14 03:14:23 +00:00
ifcviewer: v15 — defer property metadata off the first-paint path
First-paint over a network is metadata-bound: the whole post-index metadata (~10 MB on a 118 MB model) had to download before any geometry. But ~25% of it — elements + string_table, the IFC element tree (names/GUIDs/hierarchy) — is used only for UI/picking, never for rendering (ViewportCore never touches it). v15 splits the post-index metadata into a render-CRITICAL block (meshes, instances, georef, chunk TOC) preceded by its byte length, then a DEFERRED block (elements + string_table). The web loader reads only the critical block before painting; the deferred block sits at a known, self-describing offset ([critical end, EOF)) and is fetched on demand. Desktop reads both (local). Web on-demand path is wired and complete (not yet called — no UI consumer): loadDeferredMetadataWeb(model_id) range-fetches + parses the deferred block into ModelGpuData.elements/string_table, at most once; the first consumer will be "show the selected object's name" on pick. No background prefetch — view-only sessions never download the property data (saves 2.64 MB on this model). parseSidecarTail split into parseSidecarCritical + parseSidecarDeferred (pure, unit-tested); StreamingSidecar gains the critical-block locator. Measured (118 MB model): critical metadata 10.35 -> 7.71 MB, deferred 2.64 MB off the path; first paint 10.5 -> 9.6 s @ 24 Mbps. (Instances still dominate the critical block — the next metadata lever.) Format -> v15, no back-compat; regenerate sidecars. 111/111 unit + 6/6 web smoke pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -96,9 +96,18 @@ bool writeSidecar(const std::string& ifc_path, const SidecarData& data) {
|
||||
|
||||
if (!writeVec(f, data.vertices)) { fclose(f); return false; }
|
||||
if (!writeVec(f, data.indices)) { fclose(f); return false; }
|
||||
|
||||
// v15: a render-critical metadata block (meshes, instances, georef, chunk
|
||||
// TOC) preceded by its byte length, then a deferred block (elements +
|
||||
// string_table). The length lets the web loader read just the critical
|
||||
// block before painting and fetch the property data lazily / not at all.
|
||||
const long crit_len_pos = ftell(f);
|
||||
uint64_t crit_bytes = 0;
|
||||
if (fwrite(&crit_bytes, sizeof(crit_bytes), 1, f) != 1) { fclose(f); return false; }
|
||||
const long crit_start = ftell(f);
|
||||
|
||||
if (!writeVec(f, data.meshes)) { fclose(f); return false; }
|
||||
if (!writeVec(f, data.instances)) { fclose(f); return false; }
|
||||
|
||||
// v11 georef block (148 B).
|
||||
if (fwrite(&data.has_coordinate_operation, 4, 1, f) != 1) { fclose(f); return false; }
|
||||
if (fwrite(data.coordinate_operation_meters,
|
||||
@@ -107,18 +116,24 @@ bool writeSidecar(const std::string& ifc_path, const SidecarData& data) {
|
||||
sizeof(double), 1, f) != 1) { fclose(f); return false; }
|
||||
if (fwrite(&data.map_unit_to_meters,
|
||||
sizeof(double), 1, f) != 1) { fclose(f); return false; }
|
||||
if (!writeVec(f, data.chunks)) { fclose(f); return false; }
|
||||
|
||||
// Backpatch the critical-block length.
|
||||
const long crit_end = ftell(f);
|
||||
if (crit_start < 0 || crit_end < 0) { fclose(f); return false; }
|
||||
crit_bytes = uint64_t(crit_end - crit_start);
|
||||
if (fseek(f, crit_len_pos, SEEK_SET) != 0) { fclose(f); return false; }
|
||||
if (fwrite(&crit_bytes, sizeof(crit_bytes), 1, f) != 1) { fclose(f); return false; }
|
||||
if (fseek(f, crit_end, SEEK_SET) != 0) { fclose(f); return false; }
|
||||
|
||||
// Deferred block: element tree + string table (UI/picking, never rendered).
|
||||
if (!writeVec(f, data.elements)) { fclose(f); return false; }
|
||||
|
||||
uint32_t stbl_len = static_cast<uint32_t>(data.string_table.size());
|
||||
if (fwrite(&stbl_len, 4, 1, f) != 1) { fclose(f); return false; }
|
||||
if (stbl_len > 0 && fwrite(data.string_table.data(), 1, stbl_len, f) != stbl_len) {
|
||||
fclose(f); return false;
|
||||
}
|
||||
|
||||
// v14 chunk TOC.
|
||||
if (!writeVec(f, data.chunks)) { fclose(f); return false; }
|
||||
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
@@ -139,10 +154,15 @@ std::optional<SidecarData> readSidecar(const std::string& ifc_path) {
|
||||
SidecarData data;
|
||||
if (!readVec(f, data.vertices)) return fail();
|
||||
if (!readVec(f, data.indices)) return fail();
|
||||
|
||||
// v15 critical-block length (consumed; only the streaming/web readers need
|
||||
// it for a one-shot range read — here we read sequentially).
|
||||
uint64_t crit_bytes = 0;
|
||||
if (fread(&crit_bytes, sizeof(crit_bytes), 1, f) != 1) return fail();
|
||||
|
||||
// Critical block: meshes, instances, georef, chunk TOC.
|
||||
if (!readVec(f, data.meshes)) return fail();
|
||||
if (!readVec(f, data.instances)) return fail();
|
||||
|
||||
// v11 georef block.
|
||||
if (fread(&data.has_coordinate_operation, 4, 1, f) != 1) return fail();
|
||||
if (fread(data.coordinate_operation_meters,
|
||||
sizeof(double), 16, f) != 16) return fail();
|
||||
@@ -150,18 +170,16 @@ std::optional<SidecarData> readSidecar(const std::string& ifc_path) {
|
||||
sizeof(double), 1, f) != 1) return fail();
|
||||
if (fread(&data.map_unit_to_meters,
|
||||
sizeof(double), 1, f) != 1) return fail();
|
||||
if (!readVec(f, data.chunks)) return fail();
|
||||
|
||||
// Deferred block: element tree + string table.
|
||||
if (!readVec(f, data.elements)) return fail();
|
||||
|
||||
uint32_t stbl_len;
|
||||
if (fread(&stbl_len, 4, 1, f) != 1) return fail();
|
||||
data.string_table.resize(stbl_len);
|
||||
if (stbl_len > 0 && fread(data.string_table.data(), 1, stbl_len, f) != stbl_len)
|
||||
return fail();
|
||||
|
||||
// v14 chunk TOC.
|
||||
if (!readVec(f, data.chunks)) return fail();
|
||||
|
||||
fclose(f);
|
||||
return data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user