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
+50 -49
View File
@@ -166,31 +166,31 @@ void removeGroup(SessionState& session, QWidget& host, const QString& group_id)
session.setStatusMessage("Models", "Group removed");
}
void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host, const QString& fed_id) {
const Federation::Model* model = session.federation()->findById(fed_id);
const QString label = model ? model->display_name : fed_id;
void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host, const QString& model_id) {
const Federation::Model* model = session.federation()->findById(model_id);
const QString label = model ? model->display_name : model_id;
const auto choice = QMessageBox::question(
&host, "Remove Model",
QString("Remove model '%1' from the federation?").arg(label),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (choice != QMessageBox::Yes) return;
const uint32_t model_id = session.modelIdForFedId(fed_id);
if (model_id == 0) {
session.federation()->removeModel(fed_id);
const uint32_t session_model_id = session.sessionModelIdForModelId(model_id);
if (session_model_id == 0) {
session.federation()->removeModel(model_id);
session.notifyFederationChanged();
session.setStatusMessage("Models", "Model removed");
return;
}
if (session.loader()->isLoadingModel(model_id)) return;
if (session.loader()->isLoadingModel(session_model_id)) return;
viewport.setSelectedObjectId(0);
session.setSelectedObjectId(0);
session.federation()->removeModel(fed_id);
viewport.removeModel(model_id);
session.loader()->removeModel(model_id);
session.elementRegistry()->removeModel(model_id);
session.removeModelMappingByFedId(fed_id);
session.federation()->removeModel(model_id);
viewport.removeModel(session_model_id);
session.loader()->removeModel(session_model_id);
session.elementRegistry()->removeModel(session_model_id);
session.removeModelMappingByModelId(model_id);
session.notifySelectionChanged();
session.notifyModelsChanged();
session.setStatusMessage("Models", "Model removed");
@@ -198,12 +198,12 @@ void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host,
namespace detail {
void loadModels(SessionState& session, const QStringList& paths, const QStringList& fed_ids) {
void loadModels(SessionState& session, const QStringList& paths, const QStringList& model_ids) {
if (paths.isEmpty()) return;
const auto model_ids = session.loader()->addFiles(paths);
for (int i = 0; i < paths.size() && i < static_cast<int>(model_ids.size()) && i < fed_ids.size(); ++i) {
session.setModelMapping(fed_ids[i], model_ids[i]);
const auto session_model_ids = session.loader()->queueModels(paths);
for (int i = 0; i < paths.size() && i < static_cast<int>(session_model_ids.size()) && i < model_ids.size(); ++i) {
session.setModelMapping(model_ids[i], session_model_ids[i]);
}
}
@@ -269,20 +269,21 @@ void addModel(SessionState& session, QWidget& host) {
// models yet — the first model that finishes loading will set the
// origin via ViewportView. Checked here (before federation->addModel)
// because federation->addModel doesn't yet populate SessionState's
// model mapping; modelIds() reflects pre-add state at this point.
if (session.modelIds().isEmpty()) {
// model mapping; sessionModelIds() reflects pre-add state at this point.
if (session.sessionModelIds().isEmpty()) {
armFederatedFalseOriginGuess();
}
QStringList accepted_paths;
QStringList accepted_fed_ids;
QStringList accepted_model_ids;
for (const auto& path : paths) {
const QString fed_id = session.federation()->addModel(path);
if (fed_id.isEmpty()) continue;
const QString model_id =
session.federation()->addModel(path, QFileInfo(path).fileName());
if (model_id.isEmpty()) continue;
accepted_paths << path;
accepted_fed_ids << fed_id;
accepted_model_ids << model_id;
}
detail::loadModels(session, accepted_paths, accepted_fed_ids);
detail::loadModels(session, accepted_paths, accepted_model_ids);
session.notifyModelsChanged();
}
@@ -317,15 +318,15 @@ void addModelFromCloud(SessionState& session, QWidget& host) {
proc->call("pull_models_interactive", QJsonValue(),
[sguard, connector_id](const QJsonValue& result) {
if (!sguard) return;
// Arm before the first addCloudModel — modelIds() reflects the
// Arm before the first addCloudModel — sessionModelIds() reflects the
// session state at the moment the connector returns, which is
// when the user's "add into empty session" intent applies.
if (sguard->modelIds().isEmpty()) {
if (sguard->sessionModelIds().isEmpty()) {
armFederatedFalseOriginGuess();
}
const QJsonArray arr = result.toArray();
QStringList paths;
QStringList fed_ids;
QStringList model_ids;
int added = 0;
for (const QJsonValue& value : arr) {
if (value.isNull() || !value.isObject()) continue;
@@ -337,19 +338,19 @@ void addModelFromCloud(SessionState& session, QWidget& host) {
QString src_connector = source.value("connector").toString();
if (src_connector.isEmpty()) src_connector = connector_id;
const QString fed_id = sguard->federation()->addCloudModel(
const QString model_id = sguard->federation()->addCloudModel(
display_name, src_connector, source);
if (fed_id.isEmpty()) continue;
if (model_id.isEmpty()) continue;
const QJsonObject meta = entry.value("metadata").toObject();
sguard->setCloudMetadata(fed_id, meta.toVariantMap());
sguard->setCloudMetadata(model_id, meta.toVariantMap());
paths << path;
fed_ids << fed_id;
model_ids << model_id;
++added;
}
if (!paths.isEmpty()) {
detail::loadModels(*sguard, paths, fed_ids);
detail::loadModels(*sguard, paths, model_ids);
sguard->notifyModelsChanged();
}
sguard->setStatusMessage("Cloud",
@@ -368,28 +369,28 @@ void addModelFromCloud(SessionState& session, QWidget& host) {
namespace {
// Shared "local path on disk" lookup for the right-click cloud commands:
// the loader keeps the path keyed by model_id (set when a file or pull_models
// the loader keeps the path keyed by session_model_id (set when a file or pull_models
// path was queued). Both local-sourced and resolved cloud-sourced models
// have one; only un-resolved cloud models (where pull_models hasn't
// returned yet) won't.
QString localPathForModel(SessionState& session, const QString& fed_id) {
const uint32_t model_id = session.modelIdForFedId(fed_id);
if (model_id == 0 || !session.loader()) return {};
return session.loader()->filePath(model_id);
QString localPathForModel(SessionState& session, const QString& model_id) {
const uint32_t session_model_id = session.sessionModelIdForModelId(model_id);
if (session_model_id == 0 || !session.loader()) return {};
return session.loader()->filePath(session_model_id);
}
} // namespace
void saveModelToCloud(SessionState& session, QWidget& host, const QString& fed_id) {
void saveModelToCloud(SessionState& session, QWidget& host, const QString& model_id) {
auto* federation = session.federation();
const Federation::Model* model = federation->findById(fed_id);
const Federation::Model* model = federation->findById(model_id);
if (!model) return;
if (model->source_connector == "local") {
QMessageBox::information(&host, "Save Model To Cloud",
"This model has no cloud target. Use \"Save As To Cloud\" first.");
return;
}
const QString local_path = localPathForModel(session, fed_id);
const QString local_path = localPathForModel(session, model_id);
if (local_path.isEmpty()) {
QMessageBox::warning(&host, "Save Model To Cloud",
"Cannot find a local copy of this model to push.");
@@ -416,15 +417,15 @@ void saveModelToCloud(SessionState& session, QWidget& host, const QString& fed_i
QPointer<SessionState> sguard(&session);
proc->call("push_model", params,
[sguard, fed_id, connector_id](const QJsonValue& result) {
[sguard, model_id, connector_id](const QJsonValue& result) {
if (!sguard) return;
const QJsonObject obj = result.toObject();
const QJsonObject new_source = obj.value("source").toObject();
QString new_connector = new_source.value("connector").toString();
if (new_connector.isEmpty()) new_connector = connector_id;
sguard->federation()->setModelSource(fed_id, new_connector, new_source);
sguard->federation()->setModelSource(model_id, new_connector, new_source);
const QJsonObject meta = obj.value("metadata").toObject();
sguard->setCloudMetadata(fed_id, meta.toVariantMap());
sguard->setCloudMetadata(model_id, meta.toVariantMap());
sguard->setStatusMessage("Cloud",
QString("Saved to %1").arg(new_connector));
},
@@ -438,10 +439,10 @@ void saveModelToCloud(SessionState& session, QWidget& host, const QString& fed_i
});
}
void saveModelAsToCloud(SessionState& session, QWidget& host, const QString& fed_id) {
const Federation::Model* model = session.federation()->findById(fed_id);
void saveModelAsToCloud(SessionState& session, QWidget& host, const QString& model_id) {
const Federation::Model* model = session.federation()->findById(model_id);
if (!model) return;
const QString local_path = localPathForModel(session, fed_id);
const QString local_path = localPathForModel(session, model_id);
if (local_path.isEmpty()) {
QMessageBox::warning(&host, "Save Model As To Cloud",
"Cannot find a local copy of this model to push.");
@@ -480,20 +481,20 @@ void saveModelAsToCloud(SessionState& session, QWidget& host, const QString& fed
QPointer<SessionState> sguard(&session);
proc->call("push_model_interactive", params,
[sguard, fed_id, connector_id](const QJsonValue& result) {
[sguard, model_id, connector_id](const QJsonValue& result) {
if (!sguard) return;
const QJsonObject obj = result.toObject();
const QJsonObject new_source = obj.value("source").toObject();
QString new_connector = new_source.value("connector").toString();
if (new_connector.isEmpty()) new_connector = connector_id;
sguard->federation()->setModelSource(fed_id, new_connector, new_source);
sguard->federation()->setModelSource(model_id, new_connector, new_source);
const QString new_name = obj.value("display_name").toString();
if (!new_name.isEmpty()) {
sguard->federation()->setModelDisplayName(fed_id, new_name);
sguard->federation()->setModelDisplayName(model_id, new_name);
}
const QJsonObject meta = obj.value("metadata").toObject();
sguard->setCloudMetadata(fed_id, meta.toVariantMap());
sguard->setCloudMetadata(model_id, meta.toVariantMap());
sguard->setStatusMessage("Cloud",
QString("Pushed to %1").arg(new_connector));
},
+4 -4
View File
@@ -58,7 +58,7 @@ void renameGroup(SessionState& session, QWidget& host, const QString& group_id);
void moveGroup(SessionState& session, const QString& id, const QString& parent_group_id);
void moveModels(SessionState& session, const QStringList& ids, const QString& parent_group_id);
void removeGroup(SessionState& session, QWidget& host, const QString& group_id);
void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host, const QString& fed_id);
void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host, const QString& model_id);
void addModel(SessionState& session, QWidget& host);
// Connector picker → pull_models_interactive → addCloudModel + load.
// Reachable from AddModelDialog's CloudModel button; the underlying call
@@ -66,10 +66,10 @@ void addModel(SessionState& session, QWidget& host);
void addModelFromCloud(SessionState& session, QWidget& host);
// push_model: push a cloud-sourced model back to its existing target.
// Only valid when model.source_connector != "local". Async.
void saveModelToCloud(SessionState& session, QWidget& host, const QString& fed_id);
void saveModelToCloud(SessionState& session, QWidget& host, const QString& model_id);
// push_model_interactive: pick a connector and push to a fresh cloud
// target. Valid for any model (local or already cloud-sourced). Async.
void saveModelAsToCloud(SessionState& session, QWidget& host, const QString& fed_id);
void saveModelAsToCloud(SessionState& session, QWidget& host, const QString& model_id);
void convertIfcToDatabase(SessionState& session, QWidget& host);
void exportGeometryDatabase(SessionState& session, QWidget& host);
void openSettings(SessionState& session, QWidget& host);
@@ -80,7 +80,7 @@ void openSettings(SessionState& session, QWidget& host);
namespace detail {
// Queues already-federated models on the loader and maps their federation-ids to mids.
void loadModels(SessionState& session, const QStringList& paths, const QStringList& fed_ids);
void loadModels(SessionState& session, const QStringList& paths, const QStringList& model_ids);
} // namespace detail
@@ -87,9 +87,9 @@ QStandardItem* FederationItemModel::makeGroupNameItem(const QString& group_id, c
return item;
}
QStandardItem* FederationItemModel::makeModelNameItem(const QString& fed_id, const QString& display_name) const {
QStandardItem* FederationItemModel::makeModelNameItem(const QString& model_id, const QString& display_name) const {
auto* item = new QStandardItem(components::icons::makeSvgIcon(":/icons/cube.svg"), display_name);
item->setData(fed_id, IdRole);
item->setData(model_id, IdRole);
item->setData(int(ItemKind::Model), KindRole);
item->setEditable(false);
return item;
@@ -129,14 +129,14 @@ QStandardItem* FederationItemModel::parentItemForGroup(const QString& parent_gro
return found ? found : invisibleRootItem();
}
void FederationItemModel::appendModelTo(QStandardItem* parent_item, const QString& fed_id) {
const Federation::Model* model = federation_->findById(fed_id);
void FederationItemModel::appendModelTo(QStandardItem* parent_item, const QString& model_id) {
const Federation::Model* model = federation_->findById(model_id);
if (!model) return;
auto* name_item = makeModelNameItem(fed_id, model->display_name);
auto* vis_item = makeVisibilityItem(ItemKind::Model, federation_->isModelEffectivelyVisible(fed_id));
auto* name_item = makeModelNameItem(model_id, model->display_name);
auto* vis_item = makeVisibilityItem(ItemKind::Model, federation_->isModelEffectivelyVisible(model_id));
parent_item->appendRow({name_item, vis_item});
id_to_name_item_.insert(fed_id, name_item);
styleRowVisibility(name_item, federation_->isModelEffectivelyVisible(fed_id));
id_to_name_item_.insert(model_id, name_item);
styleRowVisibility(name_item, federation_->isModelEffectivelyVisible(model_id));
}
void FederationItemModel::appendGroupSubtreeTo(QStandardItem* parent_item, const QString& group_id) {
@@ -228,38 +228,38 @@ void FederationItemModel::onGroupVisibilityChanged(const QString& group_id, bool
refreshSubtreeVisibility(item);
}
void FederationItemModel::onModelAdded(const QString& fed_id) {
const Federation::Model* model = federation_->findById(fed_id);
void FederationItemModel::onModelAdded(const QString& model_id) {
const Federation::Model* model = federation_->findById(model_id);
if (!model) return;
QStandardItem* parent_item = parentItemForGroup(model->group_id);
appendModelTo(parent_item, fed_id);
appendModelTo(parent_item, model_id);
}
void FederationItemModel::onModelRemoved(const QString& fed_id) {
QStandardItem* item = findItem(fed_id);
void FederationItemModel::onModelRemoved(const QString& model_id) {
QStandardItem* item = findItem(model_id);
if (!item) return;
id_to_name_item_.remove(fed_id);
id_to_name_item_.remove(model_id);
QStandardItem* parent_item = item->parent();
if (!parent_item) parent_item = invisibleRootItem();
parent_item->removeRow(item->row());
}
void FederationItemModel::onModelVisibilityChanged(const QString& fed_id, bool /*visible*/) {
QStandardItem* item = findItem(fed_id);
void FederationItemModel::onModelVisibilityChanged(const QString& model_id, bool /*visible*/) {
QStandardItem* item = findItem(model_id);
if (!item) return;
refreshSubtreeVisibility(item);
}
void FederationItemModel::onModelChanged(const QString& fed_id) {
QStandardItem* item = findItem(fed_id);
void FederationItemModel::onModelChanged(const QString& model_id) {
QStandardItem* item = findItem(model_id);
if (!item) return;
const Federation::Model* model = federation_->findById(fed_id);
const Federation::Model* model = federation_->findById(model_id);
if (!model) return;
item->setText(model->display_name);
}
void FederationItemModel::onModelGroupChanged(const QString& fed_id, const QString& new_group_id) {
QStandardItem* item = findItem(fed_id);
void FederationItemModel::onModelGroupChanged(const QString& model_id, const QString& new_group_id) {
QStandardItem* item = findItem(model_id);
if (!item) return;
QStandardItem* current_parent = item->parent();
if (!current_parent) current_parent = invisibleRootItem();
@@ -58,27 +58,27 @@ private slots:
void onGroupRemoved(const QString& group_id);
void onGroupChanged(const QString& group_id);
void onGroupVisibilityChanged(const QString& group_id, bool visible);
void onModelAdded(const QString& fed_id);
void onModelRemoved(const QString& fed_id);
void onModelVisibilityChanged(const QString& fed_id, bool visible);
void onModelGroupChanged(const QString& fed_id, const QString& new_group_id);
void onModelChanged(const QString& fed_id);
void onModelAdded(const QString& model_id);
void onModelRemoved(const QString& model_id);
void onModelVisibilityChanged(const QString& model_id, bool visible);
void onModelGroupChanged(const QString& model_id, const QString& new_group_id);
void onModelChanged(const QString& model_id);
private:
QStandardItem* makeGroupNameItem(const QString& group_id, const QString& display_name) const;
QStandardItem* makeModelNameItem(const QString& fed_id, const QString& display_name) const;
QStandardItem* makeModelNameItem(const QString& model_id, const QString& display_name) const;
QStandardItem* makeVisibilityItem(ItemKind kind, bool visible) const;
void styleRowVisibility(QStandardItem* name_item, bool visible) const;
QStandardItem* findItem(const QString& id) const;
QStandardItem* parentItemForGroup(const QString& parent_group_id) const;
void appendModelTo(QStandardItem* parent_item, const QString& fed_id);
void appendModelTo(QStandardItem* parent_item, const QString& model_id);
void appendGroupSubtreeTo(QStandardItem* parent_item, const QString& group_id);
void refreshSubtreeVisibility(QStandardItem* root);
Federation* federation_ = nullptr;
QHash<QString, QStandardItem*> id_to_name_item_; // both group_ids and fed_ids
QHash<QString, QStandardItem*> id_to_name_item_; // both group_ids and model_ids
};
} // namespace bonsaiviewer::modules::models
@@ -378,7 +378,7 @@ void SettingsDialog::populateModelTable() {
model_table_->setItem(row, 0, model_item);
ModelRowWidgets widgets;
widgets.fed_id = model.id;
widgets.model_id = model.id;
widgets.frame = new QComboBox(model_table_);
widgets.frame->addItem("Local", static_cast<int>(AFrame::ModelLocal));
@@ -448,7 +448,7 @@ void SettingsDialog::updateSelectedModelGeoref() {
return;
}
settings_view_->refresh(model_rows_[row].fed_id);
settings_view_->refresh(model_rows_[row].model_id);
}
void SettingsDialog::onAccepted() {
@@ -473,7 +473,7 @@ void SettingsDialog::onAccepted() {
transformation.b = parseVector3(row.to_point->text());
transformation.rxyz_deg = parseVector3(row.rotate->text());
transformation.pivot = parseVector3(row.pivot->text());
federation_->setModelTransformation(row.fed_id, transformation);
federation_->setModelTransformation(row.model_id, transformation);
}
if (session_state_) {
session_state_->notifyFederationChanged();
@@ -55,7 +55,7 @@ protected:
private:
struct ModelRowWidgets {
QString fed_id;
QString model_id;
QComboBox* frame = nullptr;
QTableWidgetItem* from_point = nullptr;
QTableWidgetItem* to_point = nullptr;
@@ -212,7 +212,7 @@ SettingsView::SettingsView(SettingsDialog* widget,
{
}
void SettingsView::refresh(const QString& fed_id) const {
void SettingsView::refresh(const QString& model_id) const {
if (!widget_) {
return;
}
@@ -228,18 +228,18 @@ void SettingsView::refresh(const QString& fed_id) const {
return;
}
const uint32_t model_id = session_state_->modelIdForFedId(fed_id);
if (model_id == 0) {
const uint32_t session_model_id = session_state_->sessionModelIdForModelId(model_id);
if (session_model_id == 0) {
widget_->renderSelectedModelGeoref(unknownState("Not loaded", "No live model"));
return;
}
if (auto* ifc_file = loader->ifcFile(model_id)) {
if (auto* ifc_file = loader->ifcFile(session_model_id)) {
widget_->renderSelectedModelGeoref(stateFromLiveFile(ifc_file));
return;
}
const ModelGeoref* georef = loader->modelGeoref(model_id);
const ModelGeoref* georef = loader->modelGeoref(session_model_id);
if (!georef) {
widget_->renderSelectedModelGeoref(unknownState("Not available yet", "No data source"));
return;
@@ -36,7 +36,7 @@ public:
explicit SettingsView(SettingsDialog* widget,
bonsaiviewer::SessionState* session_state);
void refresh(const QString& fed_id) const;
void refresh(const QString& model_id) const;
private:
SettingsDialog* widget_ = nullptr;
+27 -27
View File
@@ -56,9 +56,9 @@ namespace {
void clearScene(SessionState& session, ViewportWindow& viewport) {
viewport.setSelectedObjectId(0);
session.setSelectedObjectId(0);
for (uint32_t model_id : session.modelIds()) {
viewport.removeModel(model_id);
session.loader()->removeModel(model_id);
for (uint32_t session_model_id : session.sessionModelIds()) {
viewport.removeModel(session_model_id);
session.loader()->removeModel(session_model_id);
}
session.clearModelMappings();
session.elementRegistry()->clear();
@@ -81,7 +81,7 @@ bool confirmDiscardIfDirty(SessionState& session, QWidget& host) {
// Fire-and-forget async resolution of any non-local models in the
// federation. Groups by source_connector and issues one pull_models per
// group. For each returned entry:
// - if the fed_id already has a scene entry pointed at the same path,
// - if the model_id already has a scene entry pointed at the same path,
// just refresh cloud metadata (no reload, preserves view state);
// - if the path differs, tear down the stale scene entry and queue a
// fresh load (federation entry is preserved either way);
@@ -90,21 +90,21 @@ bool confirmDiscardIfDirty(SessionState& session, QWidget& host) {
// has already shown its own UI.
void resolveCloudModels(SessionState& session, ViewportWindow& viewport) {
auto* federation = session.federation();
QHash<QString, QStringList> connector_to_fed_ids;
QHash<QString, QStringList> connector_to_model_ids;
for (const auto& model : federation->models()) {
if (model.source_connector == "local") continue;
connector_to_fed_ids[model.source_connector].push_back(model.id);
connector_to_model_ids[model.source_connector].push_back(model.id);
}
if (connector_to_fed_ids.isEmpty()) return;
if (connector_to_model_ids.isEmpty()) return;
auto* registry = session.connectorRegistry();
QPointer<SessionState> sguard(&session);
QPointer<ViewportWindow> vguard(&viewport);
for (auto it = connector_to_fed_ids.constBegin();
it != connector_to_fed_ids.constEnd(); ++it) {
for (auto it = connector_to_model_ids.constBegin();
it != connector_to_model_ids.constEnd(); ++it) {
const QString connector_id = it.key();
const QStringList fed_ids = it.value();
const QStringList model_ids = it.value();
auto* proc = registry->get(connector_id);
if (!proc) {
@@ -114,8 +114,8 @@ void resolveCloudModels(SessionState& session, ViewportWindow& viewport) {
}
QJsonArray params;
for (const QString& fed_id : fed_ids) {
const Federation::Model* model = federation->findById(fed_id);
for (const QString& model_id : model_ids) {
const Federation::Model* model = federation->findById(model_id);
if (!model) continue;
QJsonObject source = model->source_data;
source["connector"] = model->source_connector;
@@ -127,25 +127,25 @@ void resolveCloudModels(SessionState& session, ViewportWindow& viewport) {
}
proc->call("pull_models", params,
[sguard, vguard, fed_ids](const QJsonValue& result) {
[sguard, vguard, model_ids](const QJsonValue& result) {
if (!sguard) return;
const QJsonArray arr = result.toArray();
QStringList paths_to_load;
QStringList fed_ids_to_load;
QStringList model_ids_to_load;
bool any_detached = false;
for (int i = 0; i < arr.size() && i < fed_ids.size(); ++i) {
for (int i = 0; i < arr.size() && i < model_ids.size(); ++i) {
if (arr[i].isNull()) continue;
const QJsonObject obj = arr[i].toObject();
const QString new_path = obj.value("path").toString();
if (new_path.isEmpty()) continue;
const QString fed_id = fed_ids[i];
const QString model_id = model_ids[i];
const QJsonObject meta = obj.value("metadata").toObject();
const uint32_t existing_mid = sguard->modelIdForFedId(fed_id);
const uint32_t existing_mid = sguard->sessionModelIdForModelId(model_id);
if (existing_mid != 0 && sguard->loader()) {
const QString existing_path = sguard->loader()->filePath(existing_mid);
if (QDir::cleanPath(existing_path) == QDir::cleanPath(new_path)) {
sguard->setCloudMetadata(fed_id, meta.toVariantMap());
sguard->setCloudMetadata(model_id, meta.toVariantMap());
continue;
}
// Path changed (new revision lives in a fresh cache dir).
@@ -153,13 +153,13 @@ void resolveCloudModels(SessionState& session, ViewportWindow& viewport) {
if (vguard) vguard->removeModel(existing_mid);
sguard->loader()->removeModel(existing_mid);
sguard->elementRegistry()->removeModel(existing_mid);
sguard->removeModelMappingByFedId(fed_id);
sguard->removeModelMappingByModelId(model_id);
any_detached = true;
}
sguard->setCloudMetadata(fed_id, meta.toVariantMap());
sguard->setCloudMetadata(model_id, meta.toVariantMap());
paths_to_load << new_path;
fed_ids_to_load << fed_id;
model_ids_to_load << model_id;
}
if (any_detached) {
if (vguard) vguard->setSelectedObjectId(0);
@@ -168,7 +168,7 @@ void resolveCloudModels(SessionState& session, ViewportWindow& viewport) {
}
if (!paths_to_load.isEmpty()) {
modules::models::commands::detail::loadModels(
*sguard, paths_to_load, fed_ids_to_load);
*sguard, paths_to_load, model_ids_to_load);
sguard->notifyModelsChanged();
}
},
@@ -183,8 +183,8 @@ void resolveCloudModels(SessionState& session, ViewportWindow& viewport) {
}
int total = 0;
for (auto it = connector_to_fed_ids.constBegin();
it != connector_to_fed_ids.constEnd(); ++it) {
for (auto it = connector_to_model_ids.constBegin();
it != connector_to_model_ids.constEnd(); ++it) {
total += it.value().size();
}
session.setStatusMessage("Cloud", QString("Resolving %1 model(s)...").arg(total));
@@ -224,7 +224,7 @@ bool openProjectAt(SessionState& session, QWidget& host, ViewportWindow& viewpor
clearScene(session, viewport);
QStringList paths;
QStringList fed_ids;
QStringList model_ids;
for (const auto& model : session.federation()->models()) {
if (model.source_connector != "local") continue;
if (!QFileInfo::exists(model.source_path)) {
@@ -232,9 +232,9 @@ bool openProjectAt(SessionState& session, QWidget& host, ViewportWindow& viewpor
continue;
}
paths << model.source_path;
fed_ids << model.id;
model_ids << model.id;
}
modules::models::commands::detail::loadModels(session, paths, fed_ids);
modules::models::commands::detail::loadModels(session, paths, model_ids);
if (!warnings.isEmpty()) {
QMessageBox::warning(&host, "Open Project",
+25 -25
View File
@@ -67,9 +67,9 @@ ViewportView::ViewportView(bonsaiviewer::SessionState* session_state,
// first geometry-ready consumes the arm). refresh() stays terminal —
// any federation mutation from the guess propagates through
// SessionState's federatedFalseOriginChanged relay.
connect(session_state_, &SessionState::modelGeometryReady, this, [this](uint32_t model_id) {
connect(session_state_, &SessionState::modelGeometryReady, this, [this](uint32_t session_model_id) {
if (modules::models::consumeFederatedFalseOriginGuess()) {
guessFederatedFalseOriginFromFirstModel(model_id);
guessFederatedFalseOriginFromFirstModel(session_model_id);
}
refresh();
});
@@ -136,34 +136,34 @@ void ViewportView::refresh() {
viewport_->setFederatedFalseOrigin(
composeFederatedFalseOrigin(federation->federatedFalseOrigin(), federation->config()));
for (uint32_t model_id : session_state_->modelIds()) {
applyCoordinateOperation(model_id);
applyModelVisibility(model_id);
for (uint32_t session_model_id : session_state_->sessionModelIds()) {
applyCoordinateOperation(session_model_id);
applyModelVisibility(session_model_id);
}
}
void ViewportView::applyCoordinateOperation(uint32_t model_id) {
void ViewportView::applyCoordinateOperation(uint32_t session_model_id) {
SceneLoader* loader = session_state_->loader();
Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity();
if (const ModelGeoref* georef = loader->modelGeoref(model_id)) {
if (const ModelGeoref* georef = loader->modelGeoref(session_model_id)) {
if (georef->has_coordinate_operation) {
matrix = georef->coordinate_operation_meters;
}
}
viewport_->setModelCoordinateOperation(model_id, matrix);
applyModelTransformation(model_id);
viewport_->setModelCoordinateOperation(session_model_id, matrix);
applyModelTransformation(session_model_id);
}
void ViewportView::applyModelTransformation(uint32_t model_id) {
void ViewportView::applyModelTransformation(uint32_t session_model_id) {
Federation* federation = session_state_->federation();
SceneLoader* loader = session_state_->loader();
Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity();
const QString fed_id = session_state_->fedIdForModelId(model_id);
if (!fed_id.isEmpty()) {
if (const Federation::Model* model = federation->findById(fed_id)) {
const QString model_id = session_state_->modelIdForSessionModelId(session_model_id);
if (!model_id.isEmpty()) {
if (const Federation::Model* model = federation->findById(model_id)) {
ModelUnits units;
Eigen::Matrix4d coordinate_operation = Eigen::Matrix4d::Identity();
if (const ModelGeoref* georef = loader->modelGeoref(model_id)) {
if (const ModelGeoref* georef = loader->modelGeoref(session_model_id)) {
units = georef->units;
if (georef->has_coordinate_operation) {
coordinate_operation = georef->coordinate_operation_meters;
@@ -173,18 +173,18 @@ void ViewportView::applyModelTransformation(uint32_t model_id) {
model->model_transformation, federation->config(), units, coordinate_operation);
}
}
viewport_->setModelTransformation(model_id, matrix);
viewport_->setModelTransformation(session_model_id, matrix);
}
void ViewportView::applyModelVisibility(uint32_t model_id) {
void ViewportView::applyModelVisibility(uint32_t session_model_id) {
Federation* federation = session_state_->federation();
const QString fed_id = session_state_->fedIdForModelId(model_id);
if (fed_id.isEmpty()) return;
const QString model_id = session_state_->modelIdForSessionModelId(session_model_id);
if (model_id.isEmpty()) return;
if (federation->isModelEffectivelyVisible(fed_id)) {
viewport_->showModel(model_id);
if (federation->isModelEffectivelyVisible(model_id)) {
viewport_->showModel(session_model_id);
} else {
viewport_->hideModel(model_id);
viewport_->hideModel(session_model_id);
}
}
@@ -209,7 +209,7 @@ void ViewportView::applyModelVisibility(uint32_t model_id) {
// mutation here propagates through SessionState's federation relay
// (federatedFalseOriginChanged → notifyFederationChanged) without
// re-entering this function.
void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t model_id) {
void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t session_model_id) {
Federation* federation = session_state_->federation();
if (!federation->filePath().isEmpty()) return;
@@ -218,10 +218,10 @@ void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t model_id) {
if (current.xyz != defaults.xyz || current.rz_deg != defaults.rz_deg) return;
Eigen::Vector3d first_geometry_point_m;
if (!viewport_->firstGeometryPointWorldM(model_id, first_geometry_point_m)) return;
if (!viewport_->firstGeometryPointWorldM(session_model_id, first_geometry_point_m)) return;
SceneLoader* loader = session_state_->loader();
const ModelGeoref* georef = loader->modelGeoref(model_id);
const ModelGeoref* georef = loader->modelGeoref(session_model_id);
if (georef == nullptr) return;
federation->setFederatedFalseOrigin(::guessFederatedFalseOrigin(
@@ -236,7 +236,7 @@ void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t model_id) {
// (0,0,0) — the federated false origin in render space — capped at
// 100 m so a model with crazy-coord geometry can't pull the camera
// back into nothing.
viewport_->frameOnFederatedOrigin(model_id, 100.0f);
viewport_->frameOnFederatedOrigin(session_model_id, 100.0f);
}
void ViewportView::updateVolumeReadout() {
+4 -4
View File
@@ -50,10 +50,10 @@ public:
private:
void refresh();
void applyCoordinateOperation(uint32_t model_id);
void applyModelTransformation(uint32_t model_id);
void applyModelVisibility(uint32_t model_id);
void guessFederatedFalseOriginFromFirstModel(uint32_t model_id);
void applyCoordinateOperation(uint32_t session_model_id);
void applyModelTransformation(uint32_t session_model_id);
void applyModelVisibility(uint32_t session_model_id);
void guessFederatedFalseOriginFromFirstModel(uint32_t session_model_id);
void updateVolumeReadout();
bonsaiviewer::SessionState* session_state_ = nullptr;