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
+27 -21
View File
@@ -123,9 +123,15 @@ public:
// A point that actually lies on the model's first instance — used
// by the federation false-origin guess on first geometry. Pure
// read of models_gpu_; no GPU touch.
bool firstGeometryPointWorldM(uint32_t model_id,
bool firstGeometryPointWorldM(uint32_t session_model_id,
Eigen::Vector3d& out) const;
// The global-id base applyCachedModel added to this model's instance
// object_ids. Callers that hold the element table separately (the desktop
// sidecar path) rebase their element records by the same base so registry
// ids match the ids pick/selection return. 0 if the model is unknown.
uint32_t modelObjectIdBase(uint32_t session_model_id) const;
// ---- Scene mutators -----------------------------------------------------
//
// All of these flip scene state (or post a recompose) and ask the
@@ -133,26 +139,26 @@ public:
// is responsible for coalescing those requests (Qt's requestUpdate
// does it natively; the web host wraps requestAnimationFrame).
void removeModel(uint32_t model_id);
void removeModel(uint32_t session_model_id);
void resetScene();
void hideModel(uint32_t model_id);
void showModel(uint32_t model_id);
void hideModel(uint32_t session_model_id);
void showModel(uint32_t session_model_id);
// Federation matrix setters. Each writes to model state and posts
// a recompose so per-instance world matrices stay consistent with
// the configured georef + transformation pipeline.
void setFederatedFalseOrigin(const Eigen::Matrix4d& matrix_meters);
void setModelCoordinateOperation(uint32_t model_id,
void setModelCoordinateOperation(uint32_t session_model_id,
const Eigen::Matrix4d& matrix_meters);
void setModelTransformation(uint32_t model_id,
void setModelTransformation(uint32_t session_model_id,
const Eigen::Matrix4d& matrix_meters);
// Walk every instance of `model_id`, recompose its transform from
// Walk every instance of `session_model_id`, recompose its transform from
// the current federation matrices, refresh per-chunk world AABBs,
// and re-upload InstanceGpu[] into m.instance_storage. No-op if
// the model is unknown, has no instances, or wgpu init hasn't
// completed.
void recomposeAndUploadModel(uint32_t model_id);
void recomposeAndUploadModel(uint32_t session_model_id);
// ---- Camera math --------------------------------------------------------
//
@@ -384,7 +390,7 @@ public:
// sidecar offsets. Pure function of model + chunk metadata; safe to
// call from the main thread.
static StreamingThread::Request makeChunkRequest(
const ModelGpuData& m, std::size_t chunk_idx, std::uint32_t model_id);
const ModelGpuData& m, std::size_t chunk_idx, std::uint32_t session_model_id);
// Per-frame streaming driver. Called from render() after cull. Walks
// every model's chunks once for residency bookkeeping, drains the
@@ -397,20 +403,20 @@ public:
// ---- Sidecar / direct load (#84-q) -----------------------------------
//
// Apply a parsed sidecar's metadata + planned chunk layout to
// models_gpu_[model_id]. Builds the per-chunk small buffers
// models_gpu_[session_model_id]. Builds the per-chunk small buffers
// (visible_draws / prefix_sums / per_chunk_uniform), the per-model
// mesh + instance storage SSBOs, and the spatial chunk plan; chunk
// vertex/index slices stay non-resident until the streaming loader
// brings them in. Triggers an auto-viewAll on the first model (so a
// freshly-loaded scene frames itself).
void applyCachedModel(std::uint32_t model_id, StreamingSidecar metadata);
void applyCachedModel(std::uint32_t session_model_id, StreamingSidecar metadata);
// Qt-free sidecar load: readSidecarMetadataOnly + applyCachedModel.
// Qt-free sidecar load: readSidecarMetadata + applyCachedModel.
// Used by the web build (and any other non-Qt embedder) so the
// public ViewportWindow::loadSidecar's QString + QFile triage
// tilde-expansion doesn't have to be replicated. Returns 0 on
// any failure (device not ready, file missing, magic / version
// mismatch) and the freshly-assigned model_id on success.
// mismatch) and the freshly-assigned session_model_id on success.
std::uint32_t loadSidecarFromPath(const std::string& path);
#if defined(__EMSCRIPTEN__)
@@ -431,7 +437,7 @@ public:
// / 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.
void loadElementMetadataWeb(std::uint32_t model_id,
void loadElementMetadataWeb(std::uint32_t session_model_id,
std::function<void(bool)> done = {});
// Demo consumer of the element metadata fetch: on pick, ensure the owning model's
@@ -444,7 +450,7 @@ public:
// the active web source). applyStreamedChunk runs in the JS completion
// callback; c.is_loading is held until then. No-op if the model/chunk
// vanished mid-flight (e.g. a resetScene landed between issue and done).
void beginWebChunkLoad(std::uint32_t model_id, std::size_t chunk_idx);
void beginWebChunkLoad(std::uint32_t session_model_id, std::size_t chunk_idx);
#endif
// Streaming progress for a loading UI: resident vs total streaming chunks
@@ -454,7 +460,7 @@ public:
// Per-model progress for a federation loading UI. count() is how many
// models have metadata (are in the scene); progress(idx,…) gives the
// idx-th model's resident/total chunks, ordered by model_id (= load order)
// idx-th model's resident/total chunks, ordered by session_model_id (= load order)
// so each model keeps a stable UI slot as it streams.
int streamingModelCount() const;
void streamingModelProgress(int idx, int& resident_chunks,
@@ -474,7 +480,7 @@ public:
// ViewportCore so both halves can share it.
void uploadStreamedMesh(const StreamedMesh& mesh);
void uploadStreamedInstance(const StreamedInstance& instance_record);
void finalizeModel(std::uint32_t model_id);
void finalizeModel(std::uint32_t session_model_id);
// ---- Cross-chunk + screenshot capture (#84-v) -------------------------
//
@@ -774,7 +780,7 @@ public:
// round-trip from mesh-local back to world without re-deriving it.
struct MeshLocalPick {
std::uint32_t object_id = 0;
std::uint32_t model_id = 0;
std::uint32_t session_model_id = 0;
std::uint32_t mesh_id = 0;
float mesh_local [3] = {0, 0, 0};
float world_pos [3] = {0, 0, 0};
@@ -1082,9 +1088,9 @@ private:
// on subsequent frames.
StreamingThread streaming_thread_;
// Per-model GPU + CPU state, keyed by viewport-assigned model_id.
// Per-model GPU + CPU state, keyed by viewport-assigned session_model_id.
std::unordered_map<uint32_t, ModelGpuData> models_gpu_;
uint32_t next_model_id_ = 1;
uint32_t next_session_model_id_ = 1;
// Globally-unique object_id allocator. Each applyCachedModel rebases
// the sidecar's local object_ids by base_object_id_so_far so picks
// are unambiguous across models.
@@ -1163,7 +1169,7 @@ private:
std::string pending_screenshot_path_;
// Bonsai direct-load staging map. uploadStreamedMesh +
// uploadStreamedInstance append into entries keyed by model_id; the
// uploadStreamedInstance append into entries keyed by session_model_id; the
// finalizeModel call moves the entry out, hands it to
// applyCachedModel, and uploads the chunk slices synchronously.
std::unordered_map<std::uint32_t, std::unique_ptr<SidecarData>>