mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-15 18:14:08 +00:00
ifcviewer: rename sidecar transfer/record types; drop unused element hierarchy (sidecar v17)
Rename the streamer/sidecar transfer and record types to describe what they are rather than how they move: MeshChunk -> StreamedMesh InstanceChunk -> StreamedInstance InstanceCpu -> InstanceInfo PackedElementInfo -> ElementTableRecord uploadMeshChunk -> uploadStreamedMesh uploadInstanceChunk -> uploadStreamedInstance buildMeshChunk -> buildStreamedMesh and the two post-index sidecar metadata blocks: "critical" metadata -> "geometry" metadata (meshes/instances/georef/TOC) "deferred" metadata -> "element" metadata (elements + string table) parseSidecarCritical -> parseSidecarGeometryMetadata parseSidecarDeferred -> parseSidecarElementMetadata The one behavioural change: the element hierarchy (parent_id) was carried through ElementInfo, ElementTableRecord, and the sidecar element table but never consumed, so drop it and bump SIDECAR_VERSION 16 -> 17. No back-compat: regenerate sidecars. sample.ifcview is regenerated at v17. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -283,7 +283,7 @@ namespace {
|
||||
ModelGpuData make_model_with_one_instance(uint32_t object_id, uint32_t mesh_id,
|
||||
double placement_tx) {
|
||||
ModelGpuData m;
|
||||
InstanceCpu inst{};
|
||||
InstanceInfo inst{};
|
||||
inst.mesh_id = mesh_id;
|
||||
inst.object_id = object_id;
|
||||
// Column-major identity with a tx for verification.
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
// vertex quantization used to fill it.
|
||||
//
|
||||
// quantizeVertex / octEncodeNormal (VertexQuantization.h) are the shared
|
||||
// production helpers: ViewportWindow::uploadMeshChunk and SidecarBuilder both
|
||||
// production helpers: ViewportWindow::uploadStreamedMesh and SidecarBuilder both
|
||||
// route through them so the rendered VBO and the on-disk .ifcview record are
|
||||
// byte-identical. The tests exercise that real implementation directly:
|
||||
// - runtime size/alignment assertions (defense in depth for the static_asserts)
|
||||
@@ -245,14 +245,14 @@ TEST_CASE("quantizeVertex passes the packed color through unchanged", "[instgeom
|
||||
REQUIRE(std::memcmp(dst + INSTANCED_VERTEX_COLOR_OFFSET, rgba, 4) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("MeshChunk and InstanceChunk default-init to zeroed metadata", "[instgeom]") {
|
||||
MeshChunk mc;
|
||||
TEST_CASE("StreamedMesh and StreamedInstance default-init to zeroed metadata", "[instgeom]") {
|
||||
StreamedMesh mc;
|
||||
REQUIRE(mc.model_id == 0);
|
||||
REQUIRE(mc.local_mesh_id == 0);
|
||||
REQUIRE(mc.vertices.empty());
|
||||
REQUIRE(mc.indices.empty());
|
||||
|
||||
InstanceChunk ic;
|
||||
StreamedInstance ic;
|
||||
REQUIRE(ic.model_id == 0);
|
||||
REQUIRE(ic.local_mesh_id == 0);
|
||||
REQUIRE(ic.object_id == 0);
|
||||
|
||||
@@ -83,7 +83,7 @@ SidecarData buildFixture() {
|
||||
|
||||
sd.instances.resize(5);
|
||||
for (size_t i = 0; i < sd.instances.size(); ++i) {
|
||||
InstanceCpu& inst = sd.instances[i];
|
||||
InstanceInfo& inst = sd.instances[i];
|
||||
inst.mesh_id = (i < 3) ? 0u : 1u;
|
||||
inst.object_id = uint32_t(100 + i);
|
||||
inst.color_override_rgba8 = uint32_t(0xAA000000u | (i * 0x010203u));
|
||||
@@ -109,11 +109,10 @@ SidecarData buildFixture() {
|
||||
sd.string_table = std::string("\0Wall\0Slab\0", 11); // includes embedded NULs
|
||||
sd.elements.resize(3);
|
||||
for (size_t i = 0; i < sd.elements.size(); ++i) {
|
||||
PackedElementInfo& e = sd.elements[i];
|
||||
ElementTableRecord& e = sd.elements[i];
|
||||
e.object_id = uint32_t(100 + i);
|
||||
e.model_id = 1;
|
||||
e.ifc_id = int32_t(1000 + i);
|
||||
e.parent_id = (i == 0) ? -1 : int32_t(100);
|
||||
e.guid_offset = 0; e.guid_length = 0;
|
||||
e.name_offset = 1; e.name_length = 4; // "Wall"
|
||||
e.type_offset = 6; e.type_length = 4; // "Slab"
|
||||
@@ -136,10 +135,10 @@ bool sidecarDataEqual(const SidecarData& a, const SidecarData& b) {
|
||||
if (std::memcmp(&a.meshes[i], &b.meshes[i], sizeof(MeshInfo)) != 0) return false;
|
||||
}
|
||||
for (size_t i = 0; i < a.instances.size(); ++i) {
|
||||
if (std::memcmp(&a.instances[i], &b.instances[i], sizeof(InstanceCpu)) != 0) return false;
|
||||
if (std::memcmp(&a.instances[i], &b.instances[i], sizeof(InstanceInfo)) != 0) return false;
|
||||
}
|
||||
for (size_t i = 0; i < a.elements.size(); ++i) {
|
||||
if (std::memcmp(&a.elements[i], &b.elements[i], sizeof(PackedElementInfo)) != 0) return false;
|
||||
if (std::memcmp(&a.elements[i], &b.elements[i], sizeof(ElementTableRecord)) != 0) return false;
|
||||
}
|
||||
|
||||
// v11 georef block.
|
||||
@@ -155,10 +154,12 @@ bool sidecarDataEqual(const SidecarData& a, const SidecarData& b) {
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("MeshInfo and InstanceCpu have stable layouts (sidecar wire format)", "[sidecar]") {
|
||||
TEST_CASE("MeshInfo and InstanceInfo have stable layouts (sidecar wire format)", "[sidecar]") {
|
||||
REQUIRE(sizeof(MeshInfo) == 56);
|
||||
REQUIRE(sizeof(InstanceInfo) == 232);
|
||||
REQUIRE(sizeof(InstanceGpu) == 80);
|
||||
REQUIRE(SIDECAR_VERSION == 16);
|
||||
REQUIRE(sizeof(ElementTableRecord) == 36);
|
||||
REQUIRE(SIDECAR_VERSION == 17);
|
||||
REQUIRE(sizeof(SidecarChunk) == 56);
|
||||
REQUIRE(SIDECAR_MAGIC == 0x49465657u);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ SidecarData buildFixture() {
|
||||
for (uint32_t k = 0; k < 3; ++k) { // outer loop = interleave
|
||||
for (int i = 0; i < N; ++i) {
|
||||
if (k >= ninst(i)) continue;
|
||||
InstanceCpu ic;
|
||||
InstanceInfo ic;
|
||||
ic.mesh_id = uint32_t(i); // authoritative
|
||||
ic.object_id = obj++;
|
||||
ic.model_id = 1;
|
||||
@@ -114,7 +114,7 @@ struct InstSig {
|
||||
}
|
||||
};
|
||||
|
||||
InstSig sigFor(const SidecarData& sd, const InstanceCpu& inst) {
|
||||
InstSig sigFor(const SidecarData& sd, const InstanceInfo& inst) {
|
||||
const MeshInfo& m = sd.meshes.at(inst.mesh_id);
|
||||
InstSig s{};
|
||||
s.verts.assign(sd.vertices.begin() + m.vbo_byte_offset,
|
||||
|
||||
@@ -84,7 +84,6 @@ SidecarData buildFixture() {
|
||||
sd.elements[i].object_id = uint32_t(100 + i);
|
||||
sd.elements[i].model_id = 1;
|
||||
sd.elements[i].ifc_id = int32_t(1000 + i);
|
||||
sd.elements[i].parent_id = (i == 0) ? -1 : int32_t(100);
|
||||
}
|
||||
// v16 stores geometry per-chunk (compressed); a fixture with geometry needs
|
||||
// a chunk TOC covering its meshes (one chunk per mesh here).
|
||||
@@ -113,8 +112,8 @@ TEST_CASE("readSidecarMetadataOnly returns metadata, skips bulk geometry",
|
||||
// Chunk TOC carries compressed blob locators for each chunk.
|
||||
REQUIRE(meta->meta.chunks.size() == sd.chunks.size());
|
||||
REQUIRE(meta->meta.chunks[0].v_comp_size > 0);
|
||||
// The deferred (property) block locator is recorded for on-demand fetch.
|
||||
REQUIRE(meta->deferred_comp_size > 0);
|
||||
// The element metadata block locator is recorded for on-demand fetch.
|
||||
REQUIRE(meta->element_metadata_comp_size > 0);
|
||||
|
||||
// Metadata round-trips.
|
||||
REQUIRE(meta->meta.meshes.size() == sd.meshes.size());
|
||||
@@ -197,37 +196,37 @@ TEST_CASE("parseSidecarHead validates magic / version, reads geom length", "[str
|
||||
REQUIRE_FALSE(parseSidecarHead(bad, sizeof(bad), got));
|
||||
}
|
||||
|
||||
TEST_CASE("v16 deferred block: fetch via locator, decompress, parse", "[streaming]") {
|
||||
fs::path dir = makeScratchDir("v16def");
|
||||
TEST_CASE("v16 element metadata block: fetch via locator, decompress, parse", "[streaming]") {
|
||||
fs::path dir = makeScratchDir("v16element");
|
||||
fs::path ifc = dir / "model.ifc";
|
||||
SidecarData sd = buildFixture();
|
||||
REQUIRE(writeSidecar(ifc.string(), sd));
|
||||
|
||||
auto meta = readSidecarMetadataOnly(ifc.string());
|
||||
REQUIRE(meta.has_value());
|
||||
REQUIRE(meta->meta.meshes.size() == sd.meshes.size()); // critical
|
||||
REQUIRE(meta->meta.meshes.size() == sd.meshes.size()); // geometry metadata
|
||||
REQUIRE(meta->meta.chunks.size() == sd.chunks.size());
|
||||
REQUIRE(meta->meta.elements.size() == sd.elements.size()); // desktop reads deferred too
|
||||
REQUIRE(meta->deferred_comp_size > 0);
|
||||
REQUIRE(meta->meta.elements.size() == sd.elements.size()); // desktop reads element metadata too
|
||||
REQUIRE(meta->element_metadata_comp_size > 0);
|
||||
|
||||
// The on-demand path (web) fetches the compressed deferred frame via the
|
||||
// The on-demand path (web) fetches the compressed element metadata frame via the
|
||||
// recorded locator and decompresses it — verify that round-trips.
|
||||
FILE* f = std::fopen((dir / "model.ifcview").string().c_str(), "rb");
|
||||
REQUIRE(f);
|
||||
std::vector<uint8_t> cz(size_t(meta->deferred_comp_size));
|
||||
std::fseek(f, long(meta->deferred_comp_offset), SEEK_SET);
|
||||
std::vector<uint8_t> cz(size_t(meta->element_metadata_comp_size));
|
||||
std::fseek(f, long(meta->element_metadata_comp_offset), SEEK_SET);
|
||||
REQUIRE(std::fread(cz.data(), 1, cz.size(), f) == cz.size());
|
||||
std::fclose(f);
|
||||
|
||||
std::vector<uint8_t> raw(size_t(meta->deferred_raw_size));
|
||||
std::vector<uint8_t> raw(size_t(meta->element_metadata_raw_size));
|
||||
REQUIRE(SidecarCompress::decompress(cz.data(), cz.size(), raw.data(), raw.size()));
|
||||
SidecarData d;
|
||||
REQUIRE(parseSidecarDeferred(raw.data(), raw.size(), d));
|
||||
REQUIRE(parseSidecarElementMetadata(raw.data(), raw.size(), d));
|
||||
REQUIRE(d.elements.size() == sd.elements.size());
|
||||
REQUIRE(d.string_table == sd.string_table);
|
||||
|
||||
SidecarData chopped;
|
||||
REQUIRE_FALSE(parseSidecarDeferred(raw.data(), raw.size() - 1, chopped));
|
||||
REQUIRE_FALSE(parseSidecarElementMetadata(raw.data(), raw.size() - 1, chopped));
|
||||
}
|
||||
|
||||
TEST_CASE("planSidecarReadRanges coalesces adjacent ranges, keeps far ones split",
|
||||
|
||||
Reference in New Issue
Block a user