ifcviewer: cache CoordinateOperation in sidecar (v10 -> v11)

Previously, applyCoordinateOperationToViewport — which pushes both
CoordinateOperation and ModelTransformation — was only called on
paths that required the IFC source to be loaded
(onLoadedFromStream and onDataSourceReady).  Sidecar-only loads
(loadDataSource off, or no .ifc/.rdb sibling) silently lost both
stages.

Cache the per-model georef + unit scales in the sidecar itself so
the IFC source isn't needed to apply them:

  SidecarData gains
    coordinate_operation_meters[16]  // column-major
    project_length_to_meters
    map_unit_to_meters
    has_coordinate_operation

  148 B fixed block written/read between instances and elements.
  SIDECAR_VERSION 10 -> 11; existing sidecars rebuild on next load.

  MainWindow::writeSidecarForModel populates the block from
  loader_->modelGeoref(mid) before writeSidecar.

  SceneLoader::applySidecarData restores it into the model's
  ModelGeoref + sets has_georef = true, so subsequent
  loader_->modelGeoref(mid) calls return the cached data without
  needing the IFC.

  MainWindow::onLoadedFromSidecar now calls
  applyCoordinateOperationToViewport(mid) directly — both
  CoordinateOperation and ModelTransformation land at sidecar-load
  time, no longer waiting on a possibly-never-arriving data source.

Edits to the IFC's IfcMapConversion don't invalidate the cache —
delete the .ifcview manually if the source's georef changes.  This
matches the existing cache-invalidation contract.

Tests: round-trip the new fields through the existing sidecar
fixture; assert SIDECAR_VERSION == 11.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-05-02 19:08:45 +10:00
parent e0e143bab5
commit 8ffdb8f0b9
5 changed files with 99 additions and 6 deletions
+20 -2
View File
@@ -88,7 +88,10 @@ SidecarData buildFixture() {
inst.object_id = uint32_t(100 + i);
inst.color_override_rgba8 = uint32_t(0xAA000000u | (i * 0x010203u));
inst.model_id = 1;
for (int k = 0; k < 16; ++k) inst.transform[k] = float(i) * 0.5f + float(k);
for (int k = 0; k < 16; ++k) {
inst.placement_transformation[k] = float(i) * 0.25f + float(k);
inst.transform[k] = float(i) * 0.5f + float(k);
}
inst.world_aabb_min[0] = float(i);
inst.world_aabb_min[1] = float(i + 1);
inst.world_aabb_min[2] = float(i + 2);
@@ -97,6 +100,12 @@ SidecarData buildFixture() {
inst.world_aabb_max[2] = float(i + 2) + 10.0f;
}
// Non-default georef block.
sd.has_coordinate_operation = 1;
for (int k = 0; k < 16; ++k) sd.coordinate_operation_meters[k] = 0.5 + 0.1 * k;
sd.project_length_to_meters = 0.001; // mm project
sd.map_unit_to_meters = 1.0; // metres map
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) {
@@ -129,6 +138,15 @@ bool sidecarDataEqual(const SidecarData& a, const SidecarData& b) {
for (size_t i = 0; i < a.elements.size(); ++i) {
if (std::memcmp(&a.elements[i], &b.elements[i], sizeof(PackedElementInfo)) != 0) return false;
}
// v11 georef block.
if (a.has_coordinate_operation != b.has_coordinate_operation) return false;
if (a.project_length_to_meters != b.project_length_to_meters) return false;
if (a.map_unit_to_meters != b.map_unit_to_meters) return false;
for (int i = 0; i < 16; ++i) {
if (a.coordinate_operation_meters[i] != b.coordinate_operation_meters[i])
return false;
}
return true;
}
@@ -137,7 +155,7 @@ bool sidecarDataEqual(const SidecarData& a, const SidecarData& b) {
TEST_CASE("MeshInfo and InstanceCpu have stable layouts (sidecar wire format)", "[sidecar]") {
REQUIRE(sizeof(MeshInfo) == 56);
REQUIRE(sizeof(InstanceGpu) == 80);
REQUIRE(SIDECAR_VERSION == 9);
REQUIRE(SIDECAR_VERSION == 11);
REQUIRE(SIDECAR_MAGIC == 0x49465657u);
}