ifcviewer: overhaul model/object ID tracking

Rename the two overloaded model identifiers and make object_id
assignment single-authority, fixing a pick -> properties mismatch.

Identifiers:
- Per-model UUID fed_id -> model_id; the uint32 runtime handle
  model_id -> session_model_id (SessionState accessors + mirror hashes
  renamed to match). "fed_id" was a misnomer -- the federation is the
  whole collection, not one model.

object_id assignment (fixes wrong class on click):
- Producers (GeometryStreamer, .ifcview sidecar) now stamp model-LOCAL
  object_ids; ViewportCore::applyCachedModel is the sole authority that
  assigns the session-global id (base + local). Removed
  SceneLoader::next_object_id_, GeometryStreamer::lastObjectId(), and the
  streamer's start_object_id parameter.
- The element table is stamped by the same base on both load paths
  (applySidecarData and onStreamerFinished), so registry ids match the
  ids pick returns. Previously the sidecar path double-rebased instances
  vs the registry (click IfcSite -> showed IfcDoor); the live-stream path
  had the same latent mismatch. Both closed.

Naming / cleanup:
- SceneLoader::addFiles -> queueModels; startStreamLoadFor ->
  loadFromGeometryStreamer; readSidecarMetadataOnly -> readSidecarMetadata.
- Federation::addModel takes an explicit display_name (no QFileInfo
  fallback); callers pass QFileInfo(path).fileName().
- Disambiguate cryptic short locals (d->sidecar, m->model, c->chunk, ...)
  in SceneLoader, Federation, ViewportWindow, AreaMeasurement,
  SectionGizmoRenderer, and the SidecarData/SidecarReadPlan spots in
  ViewportCore.

Tests: 125/125 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-07-08 14:10:05 +10:00
parent b441fada90
commit 75c9da5098
49 changed files with 914 additions and 885 deletions
+15 -15
View File
@@ -71,7 +71,7 @@ double volumeOfObjects(ViewportWindow& vp,
const std::vector<uint32_t>& object_ids) {
if (object_ids.empty()) return 0.0;
// Group selected instances by (model_id, mesh_id) so each unique mesh
// Group selected instances by (session_model_id, mesh_id) so each unique mesh
// is read back at most once per call. Each entry stores the |det| of
// every instance of that mesh in the request.
std::unordered_map<uint64_t, std::vector<double>> by_mesh;
@@ -79,16 +79,16 @@ double volumeOfObjects(ViewportWindow& vp,
for (uint32_t oid : object_ids) {
ViewportWindow::InstanceLookup lk;
if (!vp.findInstance(oid, lk)) continue;
const uint64_t key = (uint64_t(lk.model_id) << 32) | lk.mesh_id;
const uint64_t key = (uint64_t(lk.session_model_id) << 32) | lk.mesh_id;
by_mesh[key].push_back(std::abs(det3(lk.placement_transformation)));
}
double total = 0.0;
ViewportWindow::MeshTriangles tris;
for (const auto& [key, dets] : by_mesh) {
const uint32_t model_id = uint32_t(key >> 32);
const uint32_t session_model_id = uint32_t(key >> 32);
const uint32_t mesh_id = uint32_t(key & 0xffffffffu);
if (!vp.readbackMeshTriangles(model_id, mesh_id, tris)) continue;
if (!vp.readbackMeshTriangles(session_model_id, mesh_id, tris)) continue;
const double v = meshLocalVolume(tris);
for (double d : dets) total += v * d;
}
@@ -102,7 +102,7 @@ volumesPerObject(ViewportWindow& vp,
if (object_ids.empty()) return out;
out.reserve(object_ids.size());
// Cache the local-frame volume per unique (model_id, mesh_id) so each
// Cache the local-frame volume per unique (session_model_id, mesh_id) so each
// mesh is read back at most once even when many instances share it
// (common for repeated families like windows / columns).
std::unordered_map<uint64_t, double> mesh_vol_local;
@@ -113,11 +113,11 @@ volumesPerObject(ViewportWindow& vp,
ViewportWindow::InstanceLookup lk;
if (!vp.findInstance(oid, lk)) continue;
const uint64_t key = (uint64_t(lk.model_id) << 32) | lk.mesh_id;
const uint64_t key = (uint64_t(lk.session_model_id) << 32) | lk.mesh_id;
auto it = mesh_vol_local.find(key);
double v_local = 0.0;
if (it == mesh_vol_local.end()) {
if (vp.readbackMeshTriangles(lk.model_id, lk.mesh_id, tris)) {
if (vp.readbackMeshTriangles(lk.session_model_id, lk.mesh_id, tris)) {
v_local = meshLocalVolume(tris);
}
mesh_vol_local.emplace(key, v_local);
@@ -252,7 +252,7 @@ void AreaMeasurement::rebuildHighlight(ViewportWindow& vp) {
std::vector<float> world_xyz;
world_xyz.reserve(selected_.size() * 9);
for (const auto& [key, sel] : selected_) {
const uint64_t cache_key = (uint64_t(sel.model_id) << 32)
const uint64_t cache_key = (uint64_t(sel.session_model_id) << 32)
| uint64_t(sel.mesh_id);
auto cit = mesh_cache_.find(cache_key);
if (cit == mesh_cache_.end()) continue;
@@ -292,7 +292,7 @@ void AreaMeasurement::rebuildHighlight(ViewportWindow& vp) {
if (sels.empty()) continue;
// All tris belonging to one object share its mesh + transform.
const SelectedTri& any = *sels[0];
const uint64_t cache_key = (uint64_t(any.model_id) << 32)
const uint64_t cache_key = (uint64_t(any.session_model_id) << 32)
| uint64_t(any.mesh_id);
auto cit = mesh_cache_.find(cache_key);
if (cit == mesh_cache_.end()) continue;
@@ -362,14 +362,14 @@ void AreaMeasurement::rebuildHighlight(ViewportWindow& vp) {
}
AreaMeasurement::MeshCache* AreaMeasurement::meshCache(ViewportWindow& vp,
uint32_t model_id,
uint32_t session_model_id,
uint32_t mesh_id) {
const uint64_t key = (uint64_t(model_id) << 32) | uint64_t(mesh_id);
const uint64_t key = (uint64_t(session_model_id) << 32) | uint64_t(mesh_id);
auto it = mesh_cache_.find(key);
if (it != mesh_cache_.end()) return &it->second;
ViewportWindow::MeshTriangles tris;
if (!vp.readbackMeshTriangles(model_id, mesh_id, tris)) return nullptr;
if (!vp.readbackMeshTriangles(session_model_id, mesh_id, tris)) return nullptr;
MeshCache c;
c.positions = std::move(tris.positions);
@@ -401,7 +401,7 @@ void AreaMeasurement::onPick(ViewportWindow& vp, int x, int y, bool alt) {
ViewportWindow::MeshLocalPick pick;
if (!vp.pickMeshLocalAt(x, y, pick)) return;
MeshCache* cache = meshCache(vp, pick.model_id, pick.mesh_id);
MeshCache* cache = meshCache(vp, pick.session_model_id, pick.mesh_id);
if (!cache) return;
const size_t n_tris = cache->indices.size() / 3;
if (n_tris == 0) return;
@@ -471,7 +471,7 @@ void AreaMeasurement::onPick(ViewportWindow& vp, int x, int y, bool alt) {
}
} else {
SelectedTri sel;
sel.model_id = pick.model_id;
sel.session_model_id = pick.session_model_id;
sel.mesh_id = pick.mesh_id;
sel.tri = t;
std::memcpy(sel.composed_transform, pick.composed_transform,
@@ -876,7 +876,7 @@ void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) {
ViewportWindow::MeshTriangles tris;
bool have_extent = false;
double min_t1 = 0.0, max_t1 = 0.0, min_t2 = 0.0, max_t2 = 0.0;
if (vp.readbackMeshTriangles(first_pick_.model_id, first_pick_.mesh_id, tris)) {
if (vp.readbackMeshTriangles(first_pick_.session_model_id, first_pick_.mesh_id, tris)) {
const size_t n_verts = tris.positions.size() / 3;
const size_t n_tris = tris.indices.size() / 3;
if (n_tris > 0) {