mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-16 21:42:19 +00:00
Improve viewer variable names
Rename short local variables and parameters in the viewer loading, sidecar, and BonsaiViewer command paths to make their responsibilities clearer.\n\nGenerated with the assistance of an AI coding tool.
This commit is contained in:
@@ -88,37 +88,37 @@ std::optional<express::Base> ElementRegistry::findEntity(uint32_t object_id) con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElementRegistry::onSidecarElementsReady(uint32_t /*mid*/,
|
void ElementRegistry::onSidecarElementsReady(uint32_t /*model_id*/,
|
||||||
std::vector<PackedElementInfo> elements,
|
std::vector<PackedElementInfo> elements,
|
||||||
std::string string_table) {
|
std::string string_table) {
|
||||||
auto str = [&](uint32_t offset, uint32_t length) -> QString {
|
auto string_from_table = [&](uint32_t offset, uint32_t length) -> QString {
|
||||||
if (length == 0 || offset + length > string_table.size()) return {};
|
if (length == 0 || offset + length > string_table.size()) return {};
|
||||||
return QString::fromStdString(string_table.substr(offset, length));
|
return QString::fromStdString(string_table.substr(offset, length));
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto& pe : elements) {
|
for (const auto& packed_element : elements) {
|
||||||
BasicElementInfo info;
|
BasicElementInfo info;
|
||||||
info.object_id = pe.object_id;
|
info.object_id = packed_element.object_id;
|
||||||
info.model_id = pe.model_id;
|
info.model_id = packed_element.model_id;
|
||||||
info.ifc_id = pe.ifc_id;
|
info.ifc_id = packed_element.ifc_id;
|
||||||
info.parent_id = pe.parent_id;
|
info.parent_id = packed_element.parent_id;
|
||||||
info.guid = str(pe.guid_offset, pe.guid_length);
|
info.guid = string_from_table(packed_element.guid_offset, packed_element.guid_length);
|
||||||
info.name = str(pe.name_offset, pe.name_length);
|
info.name = string_from_table(packed_element.name_offset, packed_element.name_length);
|
||||||
info.type = str(pe.type_offset, pe.type_length);
|
info.type = string_from_table(packed_element.type_offset, packed_element.type_length);
|
||||||
elements_[info.object_id] = info;
|
elements_[info.object_id] = info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElementRegistry::onStreamedElementsReady(uint32_t /*mid*/, std::vector<ElementInfo> elements) {
|
void ElementRegistry::onStreamedElementsReady(uint32_t /*model_id*/, std::vector<ElementInfo> elements) {
|
||||||
for (const auto& e : elements) {
|
for (const auto& element : elements) {
|
||||||
BasicElementInfo info;
|
BasicElementInfo info;
|
||||||
info.object_id = e.object_id;
|
info.object_id = element.object_id;
|
||||||
info.model_id = e.model_id;
|
info.model_id = element.model_id;
|
||||||
info.ifc_id = e.ifc_id;
|
info.ifc_id = element.ifc_id;
|
||||||
info.parent_id = e.parent_id;
|
info.parent_id = element.parent_id;
|
||||||
info.guid = QString::fromStdString(e.guid);
|
info.guid = QString::fromStdString(element.guid);
|
||||||
info.name = QString::fromStdString(e.name);
|
info.name = QString::fromStdString(element.name);
|
||||||
info.type = QString::fromStdString(e.type);
|
info.type = QString::fromStdString(element.type);
|
||||||
elements_[info.object_id] = info;
|
elements_[info.object_id] = info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ public:
|
|||||||
std::optional<express::Base> findEntity(uint32_t object_id) const;
|
std::optional<express::Base> findEntity(uint32_t object_id) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onSidecarElementsReady(uint32_t mid,
|
void onSidecarElementsReady(uint32_t model_id,
|
||||||
std::vector<PackedElementInfo> elements,
|
std::vector<PackedElementInfo> elements,
|
||||||
std::string string_table);
|
std::string string_table);
|
||||||
void onStreamedElementsReady(uint32_t mid, std::vector<ElementInfo> elements);
|
void onStreamedElementsReady(uint32_t model_id, std::vector<ElementInfo> elements);
|
||||||
|
|
||||||
SceneLoader* loader_ = nullptr;
|
SceneLoader* loader_ = nullptr;
|
||||||
std::unordered_map<uint32_t, BasicElementInfo> elements_;
|
std::unordered_map<uint32_t, BasicElementInfo> elements_;
|
||||||
|
|||||||
@@ -64,25 +64,25 @@ void SessionState::createLoader(ViewportWindow* viewport) {
|
|||||||
});
|
});
|
||||||
connect(loader_, &SceneLoader::progressChanged, this, &SessionState::setProgress);
|
connect(loader_, &SceneLoader::progressChanged, this, &SessionState::setProgress);
|
||||||
connect(loader_, &SceneLoader::loadedFromSidecar, this,
|
connect(loader_, &SceneLoader::loadedFromSidecar, this,
|
||||||
[this, format_elapsed](uint32_t mid, qint64 elapsed_ms) {
|
[this, format_elapsed](uint32_t model_id, qint64 elapsed_ms) {
|
||||||
setStatusMessage("Loaded",
|
setStatusMessage("Loaded",
|
||||||
QString("%1 from cache in %2")
|
QString("%1 from cache in %2")
|
||||||
.arg(loader_->displayName(mid))
|
.arg(loader_->displayName(model_id))
|
||||||
.arg(format_elapsed(elapsed_ms)));
|
.arg(format_elapsed(elapsed_ms)));
|
||||||
endProgress();
|
endProgress();
|
||||||
emit modelGeometryReady(mid);
|
emit modelGeometryReady(model_id);
|
||||||
});
|
});
|
||||||
connect(loader_, &SceneLoader::loadedFromStream, this,
|
connect(loader_, &SceneLoader::loadedFromStream, this,
|
||||||
[this, format_elapsed](uint32_t mid, qint64 elapsed_ms) {
|
[this, format_elapsed](uint32_t model_id, qint64 elapsed_ms) {
|
||||||
setStatusMessage("Loaded",
|
setStatusMessage("Loaded",
|
||||||
QString("%1 streamed in %2")
|
QString("%1 streamed in %2")
|
||||||
.arg(loader_->displayName(mid))
|
.arg(loader_->displayName(model_id))
|
||||||
.arg(format_elapsed(elapsed_ms)));
|
.arg(format_elapsed(elapsed_ms)));
|
||||||
endProgress();
|
endProgress();
|
||||||
emit modelGeometryReady(mid);
|
emit modelGeometryReady(model_id);
|
||||||
});
|
});
|
||||||
connect(loader_, &SceneLoader::loadCancelled, this, [this](uint32_t mid) {
|
connect(loader_, &SceneLoader::loadCancelled, this, [this](uint32_t model_id) {
|
||||||
setStatusMessage("Cancelled", loader_->displayName(mid));
|
setStatusMessage("Cancelled", loader_->displayName(model_id));
|
||||||
endProgress();
|
endProgress();
|
||||||
});
|
});
|
||||||
connect(loader_, &SceneLoader::loadError, this,
|
connect(loader_, &SceneLoader::loadError, this,
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ ConnectorPickerDialog::ConnectorPickerDialog(const std::vector<ConnectorManifest
|
|||||||
row->setSpacing(components::style::metrics::padding);
|
row->setSpacing(components::style::metrics::padding);
|
||||||
|
|
||||||
QList<QToolButton*> buttons;
|
QList<QToolButton*> buttons;
|
||||||
for (const auto& m : manifests) {
|
for (const auto& manifest : manifests) {
|
||||||
auto* button = components::buttons::makeButton(m.name, ":/icons/cloud-square.svg", choices);
|
auto* button = components::buttons::makeButton(manifest.name, ":/icons/cloud-square.svg", choices);
|
||||||
const QString id = m.id;
|
const QString id = manifest.id;
|
||||||
connect(button, &QToolButton::clicked, this, [this, id]() {
|
connect(button, &QToolButton::clicked, this, [this, id]() {
|
||||||
selected_id_ = id;
|
selected_id_ = id;
|
||||||
accept();
|
accept();
|
||||||
|
|||||||
@@ -189,8 +189,8 @@ void ConnectorProcess::dispatchLine(const QByteArray& line) {
|
|||||||
void ConnectorProcess::failPendingAndClear(int code, const QString& message) {
|
void ConnectorProcess::failPendingAndClear(int code, const QString& message) {
|
||||||
QHash<QString, Pending> snapshot;
|
QHash<QString, Pending> snapshot;
|
||||||
snapshot.swap(pending_);
|
snapshot.swap(pending_);
|
||||||
for (const auto& p : snapshot) {
|
for (const auto& pending_request : snapshot) {
|
||||||
if (p.on_error) p.on_error(code, message);
|
if (pending_request.on_error) pending_request.on_error(code, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ void ConnectorRegistry::refresh() {
|
|||||||
// exec changed under us. Surviving entries keep their running process.
|
// exec changed under us. Surviving entries keep their running process.
|
||||||
for (auto it = processes_.begin(); it != processes_.end();) {
|
for (auto it = processes_.begin(); it != processes_.end();) {
|
||||||
const QString id = it.key();
|
const QString id = it.key();
|
||||||
ConnectorProcess* p = it.value();
|
ConnectorProcess* process = it.value();
|
||||||
const ConnectorManifest* now = manifestFor(id);
|
const ConnectorManifest* current_manifest = manifestFor(id);
|
||||||
const bool stale = !now || !p ||
|
const bool stale = !current_manifest || !process ||
|
||||||
p->manifest().exec_path != now->exec_path;
|
process->manifest().exec_path != current_manifest->exec_path;
|
||||||
if (stale) {
|
if (stale) {
|
||||||
if (p) {
|
if (process) {
|
||||||
p->shutdown();
|
process->shutdown();
|
||||||
p->deleteLater();
|
process->deleteLater();
|
||||||
}
|
}
|
||||||
it = processes_.erase(it);
|
it = processes_.erase(it);
|
||||||
} else {
|
} else {
|
||||||
@@ -65,8 +65,8 @@ void ConnectorRegistry::refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ConnectorManifest* ConnectorRegistry::manifestFor(const QString& id) const {
|
const ConnectorManifest* ConnectorRegistry::manifestFor(const QString& id) const {
|
||||||
for (const auto& m : manifests_) {
|
for (const auto& manifest : manifests_) {
|
||||||
if (m.id == id) return &m;
|
if (manifest.id == id) return &manifest;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ ConnectorProcess* ConnectorRegistry::get(const QString& id) {
|
|||||||
processes_.insert(id, proc);
|
processes_.insert(id, proc);
|
||||||
connect(proc, &ConnectorProcess::crashed, this, [this, id](const QString& message) {
|
connect(proc, &ConnectorProcess::crashed, this, [this, id](const QString& message) {
|
||||||
qWarning() << "ifcviewer connectors:" << message;
|
qWarning() << "ifcviewer connectors:" << message;
|
||||||
if (auto* p = processes_.take(id)) p->deleteLater();
|
if (auto* process = processes_.take(id)) process->deleteLater();
|
||||||
});
|
});
|
||||||
return proc;
|
return proc;
|
||||||
}
|
}
|
||||||
@@ -99,9 +99,9 @@ void ConnectorRegistry::shutdownAll() {
|
|||||||
const auto procs = processes_;
|
const auto procs = processes_;
|
||||||
processes_.clear();
|
processes_.clear();
|
||||||
for (auto it = procs.begin(); it != procs.end(); ++it) {
|
for (auto it = procs.begin(); it != procs.end(); ++it) {
|
||||||
if (auto* p = it.value()) {
|
if (auto* process = it.value()) {
|
||||||
p->shutdown();
|
process->shutdown();
|
||||||
p->deleteLater();
|
process->deleteLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,24 +91,24 @@ QString formatElapsed(qint64 ms) {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void toggleVisibility(SessionState& s, ItemKind kind, const QString& id) {
|
void toggleVisibility(SessionState& session, ItemKind kind, const QString& id) {
|
||||||
Federation* fed = s.federation();
|
Federation* federation = session.federation();
|
||||||
if (kind == ItemKind::Group) {
|
if (kind == ItemKind::Group) {
|
||||||
const Federation::Group* group = fed->findGroupById(id);
|
const Federation::Group* group = federation->findGroupById(id);
|
||||||
if (!group) return;
|
if (!group) return;
|
||||||
fed->setGroupVisible(id, !group->visible);
|
federation->setGroupVisible(id, !group->visible);
|
||||||
s.notifyVisibilityChanged();
|
session.notifyVisibilityChanged();
|
||||||
s.setStatusMessage("Models", group->visible ? "Group hidden" : "Group shown");
|
session.setStatusMessage("Models", group->visible ? "Group hidden" : "Group shown");
|
||||||
} else {
|
} else {
|
||||||
const Federation::Model* model = fed->findById(id);
|
const Federation::Model* model = federation->findById(id);
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
fed->setModelVisible(id, !model->visible);
|
federation->setModelVisible(id, !model->visible);
|
||||||
s.notifyVisibilityChanged();
|
session.notifyVisibilityChanged();
|
||||||
s.setStatusMessage("Models", model->visible ? "Model hidden" : "Model shown");
|
session.setStatusMessage("Models", model->visible ? "Model hidden" : "Model shown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addGroup(SessionState& s, QWidget& host, const QString& parent_group_id) {
|
void addGroup(SessionState& session, QWidget& host, const QString& parent_group_id) {
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
const QString name = QInputDialog::getText(
|
const QString name = QInputDialog::getText(
|
||||||
&host, "New Group", "Group name:", QLineEdit::Normal, "Group", &ok);
|
&host, "New Group", "Group name:", QLineEdit::Normal, "Group", &ok);
|
||||||
@@ -116,13 +116,13 @@ void addGroup(SessionState& s, QWidget& host, const QString& parent_group_id) {
|
|||||||
const QString trimmed = name.trimmed();
|
const QString trimmed = name.trimmed();
|
||||||
if (trimmed.isEmpty()) return;
|
if (trimmed.isEmpty()) return;
|
||||||
|
|
||||||
s.federation()->addGroup(trimmed, parent_group_id);
|
session.federation()->addGroup(trimmed, parent_group_id);
|
||||||
s.notifyFederationChanged();
|
session.notifyFederationChanged();
|
||||||
s.setStatusMessage("Models", "Group added");
|
session.setStatusMessage("Models", "Group added");
|
||||||
}
|
}
|
||||||
|
|
||||||
void renameGroup(SessionState& s, QWidget& host, const QString& group_id) {
|
void renameGroup(SessionState& session, QWidget& host, const QString& group_id) {
|
||||||
const Federation::Group* group = s.federation()->findGroupById(group_id);
|
const Federation::Group* group = session.federation()->findGroupById(group_id);
|
||||||
if (!group) return;
|
if (!group) return;
|
||||||
|
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
@@ -132,27 +132,27 @@ void renameGroup(SessionState& s, QWidget& host, const QString& group_id) {
|
|||||||
const QString trimmed = name.trimmed();
|
const QString trimmed = name.trimmed();
|
||||||
if (trimmed.isEmpty()) return;
|
if (trimmed.isEmpty()) return;
|
||||||
|
|
||||||
s.federation()->setGroupName(group_id, trimmed);
|
session.federation()->setGroupName(group_id, trimmed);
|
||||||
s.notifyFederationChanged();
|
session.notifyFederationChanged();
|
||||||
s.setStatusMessage("Models", "Group renamed");
|
session.setStatusMessage("Models", "Group renamed");
|
||||||
}
|
}
|
||||||
|
|
||||||
void moveGroup(SessionState& s, const QString& id, const QString& parent_group_id) {
|
void moveGroup(SessionState& session, const QString& id, const QString& parent_group_id) {
|
||||||
s.federation()->setGroupParent(id, parent_group_id);
|
session.federation()->setGroupParent(id, parent_group_id);
|
||||||
s.notifyFederationChanged();
|
session.notifyFederationChanged();
|
||||||
s.setStatusMessage("Models", parent_group_id.isEmpty() ? "Group moved to root" : "Group moved");
|
session.setStatusMessage("Models", parent_group_id.isEmpty() ? "Group moved to root" : "Group moved");
|
||||||
}
|
}
|
||||||
|
|
||||||
void moveModels(SessionState& s, const QStringList& ids, const QString& parent_group_id) {
|
void moveModels(SessionState& session, const QStringList& ids, const QString& parent_group_id) {
|
||||||
for (const auto& id : ids) {
|
for (const auto& id : ids) {
|
||||||
s.federation()->setModelGroup(id, parent_group_id);
|
session.federation()->setModelGroup(id, parent_group_id);
|
||||||
}
|
}
|
||||||
s.notifyFederationChanged();
|
session.notifyFederationChanged();
|
||||||
s.setStatusMessage("Models", parent_group_id.isEmpty() ? "Model(s) moved to root" : "Model(s) moved");
|
session.setStatusMessage("Models", parent_group_id.isEmpty() ? "Model(s) moved to root" : "Model(s) moved");
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeGroup(SessionState& s, QWidget& host, const QString& group_id) {
|
void removeGroup(SessionState& session, QWidget& host, const QString& group_id) {
|
||||||
const Federation::Group* group = s.federation()->findGroupById(group_id);
|
const Federation::Group* group = session.federation()->findGroupById(group_id);
|
||||||
if (!group) return;
|
if (!group) return;
|
||||||
|
|
||||||
const auto choice = QMessageBox::question(
|
const auto choice = QMessageBox::question(
|
||||||
@@ -161,13 +161,13 @@ void removeGroup(SessionState& s, QWidget& host, const QString& group_id) {
|
|||||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||||
if (choice != QMessageBox::Yes) return;
|
if (choice != QMessageBox::Yes) return;
|
||||||
|
|
||||||
s.federation()->removeGroup(group_id);
|
session.federation()->removeGroup(group_id);
|
||||||
s.notifyFederationChanged();
|
session.notifyFederationChanged();
|
||||||
s.setStatusMessage("Models", "Group removed");
|
session.setStatusMessage("Models", "Group removed");
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QString& fed_id) {
|
void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host, const QString& fed_id) {
|
||||||
const Federation::Model* model = s.federation()->findById(fed_id);
|
const Federation::Model* model = session.federation()->findById(fed_id);
|
||||||
const QString label = model ? model->display_name : fed_id;
|
const QString label = model ? model->display_name : fed_id;
|
||||||
const auto choice = QMessageBox::question(
|
const auto choice = QMessageBox::question(
|
||||||
&host, "Remove Model",
|
&host, "Remove Model",
|
||||||
@@ -175,41 +175,41 @@ void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QStri
|
|||||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||||
if (choice != QMessageBox::Yes) return;
|
if (choice != QMessageBox::Yes) return;
|
||||||
|
|
||||||
const uint32_t mid = s.modelIdForFedId(fed_id);
|
const uint32_t model_id = session.modelIdForFedId(fed_id);
|
||||||
if (mid == 0) {
|
if (model_id == 0) {
|
||||||
s.federation()->removeModel(fed_id);
|
session.federation()->removeModel(fed_id);
|
||||||
s.notifyFederationChanged();
|
session.notifyFederationChanged();
|
||||||
s.setStatusMessage("Models", "Model removed");
|
session.setStatusMessage("Models", "Model removed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (s.loader()->isLoadingModel(mid)) return;
|
if (session.loader()->isLoadingModel(model_id)) return;
|
||||||
|
|
||||||
vp.setSelectedObjectId(0);
|
viewport.setSelectedObjectId(0);
|
||||||
s.setSelectedObjectId(0);
|
session.setSelectedObjectId(0);
|
||||||
s.federation()->removeModel(fed_id);
|
session.federation()->removeModel(fed_id);
|
||||||
vp.removeModel(mid);
|
viewport.removeModel(model_id);
|
||||||
s.loader()->removeModel(mid);
|
session.loader()->removeModel(model_id);
|
||||||
s.elementRegistry()->removeModel(mid);
|
session.elementRegistry()->removeModel(model_id);
|
||||||
s.removeModelMappingByFedId(fed_id);
|
session.removeModelMappingByFedId(fed_id);
|
||||||
s.notifySelectionChanged();
|
session.notifySelectionChanged();
|
||||||
s.notifyModelsChanged();
|
session.notifyModelsChanged();
|
||||||
s.setStatusMessage("Models", "Model removed");
|
session.setStatusMessage("Models", "Model removed");
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
void loadModels(SessionState& s, const QStringList& paths, const QStringList& fed_ids) {
|
void loadModels(SessionState& session, const QStringList& paths, const QStringList& fed_ids) {
|
||||||
if (paths.isEmpty()) return;
|
if (paths.isEmpty()) return;
|
||||||
|
|
||||||
const auto ids = s.loader()->addFiles(paths);
|
const auto model_ids = session.loader()->addFiles(paths);
|
||||||
for (int i = 0; i < paths.size() && i < static_cast<int>(ids.size()) && i < fed_ids.size(); ++i) {
|
for (int i = 0; i < paths.size() && i < static_cast<int>(model_ids.size()) && i < fed_ids.size(); ++i) {
|
||||||
s.setModelMapping(fed_ids[i], ids[i]);
|
session.setModelMapping(fed_ids[i], model_ids[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
void addModel(SessionState& s, QWidget& host) {
|
void addModel(SessionState& session, QWidget& host) {
|
||||||
AddModelDialog dialog(&host);
|
AddModelDialog dialog(&host);
|
||||||
if (dialog.exec() != QDialog::Accepted) return;
|
if (dialog.exec() != QDialog::Accepted) return;
|
||||||
|
|
||||||
@@ -253,13 +253,13 @@ void addModel(SessionState& s, QWidget& host) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SourceMode::CloudModel:
|
case SourceMode::CloudModel:
|
||||||
addModelFromCloud(s, host);
|
addModelFromCloud(session, host);
|
||||||
return;
|
return;
|
||||||
case SourceMode::ConvertToDatabase:
|
case SourceMode::ConvertToDatabase:
|
||||||
convertIfcToDatabase(s, host);
|
convertIfcToDatabase(session, host);
|
||||||
return;
|
return;
|
||||||
case SourceMode::ExportGeometryDatabase:
|
case SourceMode::ExportGeometryDatabase:
|
||||||
exportGeometryDatabase(s, host);
|
exportGeometryDatabase(session, host);
|
||||||
return;
|
return;
|
||||||
case SourceMode::None:
|
case SourceMode::None:
|
||||||
return;
|
return;
|
||||||
@@ -270,24 +270,24 @@ void addModel(SessionState& s, QWidget& host) {
|
|||||||
// origin via ViewportView. Checked here (before federation->addModel)
|
// origin via ViewportView. Checked here (before federation->addModel)
|
||||||
// because federation->addModel doesn't yet populate SessionState's
|
// because federation->addModel doesn't yet populate SessionState's
|
||||||
// model mapping; modelIds() reflects pre-add state at this point.
|
// model mapping; modelIds() reflects pre-add state at this point.
|
||||||
if (s.modelIds().isEmpty()) {
|
if (session.modelIds().isEmpty()) {
|
||||||
armFederatedFalseOriginGuess();
|
armFederatedFalseOriginGuess();
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList accepted_paths;
|
QStringList accepted_paths;
|
||||||
QStringList accepted_fed_ids;
|
QStringList accepted_fed_ids;
|
||||||
for (const auto& path : paths) {
|
for (const auto& path : paths) {
|
||||||
const QString fed_id = s.federation()->addModel(path);
|
const QString fed_id = session.federation()->addModel(path);
|
||||||
if (fed_id.isEmpty()) continue;
|
if (fed_id.isEmpty()) continue;
|
||||||
accepted_paths << path;
|
accepted_paths << path;
|
||||||
accepted_fed_ids << fed_id;
|
accepted_fed_ids << fed_id;
|
||||||
}
|
}
|
||||||
detail::loadModels(s, accepted_paths, accepted_fed_ids);
|
detail::loadModels(session, accepted_paths, accepted_fed_ids);
|
||||||
s.notifyModelsChanged();
|
session.notifyModelsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void addModelFromCloud(SessionState& s, QWidget& host) {
|
void addModelFromCloud(SessionState& session, QWidget& host) {
|
||||||
auto* registry = s.connectorRegistry();
|
auto* registry = session.connectorRegistry();
|
||||||
const auto& manifests = registry->available();
|
const auto& manifests = registry->available();
|
||||||
if (manifests.empty()) {
|
if (manifests.empty()) {
|
||||||
QMessageBox::information(&host, "Add From Cloud",
|
QMessageBox::information(&host, "Add From Cloud",
|
||||||
@@ -310,9 +310,9 @@ void addModelFromCloud(SessionState& s, QWidget& host) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
s.setStatusMessage("Cloud", QString("Browsing %1...").arg(connector_id));
|
session.setStatusMessage("Cloud", QString("Browsing %1...").arg(connector_id));
|
||||||
|
|
||||||
QPointer<SessionState> sguard(&s);
|
QPointer<SessionState> sguard(&session);
|
||||||
|
|
||||||
proc->call("pull_models_interactive", QJsonValue(),
|
proc->call("pull_models_interactive", QJsonValue(),
|
||||||
[sguard, connector_id](const QJsonValue& result) {
|
[sguard, connector_id](const QJsonValue& result) {
|
||||||
@@ -327,9 +327,9 @@ void addModelFromCloud(SessionState& s, QWidget& host) {
|
|||||||
QStringList paths;
|
QStringList paths;
|
||||||
QStringList fed_ids;
|
QStringList fed_ids;
|
||||||
int added = 0;
|
int added = 0;
|
||||||
for (const QJsonValue& v : arr) {
|
for (const QJsonValue& value : arr) {
|
||||||
if (v.isNull() || !v.isObject()) continue;
|
if (value.isNull() || !value.isObject()) continue;
|
||||||
const QJsonObject entry = v.toObject();
|
const QJsonObject entry = value.toObject();
|
||||||
const QString display_name = entry.value("display_name").toString();
|
const QString display_name = entry.value("display_name").toString();
|
||||||
const QString path = entry.value("path").toString();
|
const QString path = entry.value("path").toString();
|
||||||
if (path.isEmpty()) continue;
|
if (path.isEmpty()) continue;
|
||||||
@@ -368,35 +368,35 @@ void addModelFromCloud(SessionState& s, QWidget& host) {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Shared "local path on disk" lookup for the right-click cloud commands:
|
// Shared "local path on disk" lookup for the right-click cloud commands:
|
||||||
// the loader keeps the path keyed by mid (set when a file or pull_models
|
// the loader keeps the path keyed by model_id (set when a file or pull_models
|
||||||
// path was queued). Both local-sourced and resolved cloud-sourced models
|
// path was queued). Both local-sourced and resolved cloud-sourced models
|
||||||
// have one; only un-resolved cloud models (where pull_models hasn't
|
// have one; only un-resolved cloud models (where pull_models hasn't
|
||||||
// returned yet) won't.
|
// returned yet) won't.
|
||||||
QString localPathForModel(SessionState& s, const QString& fed_id) {
|
QString localPathForModel(SessionState& session, const QString& fed_id) {
|
||||||
const uint32_t mid = s.modelIdForFedId(fed_id);
|
const uint32_t model_id = session.modelIdForFedId(fed_id);
|
||||||
if (mid == 0 || !s.loader()) return {};
|
if (model_id == 0 || !session.loader()) return {};
|
||||||
return s.loader()->filePath(mid);
|
return session.loader()->filePath(model_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void saveModelToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
void saveModelToCloud(SessionState& session, QWidget& host, const QString& fed_id) {
|
||||||
auto* fed = s.federation();
|
auto* federation = session.federation();
|
||||||
const Federation::Model* model = fed->findById(fed_id);
|
const Federation::Model* model = federation->findById(fed_id);
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
if (model->source_connector == "local") {
|
if (model->source_connector == "local") {
|
||||||
QMessageBox::information(&host, "Save Model To Cloud",
|
QMessageBox::information(&host, "Save Model To Cloud",
|
||||||
"This model has no cloud target. Use \"Save As To Cloud\" first.");
|
"This model has no cloud target. Use \"Save As To Cloud\" first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const QString local_path = localPathForModel(s, fed_id);
|
const QString local_path = localPathForModel(session, fed_id);
|
||||||
if (local_path.isEmpty()) {
|
if (local_path.isEmpty()) {
|
||||||
QMessageBox::warning(&host, "Save Model To Cloud",
|
QMessageBox::warning(&host, "Save Model To Cloud",
|
||||||
"Cannot find a local copy of this model to push.");
|
"Cannot find a local copy of this model to push.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const QString connector_id = model->source_connector;
|
const QString connector_id = model->source_connector;
|
||||||
auto* registry = s.connectorRegistry();
|
auto* registry = session.connectorRegistry();
|
||||||
auto* proc = registry->get(connector_id);
|
auto* proc = registry->get(connector_id);
|
||||||
if (!proc) {
|
if (!proc) {
|
||||||
QMessageBox::warning(&host, "Save Model To Cloud",
|
QMessageBox::warning(&host, "Save Model To Cloud",
|
||||||
@@ -411,10 +411,10 @@ void saveModelToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
|||||||
params["path"] = local_path;
|
params["path"] = local_path;
|
||||||
params["source"] = source;
|
params["source"] = source;
|
||||||
|
|
||||||
s.setStatusMessage("Cloud",
|
session.setStatusMessage("Cloud",
|
||||||
QString("Saving %1 to %2...").arg(model->display_name, connector_id));
|
QString("Saving %1 to %2...").arg(model->display_name, connector_id));
|
||||||
|
|
||||||
QPointer<SessionState> sguard(&s);
|
QPointer<SessionState> sguard(&session);
|
||||||
proc->call("push_model", params,
|
proc->call("push_model", params,
|
||||||
[sguard, fed_id, connector_id](const QJsonValue& result) {
|
[sguard, fed_id, connector_id](const QJsonValue& result) {
|
||||||
if (!sguard) return;
|
if (!sguard) return;
|
||||||
@@ -438,17 +438,17 @@ void saveModelToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveModelAsToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
void saveModelAsToCloud(SessionState& session, QWidget& host, const QString& fed_id) {
|
||||||
const Federation::Model* model = s.federation()->findById(fed_id);
|
const Federation::Model* model = session.federation()->findById(fed_id);
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
const QString local_path = localPathForModel(s, fed_id);
|
const QString local_path = localPathForModel(session, fed_id);
|
||||||
if (local_path.isEmpty()) {
|
if (local_path.isEmpty()) {
|
||||||
QMessageBox::warning(&host, "Save Model As To Cloud",
|
QMessageBox::warning(&host, "Save Model As To Cloud",
|
||||||
"Cannot find a local copy of this model to push.");
|
"Cannot find a local copy of this model to push.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* registry = s.connectorRegistry();
|
auto* registry = session.connectorRegistry();
|
||||||
const auto& manifests = registry->available();
|
const auto& manifests = registry->available();
|
||||||
if (manifests.empty()) {
|
if (manifests.empty()) {
|
||||||
QMessageBox::information(&host, "Save Model As To Cloud",
|
QMessageBox::information(&host, "Save Model As To Cloud",
|
||||||
@@ -475,10 +475,10 @@ void saveModelAsToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
|||||||
QJsonObject params;
|
QJsonObject params;
|
||||||
params["path"] = local_path;
|
params["path"] = local_path;
|
||||||
|
|
||||||
s.setStatusMessage("Cloud",
|
session.setStatusMessage("Cloud",
|
||||||
QString("Pushing %1 to %2...").arg(model->display_name, connector_id));
|
QString("Pushing %1 to %2...").arg(model->display_name, connector_id));
|
||||||
|
|
||||||
QPointer<SessionState> sguard(&s);
|
QPointer<SessionState> sguard(&session);
|
||||||
proc->call("push_model_interactive", params,
|
proc->call("push_model_interactive", params,
|
||||||
[sguard, fed_id, connector_id](const QJsonValue& result) {
|
[sguard, fed_id, connector_id](const QJsonValue& result) {
|
||||||
if (!sguard) return;
|
if (!sguard) return;
|
||||||
@@ -507,7 +507,7 @@ void saveModelAsToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void convertIfcToDatabase(SessionState& s, QWidget& host) {
|
void convertIfcToDatabase(SessionState& session, QWidget& host) {
|
||||||
QFileDialog input_dialog(&host, "Select IFC File to Convert");
|
QFileDialog input_dialog(&host, "Select IFC File to Convert");
|
||||||
input_dialog.setFileMode(QFileDialog::ExistingFile);
|
input_dialog.setFileMode(QFileDialog::ExistingFile);
|
||||||
input_dialog.setNameFilter("IFC Files (*.ifc);;All Files (*)");
|
input_dialog.setNameFilter("IFC Files (*.ifc);;All Files (*)");
|
||||||
@@ -545,10 +545,10 @@ void convertIfcToDatabase(SessionState& s, QWidget& host) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.beginProgress(QString("Converting %1 to %2…")
|
session.beginProgress(QString("Converting %1 to %2…")
|
||||||
.arg(QFileInfo(input_path).fileName(),
|
.arg(QFileInfo(input_path).fileName(),
|
||||||
QFileInfo(output_path).fileName()));
|
QFileInfo(output_path).fileName()));
|
||||||
s.setStatusMessage("Converting",
|
session.setStatusMessage("Converting",
|
||||||
QString("%1 → %2").arg(QFileInfo(input_path).fileName(), QFileInfo(output_path).fileName()));
|
QString("%1 → %2").arg(QFileInfo(input_path).fileName(), QFileInfo(output_path).fileName()));
|
||||||
|
|
||||||
auto timer = std::make_shared<QElapsedTimer>();
|
auto timer = std::make_shared<QElapsedTimer>();
|
||||||
@@ -583,20 +583,20 @@ void convertIfcToDatabase(SessionState& s, QWidget& host) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(thread, &QThread::finished, &host,
|
QObject::connect(thread, &QThread::finished, &host,
|
||||||
[&s, host_ptr = &host, thread, timer, error_message, input_path, output_path]() {
|
[&session, host_ptr = &host, thread, timer, error_message, input_path, output_path]() {
|
||||||
const qint64 elapsed = timer->elapsed();
|
const qint64 elapsed = timer->elapsed();
|
||||||
|
|
||||||
s.endProgress();
|
session.endProgress();
|
||||||
thread->deleteLater();
|
thread->deleteLater();
|
||||||
|
|
||||||
if (!error_message->isEmpty()) {
|
if (!error_message->isEmpty()) {
|
||||||
s.setStatusMessage("Error", *error_message);
|
session.setStatusMessage("Error", *error_message);
|
||||||
QMessageBox::warning(host_ptr, "Convert IFC to Database",
|
QMessageBox::warning(host_ptr, "Convert IFC to Database",
|
||||||
QString("Conversion failed:\n%1").arg(*error_message));
|
QString("Conversion failed:\n%1").arg(*error_message));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
s.setStatusMessage(
|
session.setStatusMessage(
|
||||||
"Converted",
|
"Converted",
|
||||||
QString("%1 → %2 in %3")
|
QString("%1 → %2 in %3")
|
||||||
.arg(QFileInfo(input_path).fileName(),
|
.arg(QFileInfo(input_path).fileName(),
|
||||||
@@ -609,7 +609,7 @@ void convertIfcToDatabase(SessionState& s, QWidget& host) {
|
|||||||
thread->start();
|
thread->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
void exportGeometryDatabase(SessionState& session, QWidget& host) {
|
||||||
QFileDialog input_dialog(&host, "Select IFC File to Export");
|
QFileDialog input_dialog(&host, "Select IFC File to Export");
|
||||||
input_dialog.setFileMode(QFileDialog::ExistingFile);
|
input_dialog.setFileMode(QFileDialog::ExistingFile);
|
||||||
input_dialog.setNameFilter("IFC Files (*.ifc);;All Files (*)");
|
input_dialog.setNameFilter("IFC Files (*.ifc);;All Files (*)");
|
||||||
@@ -637,10 +637,10 @@ void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
|||||||
output_path += ".rdbview";
|
output_path += ".rdbview";
|
||||||
}
|
}
|
||||||
|
|
||||||
s.beginProgress(QString("Exporting %1 to %2…")
|
session.beginProgress(QString("Exporting %1 to %2…")
|
||||||
.arg(QFileInfo(input_path).fileName(),
|
.arg(QFileInfo(input_path).fileName(),
|
||||||
QFileInfo(output_path).fileName()));
|
QFileInfo(output_path).fileName()));
|
||||||
s.setStatusMessage("Exporting",
|
session.setStatusMessage("Exporting",
|
||||||
QString("%1 → %2").arg(QFileInfo(input_path).fileName(), QFileInfo(output_path).fileName()));
|
QString("%1 → %2").arg(QFileInfo(input_path).fileName(), QFileInfo(output_path).fileName()));
|
||||||
|
|
||||||
auto timer = std::make_shared<QElapsedTimer>();
|
auto timer = std::make_shared<QElapsedTimer>();
|
||||||
@@ -695,13 +695,13 @@ void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
|||||||
|
|
||||||
// Write to a sibling `.tmp` then rename so a partial file never
|
// Write to a sibling `.tmp` then rename so a partial file never
|
||||||
// appears at the destination (matters for cloud-sync folders).
|
// appears at the destination (matters for cloud-sync folders).
|
||||||
const QString tmp_zip = output_path + ".tmp";
|
const QString temporary_zip = output_path + ".tmp";
|
||||||
QFile::remove(tmp_zip);
|
QFile::remove(temporary_zip);
|
||||||
{
|
{
|
||||||
QZipWriter writer(tmp_zip);
|
QZipWriter writer(temporary_zip);
|
||||||
if (writer.status() != QZipWriter::NoError) {
|
if (writer.status() != QZipWriter::NoError) {
|
||||||
throw ifcopenshell::exception(
|
throw ifcopenshell::exception(
|
||||||
("Failed to open " + tmp_zip + " for writing").toStdString());
|
("Failed to open " + temporary_zip + " for writing").toStdString());
|
||||||
}
|
}
|
||||||
writer.setCompressionPolicy(QZipWriter::AutoCompress);
|
writer.setCompressionPolicy(QZipWriter::AutoCompress);
|
||||||
|
|
||||||
@@ -731,15 +731,15 @@ void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
|||||||
writer.close();
|
writer.close();
|
||||||
if (writer.status() != QZipWriter::NoError) {
|
if (writer.status() != QZipWriter::NoError) {
|
||||||
throw ifcopenshell::exception(
|
throw ifcopenshell::exception(
|
||||||
("Failed to finalize " + tmp_zip).toStdString());
|
("Failed to finalize " + temporary_zip).toStdString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QFile::remove(output_path);
|
QFile::remove(output_path);
|
||||||
if (!QFile::rename(tmp_zip, output_path)) {
|
if (!QFile::rename(temporary_zip, output_path)) {
|
||||||
QFile::remove(tmp_zip);
|
QFile::remove(temporary_zip);
|
||||||
throw ifcopenshell::exception(
|
throw ifcopenshell::exception(
|
||||||
("Failed to move " + tmp_zip + " to " + output_path).toStdString());
|
("Failed to move " + temporary_zip + " to " + output_path).toStdString());
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
*error_message = QString::fromUtf8(e.what());
|
*error_message = QString::fromUtf8(e.what());
|
||||||
@@ -751,20 +751,20 @@ void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(thread, &QThread::finished, &host,
|
QObject::connect(thread, &QThread::finished, &host,
|
||||||
[&s, host_ptr = &host, thread, timer, error_message, input_path, output_path]() {
|
[&session, host_ptr = &host, thread, timer, error_message, input_path, output_path]() {
|
||||||
const qint64 elapsed = timer->elapsed();
|
const qint64 elapsed = timer->elapsed();
|
||||||
|
|
||||||
s.endProgress();
|
session.endProgress();
|
||||||
thread->deleteLater();
|
thread->deleteLater();
|
||||||
|
|
||||||
if (!error_message->isEmpty()) {
|
if (!error_message->isEmpty()) {
|
||||||
s.setStatusMessage("Error", *error_message);
|
session.setStatusMessage("Error", *error_message);
|
||||||
QMessageBox::warning(host_ptr, "Export Geometry Database",
|
QMessageBox::warning(host_ptr, "Export Geometry Database",
|
||||||
QString("Export failed:\n%1").arg(*error_message));
|
QString("Export failed:\n%1").arg(*error_message));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
s.setStatusMessage(
|
session.setStatusMessage(
|
||||||
"Exported",
|
"Exported",
|
||||||
QString("%1 → %2 in %3")
|
QString("%1 → %2 in %3")
|
||||||
.arg(QFileInfo(input_path).fileName(),
|
.arg(QFileInfo(input_path).fileName(),
|
||||||
@@ -777,8 +777,8 @@ void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
|||||||
thread->start();
|
thread->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void openSettings(SessionState& s, QWidget& host) {
|
void openSettings(SessionState& session, QWidget& host) {
|
||||||
SettingsDialog dialog(&s, &host);
|
SettingsDialog dialog(&session, &host);
|
||||||
dialog.exec();
|
dialog.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,35 +52,35 @@ namespace bonsaiviewer::modules::models::commands {
|
|||||||
|
|
||||||
// User-facing commands. Each one is responsible for emitting any notify()
|
// User-facing commands. Each one is responsible for emitting any notify()
|
||||||
// signals exactly once, at the end of its execution.
|
// signals exactly once, at the end of its execution.
|
||||||
void toggleVisibility(SessionState& s, ItemKind kind, const QString& id);
|
void toggleVisibility(SessionState& session, ItemKind kind, const QString& id);
|
||||||
void addGroup(SessionState& s, QWidget& host, const QString& parent_group_id);
|
void addGroup(SessionState& session, QWidget& host, const QString& parent_group_id);
|
||||||
void renameGroup(SessionState& s, QWidget& host, const QString& group_id);
|
void renameGroup(SessionState& session, QWidget& host, const QString& group_id);
|
||||||
void moveGroup(SessionState& s, const QString& id, const QString& parent_group_id);
|
void moveGroup(SessionState& session, const QString& id, const QString& parent_group_id);
|
||||||
void moveModels(SessionState& s, const QStringList& ids, const QString& parent_group_id);
|
void moveModels(SessionState& session, const QStringList& ids, const QString& parent_group_id);
|
||||||
void removeGroup(SessionState& s, QWidget& host, const QString& group_id);
|
void removeGroup(SessionState& session, QWidget& host, const QString& group_id);
|
||||||
void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QString& fed_id);
|
void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host, const QString& fed_id);
|
||||||
void addModel(SessionState& s, QWidget& host);
|
void addModel(SessionState& session, QWidget& host);
|
||||||
// Connector picker → pull_models_interactive → addCloudModel + load.
|
// Connector picker → pull_models_interactive → addCloudModel + load.
|
||||||
// Reachable from AddModelDialog's CloudModel button; the underlying call
|
// Reachable from AddModelDialog's CloudModel button; the underlying call
|
||||||
// is async, so addModelFromCloud returns immediately after kicking it off.
|
// is async, so addModelFromCloud returns immediately after kicking it off.
|
||||||
void addModelFromCloud(SessionState& s, QWidget& host);
|
void addModelFromCloud(SessionState& session, QWidget& host);
|
||||||
// push_model: push a cloud-sourced model back to its existing target.
|
// push_model: push a cloud-sourced model back to its existing target.
|
||||||
// Only valid when model.source_connector != "local". Async.
|
// Only valid when model.source_connector != "local". Async.
|
||||||
void saveModelToCloud(SessionState& s, QWidget& host, const QString& fed_id);
|
void saveModelToCloud(SessionState& session, QWidget& host, const QString& fed_id);
|
||||||
// push_model_interactive: pick a connector and push to a fresh cloud
|
// push_model_interactive: pick a connector and push to a fresh cloud
|
||||||
// target. Valid for any model (local or already cloud-sourced). Async.
|
// target. Valid for any model (local or already cloud-sourced). Async.
|
||||||
void saveModelAsToCloud(SessionState& s, QWidget& host, const QString& fed_id);
|
void saveModelAsToCloud(SessionState& session, QWidget& host, const QString& fed_id);
|
||||||
void convertIfcToDatabase(SessionState& s, QWidget& host);
|
void convertIfcToDatabase(SessionState& session, QWidget& host);
|
||||||
void exportGeometryDatabase(SessionState& s, QWidget& host);
|
void exportGeometryDatabase(SessionState& session, QWidget& host);
|
||||||
void openSettings(SessionState& s, QWidget& host);
|
void openSettings(SessionState& session, QWidget& host);
|
||||||
|
|
||||||
// Internal building blocks shared by commands here and by ProjectController.
|
// Internal building blocks shared by commands here and by ProjectController.
|
||||||
// These NEVER call notify*() — the caller is responsible for emitting once
|
// These NEVER call notify*() — the caller is responsible for emitting once
|
||||||
// at the end of its execution.
|
// at the end of its execution.
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// Queues already-federated models on the loader and maps their fed-ids to mids.
|
// Queues already-federated models on the loader and maps their federation-ids to mids.
|
||||||
void loadModels(SessionState& s, const QStringList& paths, const QStringList& fed_ids);
|
void loadModels(SessionState& session, const QStringList& paths, const QStringList& fed_ids);
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
|
|||||||
@@ -164,8 +164,8 @@ void FederationItemModel::refreshSubtreeVisibility(QStandardItem* root) {
|
|||||||
const auto kind = static_cast<ItemKind>(item->data(KindRole).toInt());
|
const auto kind = static_cast<ItemKind>(item->data(KindRole).toInt());
|
||||||
bool visible = true;
|
bool visible = true;
|
||||||
if (kind == ItemKind::Group) {
|
if (kind == ItemKind::Group) {
|
||||||
const Federation::Group* g = federation_->findGroupById(id);
|
const Federation::Group* group = federation_->findGroupById(id);
|
||||||
visible = g && g->visible;
|
visible = group && group->visible;
|
||||||
} else {
|
} else {
|
||||||
visible = federation_->isModelEffectivelyVisible(id);
|
visible = federation_->isModelEffectivelyVisible(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,8 +197,9 @@ private:
|
|||||||
if (!target_index.isValid()) return true;
|
if (!target_index.isValid()) return true;
|
||||||
if (kindOf(target_index) != ItemKind::Group) return false;
|
if (kindOf(target_index) != ItemKind::Group) return false;
|
||||||
if (group_id == target_group_id) return false;
|
if (group_id == target_group_id) return false;
|
||||||
for (QModelIndex cur = target_index; cur.isValid(); cur = cur.parent()) {
|
for (QModelIndex ancestor_index = target_index; ancestor_index.isValid();
|
||||||
if (idOf(cur) == group_id) return false;
|
ancestor_index = ancestor_index.parent()) {
|
||||||
|
if (idOf(ancestor_index) == group_id) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,10 +166,10 @@ void SettingsDialog::setupUi() {
|
|||||||
federation_unit_form->setHorizontalSpacing(16);
|
federation_unit_form->setHorizontalSpacing(16);
|
||||||
federation_unit_form->setVerticalSpacing(10);
|
federation_unit_form->setVerticalSpacing(10);
|
||||||
unit_combo_ = new QComboBox(federation_unit_body);
|
unit_combo_ = new QComboBox(federation_unit_body);
|
||||||
for (const auto& uc : kUnitChoices) {
|
for (const auto& unit_choice : kUnitChoices) {
|
||||||
QStringList data;
|
QStringList data;
|
||||||
data << QString::fromUtf8(uc.prefix) << QString::fromUtf8(uc.name);
|
data << QString::fromUtf8(unit_choice.prefix) << QString::fromUtf8(unit_choice.name);
|
||||||
unit_combo_->addItem(uc.label, data);
|
unit_combo_->addItem(unit_choice.label, data);
|
||||||
}
|
}
|
||||||
federation_unit_form->addRow("Unit", unit_combo_);
|
federation_unit_form->addRow("Unit", unit_combo_);
|
||||||
federation_unit_section->addBodyWidget(unit_hint);
|
federation_unit_section->addBodyWidget(unit_hint);
|
||||||
@@ -341,13 +341,13 @@ void SettingsDialog::setupUi() {
|
|||||||
void SettingsDialog::syncFromFederation() {
|
void SettingsDialog::syncFromFederation() {
|
||||||
if (!federation_) return;
|
if (!federation_) return;
|
||||||
|
|
||||||
const auto& cfg = federation_->config();
|
const auto& config = federation_->config();
|
||||||
int idx = -1;
|
int idx = -1;
|
||||||
for (int i = 0; i < unit_combo_->count(); ++i) {
|
for (int i = 0; i < unit_combo_->count(); ++i) {
|
||||||
const QStringList data = unit_combo_->itemData(i).toStringList();
|
const QStringList data = unit_combo_->itemData(i).toStringList();
|
||||||
if (data.size() == 2 &&
|
if (data.size() == 2 &&
|
||||||
data[0].toStdString() == cfg.unit_prefix &&
|
data[0].toStdString() == config.unit_prefix &&
|
||||||
data[1].toStdString() == cfg.unit_name) {
|
data[1].toStdString() == config.unit_name) {
|
||||||
idx = i;
|
idx = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -370,7 +370,7 @@ void SettingsDialog::populateModelTable() {
|
|||||||
|
|
||||||
int row = 0;
|
int row = 0;
|
||||||
for (const auto& model : federation_->models()) {
|
for (const auto& model : federation_->models()) {
|
||||||
const auto& xf = model.model_transformation;
|
const auto& transformation = model.model_transformation;
|
||||||
model_table_->insertRow(row);
|
model_table_->insertRow(row);
|
||||||
|
|
||||||
auto* model_item = new QTableWidgetItem(model.display_name.isEmpty() ? model.id : model.display_name);
|
auto* model_item = new QTableWidgetItem(model.display_name.isEmpty() ? model.id : model.display_name);
|
||||||
@@ -383,13 +383,13 @@ void SettingsDialog::populateModelTable() {
|
|||||||
widgets.frame = new QComboBox(model_table_);
|
widgets.frame = new QComboBox(model_table_);
|
||||||
widgets.frame->addItem("Local", static_cast<int>(AFrame::ModelLocal));
|
widgets.frame->addItem("Local", static_cast<int>(AFrame::ModelLocal));
|
||||||
widgets.frame->addItem("Global", static_cast<int>(AFrame::ModelGlobal));
|
widgets.frame->addItem("Global", static_cast<int>(AFrame::ModelGlobal));
|
||||||
widgets.frame->setCurrentIndex(xf.a_frame == AFrame::ModelGlobal ? 1 : 0);
|
widgets.frame->setCurrentIndex(transformation.a_frame == AFrame::ModelGlobal ? 1 : 0);
|
||||||
model_table_->setCellWidget(row, 1, widgets.frame);
|
model_table_->setCellWidget(row, 1, widgets.frame);
|
||||||
|
|
||||||
widgets.from_point = new QTableWidgetItem(formatVector3(xf.a));
|
widgets.from_point = new QTableWidgetItem(formatVector3(transformation.a));
|
||||||
widgets.to_point = new QTableWidgetItem(formatVector3(xf.b));
|
widgets.to_point = new QTableWidgetItem(formatVector3(transformation.b));
|
||||||
widgets.rotate = new QTableWidgetItem(formatVector3(xf.rxyz_deg));
|
widgets.rotate = new QTableWidgetItem(formatVector3(transformation.rxyz_deg));
|
||||||
widgets.pivot = new QTableWidgetItem(formatVector3(xf.pivot));
|
widgets.pivot = new QTableWidgetItem(formatVector3(transformation.pivot));
|
||||||
model_table_->setItem(row, 2, widgets.from_point);
|
model_table_->setItem(row, 2, widgets.from_point);
|
||||||
model_table_->setItem(row, 3, widgets.to_point);
|
model_table_->setItem(row, 3, widgets.to_point);
|
||||||
model_table_->setItem(row, 4, widgets.rotate);
|
model_table_->setItem(row, 4, widgets.rotate);
|
||||||
@@ -454,12 +454,12 @@ void SettingsDialog::updateSelectedModelGeoref() {
|
|||||||
void SettingsDialog::onAccepted() {
|
void SettingsDialog::onAccepted() {
|
||||||
if (federation_) {
|
if (federation_) {
|
||||||
const QStringList data = unit_combo_->currentData().toStringList();
|
const QStringList data = unit_combo_->currentData().toStringList();
|
||||||
FederationConfig cfg;
|
FederationConfig config;
|
||||||
if (data.size() == 2) {
|
if (data.size() == 2) {
|
||||||
cfg.unit_prefix = data[0].toStdString();
|
config.unit_prefix = data[0].toStdString();
|
||||||
cfg.unit_name = data[1].toStdString();
|
config.unit_name = data[1].toStdString();
|
||||||
}
|
}
|
||||||
federation_->setConfig(cfg);
|
federation_->setConfig(config);
|
||||||
|
|
||||||
FederatedFalseOrigin origin;
|
FederatedFalseOrigin origin;
|
||||||
origin.xyz = Eigen::Vector3d(parseNumber(xyz_x_), parseNumber(xyz_y_), parseNumber(xyz_z_));
|
origin.xyz = Eigen::Vector3d(parseNumber(xyz_x_), parseNumber(xyz_y_), parseNumber(xyz_z_));
|
||||||
@@ -467,13 +467,13 @@ void SettingsDialog::onAccepted() {
|
|||||||
federation_->setFederatedFalseOrigin(origin);
|
federation_->setFederatedFalseOrigin(origin);
|
||||||
|
|
||||||
for (const auto& row : model_rows_) {
|
for (const auto& row : model_rows_) {
|
||||||
ModelTransformation xf;
|
ModelTransformation transformation;
|
||||||
xf.a_frame = static_cast<AFrame>(row.frame->currentData().toInt());
|
transformation.a_frame = static_cast<AFrame>(row.frame->currentData().toInt());
|
||||||
xf.a = parseVector3(row.from_point->text());
|
transformation.a = parseVector3(row.from_point->text());
|
||||||
xf.b = parseVector3(row.to_point->text());
|
transformation.b = parseVector3(row.to_point->text());
|
||||||
xf.rxyz_deg = parseVector3(row.rotate->text());
|
transformation.rxyz_deg = parseVector3(row.rotate->text());
|
||||||
xf.pivot = parseVector3(row.pivot->text());
|
transformation.pivot = parseVector3(row.pivot->text());
|
||||||
federation_->setModelTransformation(row.fed_id, xf);
|
federation_->setModelTransformation(row.fed_id, transformation);
|
||||||
}
|
}
|
||||||
if (session_state_) {
|
if (session_state_) {
|
||||||
session_state_->notifyFederationChanged();
|
session_state_->notifyFederationChanged();
|
||||||
|
|||||||
@@ -39,12 +39,16 @@ QString formatNumber(double value) {
|
|||||||
|
|
||||||
QString formatAngleDms(double degrees) {
|
QString formatAngleDms(double degrees) {
|
||||||
const double absolute = std::fabs(degrees);
|
const double absolute = std::fabs(degrees);
|
||||||
const int d = static_cast<int>(absolute);
|
const int degree_part = static_cast<int>(absolute);
|
||||||
const double minutes_total = (absolute - static_cast<double>(d)) * 60.0;
|
const double minutes_total = (absolute - static_cast<double>(degree_part)) * 60.0;
|
||||||
const int m = static_cast<int>(minutes_total);
|
const int minute_part = static_cast<int>(minutes_total);
|
||||||
const double s = (minutes_total - static_cast<double>(m)) * 60.0;
|
const double second_part = (minutes_total - static_cast<double>(minute_part)) * 60.0;
|
||||||
const QString sign = degrees < 0.0 ? "-" : "";
|
const QString sign = degrees < 0.0 ? "-" : "";
|
||||||
return QString("%1%2° %3' %4\"").arg(sign).arg(d).arg(m, 2, 10, QChar('0')).arg(formatNumber(s));
|
return QString("%1%2° %3' %4\"")
|
||||||
|
.arg(sign)
|
||||||
|
.arg(degree_part)
|
||||||
|
.arg(minute_part, 2, 10, QChar('0'))
|
||||||
|
.arg(formatNumber(second_part));
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectedModelGeorefState unknownState(const QString& georef, const QString& type) {
|
SelectedModelGeorefState unknownState(const QString& georef, const QString& type) {
|
||||||
@@ -74,8 +78,8 @@ QString formatCachedUnitScale(double meters_per_unit) {
|
|||||||
std::string enumString(const attribute_value& av) {
|
std::string enumString(const attribute_value& av) {
|
||||||
if (av.isNull()) return {};
|
if (av.isNull()) return {};
|
||||||
if (av.type() != ifcopenshell::Argument_ENUMERATION) return {};
|
if (av.type() != ifcopenshell::Argument_ENUMERATION) return {};
|
||||||
enumeration_reference er = av;
|
enumeration_reference enumeration = av;
|
||||||
return std::string(er.value() ? er.value() : "");
|
return std::string(enumeration.value() ? enumeration.value() : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString formatNamedUnit(const express::Base& unit) {
|
QString formatNamedUnit(const express::Base& unit) {
|
||||||
@@ -224,18 +228,18 @@ void SettingsView::refresh(const QString& fed_id) const {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t mid = session_state_->modelIdForFedId(fed_id);
|
const uint32_t model_id = session_state_->modelIdForFedId(fed_id);
|
||||||
if (mid == 0) {
|
if (model_id == 0) {
|
||||||
widget_->renderSelectedModelGeoref(unknownState("Not loaded", "No live model"));
|
widget_->renderSelectedModelGeoref(unknownState("Not loaded", "No live model"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto* ifc_file = loader->ifcFile(mid)) {
|
if (auto* ifc_file = loader->ifcFile(model_id)) {
|
||||||
widget_->renderSelectedModelGeoref(stateFromLiveFile(ifc_file));
|
widget_->renderSelectedModelGeoref(stateFromLiveFile(ifc_file));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModelGeoref* georef = loader->modelGeoref(mid);
|
const ModelGeoref* georef = loader->modelGeoref(model_id);
|
||||||
if (!georef) {
|
if (!georef) {
|
||||||
widget_->renderSelectedModelGeoref(unknownState("Not available yet", "No data source"));
|
widget_->renderSelectedModelGeoref(unknownState("Not available yet", "No data source"));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -53,28 +53,28 @@ namespace {
|
|||||||
// Pure helper — clears the loaded scene without emitting any signals. The
|
// Pure helper — clears the loaded scene without emitting any signals. The
|
||||||
// caller (newProject / openProject) emits projectReset / projectOpened once
|
// caller (newProject / openProject) emits projectReset / projectOpened once
|
||||||
// the whole flow finishes.
|
// the whole flow finishes.
|
||||||
void clearScene(SessionState& s, ViewportWindow& vp) {
|
void clearScene(SessionState& session, ViewportWindow& viewport) {
|
||||||
vp.setSelectedObjectId(0);
|
viewport.setSelectedObjectId(0);
|
||||||
s.setSelectedObjectId(0);
|
session.setSelectedObjectId(0);
|
||||||
for (uint32_t mid : s.modelIds()) {
|
for (uint32_t model_id : session.modelIds()) {
|
||||||
vp.removeModel(mid);
|
viewport.removeModel(model_id);
|
||||||
s.loader()->removeModel(mid);
|
session.loader()->removeModel(model_id);
|
||||||
}
|
}
|
||||||
s.clearModelMappings();
|
session.clearModelMappings();
|
||||||
s.elementRegistry()->clear();
|
session.elementRegistry()->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns false if the user cancelled (i.e. don't proceed with the destructive
|
// Returns false if the user cancelled (i.e. don't proceed with the destructive
|
||||||
// op). Handles the Save → Discard → Cancel branch including a follow-on save.
|
// op). Handles the Save → Discard → Cancel branch including a follow-on save.
|
||||||
bool confirmDiscardIfDirty(SessionState& s, QWidget& host) {
|
bool confirmDiscardIfDirty(SessionState& session, QWidget& host) {
|
||||||
if (!s.federation()->isDirty()) return true;
|
if (!session.federation()->isDirty()) return true;
|
||||||
const auto result = QMessageBox::question(
|
const auto result = QMessageBox::question(
|
||||||
&host, "Unsaved Project",
|
&host, "Unsaved Project",
|
||||||
"The current project has unsaved changes. Save before continuing?",
|
"The current project has unsaved changes. Save before continuing?",
|
||||||
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
|
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
|
||||||
QMessageBox::Save);
|
QMessageBox::Save);
|
||||||
if (result == QMessageBox::Cancel) return false;
|
if (result == QMessageBox::Cancel) return false;
|
||||||
if (result == QMessageBox::Save) return saveProject(s, host);
|
if (result == QMessageBox::Save) return saveProject(session, host);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,18 +88,18 @@ bool confirmDiscardIfDirty(SessionState& s, QWidget& host) {
|
|||||||
// - if no scene entry exists yet (initial open), queue a load.
|
// - if no scene entry exists yet (initial open), queue a load.
|
||||||
// Per spec, connector errors are not surfaced to the user; the connector
|
// Per spec, connector errors are not surfaced to the user; the connector
|
||||||
// has already shown its own UI.
|
// has already shown its own UI.
|
||||||
void resolveCloudModels(SessionState& s, ViewportWindow& vp) {
|
void resolveCloudModels(SessionState& session, ViewportWindow& viewport) {
|
||||||
auto* fed = s.federation();
|
auto* federation = session.federation();
|
||||||
QHash<QString, QStringList> connector_to_fed_ids;
|
QHash<QString, QStringList> connector_to_fed_ids;
|
||||||
for (const auto& m : fed->models()) {
|
for (const auto& model : federation->models()) {
|
||||||
if (m.source_connector == "local") continue;
|
if (model.source_connector == "local") continue;
|
||||||
connector_to_fed_ids[m.source_connector].push_back(m.id);
|
connector_to_fed_ids[model.source_connector].push_back(model.id);
|
||||||
}
|
}
|
||||||
if (connector_to_fed_ids.isEmpty()) return;
|
if (connector_to_fed_ids.isEmpty()) return;
|
||||||
|
|
||||||
auto* registry = s.connectorRegistry();
|
auto* registry = session.connectorRegistry();
|
||||||
QPointer<SessionState> sguard(&s);
|
QPointer<SessionState> sguard(&session);
|
||||||
QPointer<ViewportWindow> vguard(&vp);
|
QPointer<ViewportWindow> vguard(&viewport);
|
||||||
|
|
||||||
for (auto it = connector_to_fed_ids.constBegin();
|
for (auto it = connector_to_fed_ids.constBegin();
|
||||||
it != connector_to_fed_ids.constEnd(); ++it) {
|
it != connector_to_fed_ids.constEnd(); ++it) {
|
||||||
@@ -115,13 +115,13 @@ void resolveCloudModels(SessionState& s, ViewportWindow& vp) {
|
|||||||
|
|
||||||
QJsonArray params;
|
QJsonArray params;
|
||||||
for (const QString& fed_id : fed_ids) {
|
for (const QString& fed_id : fed_ids) {
|
||||||
const Federation::Model* m = fed->findById(fed_id);
|
const Federation::Model* model = federation->findById(fed_id);
|
||||||
if (!m) continue;
|
if (!model) continue;
|
||||||
QJsonObject source = m->source_data;
|
QJsonObject source = model->source_data;
|
||||||
source["connector"] = m->source_connector;
|
source["connector"] = model->source_connector;
|
||||||
QJsonObject entry;
|
QJsonObject entry;
|
||||||
entry["display_name"] = m->display_name;
|
entry["display_name"] = model->display_name;
|
||||||
entry["id"] = m->id;
|
entry["id"] = model->id;
|
||||||
entry["source"] = source;
|
entry["source"] = source;
|
||||||
params.append(entry);
|
params.append(entry);
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ void resolveCloudModels(SessionState& s, ViewportWindow& vp) {
|
|||||||
it != connector_to_fed_ids.constEnd(); ++it) {
|
it != connector_to_fed_ids.constEnd(); ++it) {
|
||||||
total += it.value().size();
|
total += it.value().size();
|
||||||
}
|
}
|
||||||
s.setStatusMessage("Cloud", QString("Resolving %1 model(s)...").arg(total));
|
session.setStatusMessage("Cloud", QString("Resolving %1 model(s)...").arg(total));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Byte-equality check for "is this .ifcfed the same as what we have loaded?"
|
// Byte-equality check for "is this .ifcfed the same as what we have loaded?"
|
||||||
@@ -203,29 +203,29 @@ bool isIfcfedUnchanged(const QString& current_path, const QString& candidate_pat
|
|||||||
return a.readAll() == b.readAll();
|
return a.readAll() == b.readAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool openProjectAt(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path) {
|
bool openProjectAt(SessionState& session, QWidget& host, ViewportWindow& viewport, const QString& path) {
|
||||||
SceneLoader* loader = s.loader();
|
SceneLoader* loader = session.loader();
|
||||||
if (loader && loader->isLoading()) {
|
if (loader && loader->isLoading()) {
|
||||||
QMessageBox::information(
|
QMessageBox::information(
|
||||||
&host, "Open Project",
|
&host, "Open Project",
|
||||||
"Wait until the current model load finishes before opening another project.");
|
"Wait until the current model load finishes before opening another project.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!confirmDiscardIfDirty(s, host)) return false;
|
if (!confirmDiscardIfDirty(session, host)) return false;
|
||||||
|
|
||||||
QStringList warnings;
|
QStringList warnings;
|
||||||
QString err;
|
QString err;
|
||||||
if (!s.federation()->load(path, &warnings, &err)) {
|
if (!session.federation()->load(path, &warnings, &err)) {
|
||||||
QMessageBox::warning(&host, "Open Project",
|
QMessageBox::warning(&host, "Open Project",
|
||||||
QString("Could not open project:\n%1").arg(err));
|
QString("Could not open project:\n%1").arg(err));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearScene(s, vp);
|
clearScene(session, viewport);
|
||||||
|
|
||||||
QStringList paths;
|
QStringList paths;
|
||||||
QStringList fed_ids;
|
QStringList fed_ids;
|
||||||
for (const auto& model : s.federation()->models()) {
|
for (const auto& model : session.federation()->models()) {
|
||||||
if (model.source_connector != "local") continue;
|
if (model.source_connector != "local") continue;
|
||||||
if (!QFileInfo::exists(model.source_path)) {
|
if (!QFileInfo::exists(model.source_path)) {
|
||||||
warnings << QString("Source not found, kept in project: %1").arg(model.source_path);
|
warnings << QString("Source not found, kept in project: %1").arg(model.source_path);
|
||||||
@@ -234,58 +234,58 @@ bool openProjectAt(SessionState& s, QWidget& host, ViewportWindow& vp, const QSt
|
|||||||
paths << model.source_path;
|
paths << model.source_path;
|
||||||
fed_ids << model.id;
|
fed_ids << model.id;
|
||||||
}
|
}
|
||||||
modules::models::commands::detail::loadModels(s, paths, fed_ids);
|
modules::models::commands::detail::loadModels(session, paths, fed_ids);
|
||||||
|
|
||||||
if (!warnings.isEmpty()) {
|
if (!warnings.isEmpty()) {
|
||||||
QMessageBox::warning(&host, "Open Project",
|
QMessageBox::warning(&host, "Open Project",
|
||||||
"Project opened with warnings:\n\n" + warnings.join("\n"));
|
"Project opened with warnings:\n\n" + warnings.join("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
s.federation()->markClean();
|
session.federation()->markClean();
|
||||||
if (s.federation()->hasHomeView()) {
|
if (session.federation()->hasHomeView()) {
|
||||||
const auto& hv = s.federation()->homeView();
|
const auto& home_view = session.federation()->homeView();
|
||||||
vp.setCamera(hv.target.x(), hv.target.y(), hv.target.z(),
|
viewport.setCamera(home_view.target.x(), home_view.target.y(), home_view.target.z(),
|
||||||
hv.distance, hv.yaw, hv.pitch);
|
home_view.distance, home_view.yaw, home_view.pitch);
|
||||||
}
|
}
|
||||||
s.setStatusMessage("Project", QFileInfo(path).fileName());
|
session.setStatusMessage("Project", QFileInfo(path).fileName());
|
||||||
s.notifyProjectOpened(path);
|
session.notifyProjectOpened(path);
|
||||||
|
|
||||||
resolveCloudModels(s, vp);
|
resolveCloudModels(session, viewport);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool saveProjectTo(SessionState& s, QWidget& host, const QString& path) {
|
bool saveProjectTo(SessionState& session, QWidget& host, const QString& path) {
|
||||||
QString err;
|
QString err;
|
||||||
if (!s.federation()->save(path, &err)) {
|
if (!session.federation()->save(path, &err)) {
|
||||||
QMessageBox::warning(&host, "Save Project",
|
QMessageBox::warning(&host, "Save Project",
|
||||||
QString("Could not save project:\n%1").arg(err));
|
QString("Could not save project:\n%1").arg(err));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
s.setStatusMessage("Project", QFileInfo(path).fileName());
|
session.setStatusMessage("Project", QFileInfo(path).fileName());
|
||||||
s.notifyProjectSaved(path);
|
session.notifyProjectSaved(path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
bool newProject(SessionState& session, QWidget& host, ViewportWindow& viewport) {
|
||||||
SceneLoader* loader = s.loader();
|
SceneLoader* loader = session.loader();
|
||||||
if (loader && loader->isLoading()) {
|
if (loader && loader->isLoading()) {
|
||||||
QMessageBox::information(
|
QMessageBox::information(
|
||||||
&host, "New Project",
|
&host, "New Project",
|
||||||
"Wait until the current model load finishes before creating a new project.");
|
"Wait until the current model load finishes before creating a new project.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!confirmDiscardIfDirty(s, host)) return false;
|
if (!confirmDiscardIfDirty(session, host)) return false;
|
||||||
|
|
||||||
clearScene(s, vp);
|
clearScene(session, viewport);
|
||||||
s.federation()->clear();
|
session.federation()->clear();
|
||||||
s.setStatusMessage("Project", "Untitled");
|
session.setStatusMessage("Project", "Untitled");
|
||||||
s.notifyProjectReset();
|
session.notifyProjectReset();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
bool openProject(SessionState& session, QWidget& host, ViewportWindow& viewport) {
|
||||||
QFileDialog file_dialog(&host, "Open Project");
|
QFileDialog file_dialog(&host, "Open Project");
|
||||||
file_dialog.setFileMode(QFileDialog::ExistingFile);
|
file_dialog.setFileMode(QFileDialog::ExistingFile);
|
||||||
file_dialog.setNameFilter("IFC Federation (*.ifcfed);;All Files (*)");
|
file_dialog.setNameFilter("IFC Federation (*.ifcfed);;All Files (*)");
|
||||||
@@ -294,25 +294,25 @@ bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
|||||||
|
|
||||||
const QString path = file_dialog.selectedFiles().value(0);
|
const QString path = file_dialog.selectedFiles().value(0);
|
||||||
if (path.isEmpty()) return false;
|
if (path.isEmpty()) return false;
|
||||||
return openProjectAt(s, host, vp, path);
|
return openProjectAt(session, host, viewport, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool openProjectPath(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path) {
|
bool openProjectPath(SessionState& session, QWidget& host, ViewportWindow& viewport, const QString& path) {
|
||||||
if (path.isEmpty()) return false;
|
if (path.isEmpty()) return false;
|
||||||
return openProjectAt(s, host, vp, path);
|
return openProjectAt(session, host, viewport, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
bool openCloudProject(SessionState& session, QWidget& host, ViewportWindow& viewport) {
|
||||||
SceneLoader* loader = s.loader();
|
SceneLoader* loader = session.loader();
|
||||||
if (loader && loader->isLoading()) {
|
if (loader && loader->isLoading()) {
|
||||||
QMessageBox::information(
|
QMessageBox::information(
|
||||||
&host, "Open from Cloud",
|
&host, "Open from Cloud",
|
||||||
"Wait until the current model load finishes before opening another project.");
|
"Wait until the current model load finishes before opening another project.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!confirmDiscardIfDirty(s, host)) return false;
|
if (!confirmDiscardIfDirty(session, host)) return false;
|
||||||
|
|
||||||
auto* registry = s.connectorRegistry();
|
auto* registry = session.connectorRegistry();
|
||||||
const auto& manifests = registry->available();
|
const auto& manifests = registry->available();
|
||||||
if (manifests.empty()) {
|
if (manifests.empty()) {
|
||||||
QMessageBox::information(&host, "Open from Cloud",
|
QMessageBox::information(&host, "Open from Cloud",
|
||||||
@@ -336,12 +336,12 @@ bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
s.beginProgress(QString("Opening project from %1...").arg(connector_id));
|
session.beginProgress(QString("Opening project from %1...").arg(connector_id));
|
||||||
s.setStatusMessage("Cloud", QString("Browsing %1...").arg(connector_id));
|
session.setStatusMessage("Cloud", QString("Browsing %1...").arg(connector_id));
|
||||||
|
|
||||||
QPointer<SessionState> sguard(&s);
|
QPointer<SessionState> sguard(&session);
|
||||||
QPointer<QWidget> hguard(&host);
|
QPointer<QWidget> hguard(&host);
|
||||||
QPointer<ViewportWindow> vguard(&vp);
|
QPointer<ViewportWindow> vguard(&viewport);
|
||||||
|
|
||||||
proc->call("pull_ifcfed_interactive", QJsonValue(),
|
proc->call("pull_ifcfed_interactive", QJsonValue(),
|
||||||
[sguard, hguard, vguard, connector_id](const QJsonValue& result) {
|
[sguard, hguard, vguard, connector_id](const QJsonValue& result) {
|
||||||
@@ -371,16 +371,16 @@ bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
bool syncCloudProject(SessionState& session, QWidget& host, ViewportWindow& viewport) {
|
||||||
auto* fed = s.federation();
|
auto* federation = session.federation();
|
||||||
|
|
||||||
// Per spec, sync has two independent phases — refreshing the .ifcfed
|
// Per spec, sync has two independent phases — refreshing the .ifcfed
|
||||||
// (requires manifest) and refreshing cloud models (requires any
|
// (requires manifest) and refreshing cloud models (requires any
|
||||||
// non-local source). Either is sufficient.
|
// non-local source). Either is sufficient.
|
||||||
const bool has_manifest = fed->hasManifest();
|
const bool has_manifest = federation->hasManifest();
|
||||||
bool has_cloud_models = false;
|
bool has_cloud_models = false;
|
||||||
for (const auto& m : fed->models()) {
|
for (const auto& model : federation->models()) {
|
||||||
if (m.source_connector != "local") { has_cloud_models = true; break; }
|
if (model.source_connector != "local") { has_cloud_models = true; break; }
|
||||||
}
|
}
|
||||||
if (!has_manifest && !has_cloud_models) {
|
if (!has_manifest && !has_cloud_models) {
|
||||||
QMessageBox::information(&host, "Sync From Cloud",
|
QMessageBox::information(&host, "Sync From Cloud",
|
||||||
@@ -388,7 +388,7 @@ bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SceneLoader* loader = s.loader();
|
SceneLoader* loader = session.loader();
|
||||||
if (loader && loader->isLoading()) {
|
if (loader && loader->isLoading()) {
|
||||||
QMessageBox::information(&host, "Sync From Cloud",
|
QMessageBox::information(&host, "Sync From Cloud",
|
||||||
"Wait until the current model load finishes before syncing.");
|
"Wait until the current model load finishes before syncing.");
|
||||||
@@ -399,23 +399,23 @@ bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
|||||||
// skipped). Just refresh cloud-sourced models against the .ifcfed
|
// skipped). Just refresh cloud-sourced models against the .ifcfed
|
||||||
// already on disk. Federation state is preserved, so no dirty prompt.
|
// already on disk. Federation state is preserved, so no dirty prompt.
|
||||||
if (!has_manifest) {
|
if (!has_manifest) {
|
||||||
s.setStatusMessage("Cloud", "Refreshing cloud models...");
|
session.setStatusMessage("Cloud", "Refreshing cloud models...");
|
||||||
resolveCloudModels(s, vp);
|
resolveCloudModels(session, viewport);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manifest path: the .ifcfed itself may be replaced. Confirm dirty —
|
// Manifest path: the .ifcfed itself may be replaced. Confirm dirty —
|
||||||
// even though we'll attempt to preserve the session if the returned
|
// even though we'll attempt to preserve the session if the returned
|
||||||
// .ifcfed is byte-equal, that's not known until after the round-trip.
|
// .ifcfed is byte-equal, that's not known until after the round-trip.
|
||||||
if (!confirmDiscardIfDirty(s, host)) return false;
|
if (!confirmDiscardIfDirty(session, host)) return false;
|
||||||
|
|
||||||
const QString connector_id = fed->manifestConnectorId();
|
const QString connector_id = federation->manifestConnectorId();
|
||||||
if (connector_id.isEmpty()) {
|
if (connector_id.isEmpty()) {
|
||||||
QMessageBox::warning(&host, "Sync From Cloud",
|
QMessageBox::warning(&host, "Sync From Cloud",
|
||||||
"The project's manifest does not name a connector.");
|
"The project's manifest does not name a connector.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto* registry = s.connectorRegistry();
|
auto* registry = session.connectorRegistry();
|
||||||
auto* proc = registry->get(connector_id);
|
auto* proc = registry->get(connector_id);
|
||||||
if (!proc) {
|
if (!proc) {
|
||||||
QMessageBox::warning(&host, "Sync From Cloud",
|
QMessageBox::warning(&host, "Sync From Cloud",
|
||||||
@@ -424,15 +424,15 @@ bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
s.beginProgress(QString("Syncing from %1...").arg(connector_id));
|
session.beginProgress(QString("Syncing from %1...").arg(connector_id));
|
||||||
s.setStatusMessage("Cloud", QString("Syncing from %1...").arg(connector_id));
|
session.setStatusMessage("Cloud", QString("Syncing from %1...").arg(connector_id));
|
||||||
|
|
||||||
QPointer<SessionState> sguard(&s);
|
QPointer<SessionState> sguard(&session);
|
||||||
QPointer<QWidget> hguard(&host);
|
QPointer<QWidget> hguard(&host);
|
||||||
QPointer<ViewportWindow> vguard(&vp);
|
QPointer<ViewportWindow> vguard(&viewport);
|
||||||
const QString current_path = fed->filePath();
|
const QString current_path = federation->filePath();
|
||||||
|
|
||||||
proc->call("pull_ifcfed", fed->manifest(),
|
proc->call("pull_ifcfed", federation->manifest(),
|
||||||
[sguard, hguard, vguard, connector_id, current_path](const QJsonValue& result) {
|
[sguard, hguard, vguard, connector_id, current_path](const QJsonValue& result) {
|
||||||
if (!sguard) return;
|
if (!sguard) return;
|
||||||
sguard->endProgress();
|
sguard->endProgress();
|
||||||
@@ -475,13 +475,13 @@ bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool saveProject(SessionState& s, QWidget& host) {
|
bool saveProject(SessionState& session, QWidget& host) {
|
||||||
if (s.federation()->filePath().isEmpty()) return saveProjectAs(s, host);
|
if (session.federation()->filePath().isEmpty()) return saveProjectAs(session, host);
|
||||||
return saveProjectTo(s, host, s.federation()->filePath());
|
return saveProjectTo(session, host, session.federation()->filePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool saveProjectAs(SessionState& s, QWidget& host) {
|
bool saveProjectAs(SessionState& session, QWidget& host) {
|
||||||
QString suggested = s.federation()->filePath();
|
QString suggested = session.federation()->filePath();
|
||||||
if (suggested.isEmpty()) suggested = "project.ifcfed";
|
if (suggested.isEmpty()) suggested = "project.ifcfed";
|
||||||
|
|
||||||
QFileDialog file_dialog(&host, "Save Project As", suggested);
|
QFileDialog file_dialog(&host, "Save Project As", suggested);
|
||||||
@@ -494,7 +494,7 @@ bool saveProjectAs(SessionState& s, QWidget& host) {
|
|||||||
QString path = file_dialog.selectedFiles().value(0);
|
QString path = file_dialog.selectedFiles().value(0);
|
||||||
if (path.isEmpty()) return false;
|
if (path.isEmpty()) return false;
|
||||||
if (!path.endsWith(".ifcfed", Qt::CaseInsensitive)) path += ".ifcfed";
|
if (!path.endsWith(".ifcfed", Qt::CaseInsensitive)) path += ".ifcfed";
|
||||||
return saveProjectTo(s, host, path);
|
return saveProjectTo(session, host, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -507,7 +507,7 @@ struct TempProjectFile {
|
|||||||
QString path;
|
QString path;
|
||||||
};
|
};
|
||||||
|
|
||||||
TempProjectFile writeProjectToTemp(SessionState& s, QWidget& host, const QString& op_title) {
|
TempProjectFile writeProjectToTemp(SessionState& session, QWidget& host, const QString& op_title) {
|
||||||
TempProjectFile out;
|
TempProjectFile out;
|
||||||
out.dir = std::make_shared<QTemporaryDir>();
|
out.dir = std::make_shared<QTemporaryDir>();
|
||||||
if (!out.dir->isValid()) {
|
if (!out.dir->isValid()) {
|
||||||
@@ -517,12 +517,12 @@ TempProjectFile writeProjectToTemp(SessionState& s, QWidget& host, const QString
|
|||||||
out.dir.reset();
|
out.dir.reset();
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
const QString name = s.federation()->filePath().isEmpty()
|
const QString name = session.federation()->filePath().isEmpty()
|
||||||
? "project.ifcfed"
|
? "project.ifcfed"
|
||||||
: QFileInfo(s.federation()->filePath()).fileName();
|
: QFileInfo(session.federation()->filePath()).fileName();
|
||||||
const QString tmp_path = QDir(out.dir->path()).filePath(name);
|
const QString tmp_path = QDir(out.dir->path()).filePath(name);
|
||||||
QString err;
|
QString err;
|
||||||
if (!s.federation()->writeCopyTo(tmp_path, &err)) {
|
if (!session.federation()->writeCopyTo(tmp_path, &err)) {
|
||||||
QMessageBox::warning(&host, op_title,
|
QMessageBox::warning(&host, op_title,
|
||||||
QString("Failed to write temporary project:\n%1").arg(err));
|
QString("Failed to write temporary project:\n%1").arg(err));
|
||||||
out.dir.reset();
|
out.dir.reset();
|
||||||
@@ -534,12 +534,12 @@ TempProjectFile writeProjectToTemp(SessionState& s, QWidget& host, const QString
|
|||||||
|
|
||||||
// Shared continuation for push_ifcfed[_interactive]: on success, repoint
|
// Shared continuation for push_ifcfed[_interactive]: on success, repoint
|
||||||
// Federation to the returned path and notify; on error, log + status.
|
// Federation to the returned path and notify; on error, log + status.
|
||||||
void onPushIfcfedResult(SessionState& s,
|
void onPushIfcfedResult(SessionState& session,
|
||||||
QWidget& host,
|
QWidget& host,
|
||||||
const QString& op_title,
|
const QString& op_title,
|
||||||
const QString& connector_id,
|
const QString& connector_id,
|
||||||
const QJsonValue& result) {
|
const QJsonValue& result) {
|
||||||
s.endProgress();
|
session.endProgress();
|
||||||
const QString new_path = result.toObject().value("path").toString();
|
const QString new_path = result.toObject().value("path").toString();
|
||||||
if (new_path.isEmpty()) {
|
if (new_path.isEmpty()) {
|
||||||
QMessageBox::warning(&host, op_title,
|
QMessageBox::warning(&host, op_title,
|
||||||
@@ -547,24 +547,24 @@ void onPushIfcfedResult(SessionState& s,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QStringList warnings;
|
QStringList warnings;
|
||||||
s.federation()->repointTo(new_path, &warnings);
|
session.federation()->repointTo(new_path, &warnings);
|
||||||
s.setStatusMessage("Cloud",
|
session.setStatusMessage("Cloud",
|
||||||
QString("Saved to %1 via %2")
|
QString("Saved to %1 via %2")
|
||||||
.arg(QFileInfo(new_path).fileName(), connector_id));
|
.arg(QFileInfo(new_path).fileName(), connector_id));
|
||||||
s.notifyProjectSaved(new_path);
|
session.notifyProjectSaved(new_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool saveCloudProject(SessionState& s, QWidget& host) {
|
bool saveCloudProject(SessionState& session, QWidget& host) {
|
||||||
auto* fed = s.federation();
|
auto* federation = session.federation();
|
||||||
if (!fed->hasManifest()) {
|
if (!federation->hasManifest()) {
|
||||||
QMessageBox::information(&host, "Save To Cloud",
|
QMessageBox::information(&host, "Save To Cloud",
|
||||||
"This project has no cloud target. Use \"Save As To Cloud\" first.");
|
"This project has no cloud target. Use \"Save As To Cloud\" first.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const QString connector_id = fed->manifestConnectorId();
|
const QString connector_id = federation->manifestConnectorId();
|
||||||
auto* registry = s.connectorRegistry();
|
auto* registry = session.connectorRegistry();
|
||||||
auto* proc = registry->get(connector_id);
|
auto* proc = registry->get(connector_id);
|
||||||
if (!proc) {
|
if (!proc) {
|
||||||
QMessageBox::warning(&host, "Save To Cloud",
|
QMessageBox::warning(&host, "Save To Cloud",
|
||||||
@@ -573,26 +573,26 @@ bool saveCloudProject(SessionState& s, QWidget& host) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tmp = writeProjectToTemp(s, host, "Save To Cloud");
|
auto temporary_project = writeProjectToTemp(session, host, "Save To Cloud");
|
||||||
if (!tmp.dir) return false;
|
if (!temporary_project.dir) return false;
|
||||||
|
|
||||||
QJsonObject params;
|
QJsonObject params;
|
||||||
params["path"] = tmp.path;
|
params["path"] = temporary_project.path;
|
||||||
params["manifest"] = fed->manifest();
|
params["manifest"] = federation->manifest();
|
||||||
|
|
||||||
s.beginProgress(QString("Saving to %1...").arg(connector_id));
|
session.beginProgress(QString("Saving to %1...").arg(connector_id));
|
||||||
s.setStatusMessage("Cloud", QString("Saving to %1...").arg(connector_id));
|
session.setStatusMessage("Cloud", QString("Saving to %1...").arg(connector_id));
|
||||||
|
|
||||||
QPointer<SessionState> sguard(&s);
|
QPointer<SessionState> sguard(&session);
|
||||||
QPointer<QWidget> hguard(&host);
|
QPointer<QWidget> hguard(&host);
|
||||||
|
|
||||||
proc->call("push_ifcfed", params,
|
proc->call("push_ifcfed", params,
|
||||||
[sguard, hguard, connector_id, tmp_keepalive = tmp.dir](const QJsonValue& result) {
|
[sguard, hguard, connector_id, tmp_keepalive = temporary_project.dir](const QJsonValue& result) {
|
||||||
(void)tmp_keepalive;
|
(void)tmp_keepalive;
|
||||||
if (!sguard || !hguard) return;
|
if (!sguard || !hguard) return;
|
||||||
onPushIfcfedResult(*sguard, *hguard, "Save To Cloud", connector_id, result);
|
onPushIfcfedResult(*sguard, *hguard, "Save To Cloud", connector_id, result);
|
||||||
},
|
},
|
||||||
[sguard, connector_id, tmp_keepalive = tmp.dir](int code, const QString& message) {
|
[sguard, connector_id, tmp_keepalive = temporary_project.dir](int code, const QString& message) {
|
||||||
(void)tmp_keepalive;
|
(void)tmp_keepalive;
|
||||||
qWarning() << "push_ifcfed to" << connector_id
|
qWarning() << "push_ifcfed to" << connector_id
|
||||||
<< "failed:" << code << message;
|
<< "failed:" << code << message;
|
||||||
@@ -605,8 +605,8 @@ bool saveCloudProject(SessionState& s, QWidget& host) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool saveAsCloudProject(SessionState& s, QWidget& host) {
|
bool saveAsCloudProject(SessionState& session, QWidget& host) {
|
||||||
auto* registry = s.connectorRegistry();
|
auto* registry = session.connectorRegistry();
|
||||||
const auto& manifests = registry->available();
|
const auto& manifests = registry->available();
|
||||||
if (manifests.empty()) {
|
if (manifests.empty()) {
|
||||||
QMessageBox::information(&host, "Save As To Cloud",
|
QMessageBox::information(&host, "Save As To Cloud",
|
||||||
@@ -629,25 +629,25 @@ bool saveAsCloudProject(SessionState& s, QWidget& host) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tmp = writeProjectToTemp(s, host, "Save As To Cloud");
|
auto temporary_project = writeProjectToTemp(session, host, "Save As To Cloud");
|
||||||
if (!tmp.dir) return false;
|
if (!temporary_project.dir) return false;
|
||||||
|
|
||||||
QJsonObject params;
|
QJsonObject params;
|
||||||
params["path"] = tmp.path;
|
params["path"] = temporary_project.path;
|
||||||
|
|
||||||
s.beginProgress(QString("Pushing to %1...").arg(connector_id));
|
session.beginProgress(QString("Pushing to %1...").arg(connector_id));
|
||||||
s.setStatusMessage("Cloud", QString("Pushing to %1...").arg(connector_id));
|
session.setStatusMessage("Cloud", QString("Pushing to %1...").arg(connector_id));
|
||||||
|
|
||||||
QPointer<SessionState> sguard(&s);
|
QPointer<SessionState> sguard(&session);
|
||||||
QPointer<QWidget> hguard(&host);
|
QPointer<QWidget> hguard(&host);
|
||||||
|
|
||||||
proc->call("push_ifcfed_interactive", params,
|
proc->call("push_ifcfed_interactive", params,
|
||||||
[sguard, hguard, connector_id, tmp_keepalive = tmp.dir](const QJsonValue& result) {
|
[sguard, hguard, connector_id, tmp_keepalive = temporary_project.dir](const QJsonValue& result) {
|
||||||
(void)tmp_keepalive;
|
(void)tmp_keepalive;
|
||||||
if (!sguard || !hguard) return;
|
if (!sguard || !hguard) return;
|
||||||
onPushIfcfedResult(*sguard, *hguard, "Save As To Cloud", connector_id, result);
|
onPushIfcfedResult(*sguard, *hguard, "Save As To Cloud", connector_id, result);
|
||||||
},
|
},
|
||||||
[sguard, connector_id, tmp_keepalive = tmp.dir](int code, const QString& message) {
|
[sguard, connector_id, tmp_keepalive = temporary_project.dir](int code, const QString& message) {
|
||||||
(void)tmp_keepalive;
|
(void)tmp_keepalive;
|
||||||
qWarning() << "push_ifcfed_interactive to" << connector_id
|
qWarning() << "push_ifcfed_interactive to" << connector_id
|
||||||
<< "failed:" << code << message;
|
<< "failed:" << code << message;
|
||||||
@@ -660,14 +660,14 @@ bool saveAsCloudProject(SessionState& s, QWidget& host) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool saveProjectDialog(SessionState& s, QWidget& host) {
|
bool saveProjectDialog(SessionState& session, QWidget& host) {
|
||||||
SaveProjectDialog dialog(s.federation()->hasManifest(), &host);
|
SaveProjectDialog dialog(session.federation()->hasManifest(), &host);
|
||||||
if (dialog.exec() != QDialog::Accepted) return false;
|
if (dialog.exec() != QDialog::Accepted) return false;
|
||||||
switch (dialog.selectedTarget()) {
|
switch (dialog.selectedTarget()) {
|
||||||
case SaveTarget::Local: return saveProject(s, host);
|
case SaveTarget::Local: return saveProject(session, host);
|
||||||
case SaveTarget::LocalAs: return saveProjectAs(s, host);
|
case SaveTarget::LocalAs: return saveProjectAs(session, host);
|
||||||
case SaveTarget::Cloud: return saveCloudProject(s, host);
|
case SaveTarget::Cloud: return saveCloudProject(session, host);
|
||||||
case SaveTarget::CloudAs: return saveAsCloudProject(s, host);
|
case SaveTarget::CloudAs: return saveAsCloudProject(session, host);
|
||||||
case SaveTarget::None: return false;
|
case SaveTarget::None: return false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -32,35 +32,35 @@ namespace bonsaiviewer::modules::project::commands {
|
|||||||
// User-facing commands. Each owns its own dialogs and confirmations; each
|
// User-facing commands. Each owns its own dialogs and confirmations; each
|
||||||
// emits exactly one notify() at the end (projectReset / projectOpened /
|
// emits exactly one notify() at the end (projectReset / projectOpened /
|
||||||
// projectSaved) so views refresh once per command.
|
// projectSaved) so views refresh once per command.
|
||||||
bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
bool newProject(SessionState& session, QWidget& host, ViewportWindow& viewport);
|
||||||
bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
bool openProject(SessionState& session, QWidget& host, ViewportWindow& viewport);
|
||||||
// Open a specific .ifcfed by path, bypassing the file dialog. Used by the
|
// Open a specific .ifcfed by path, bypassing the file dialog. Used by the
|
||||||
// "Open Recent" menu. Same dirty-check / load / cloud-resolve flow as
|
// "Open Recent" menu. Same dirty-check / load / cloud-resolve flow as
|
||||||
// openProject; returns false if the load failed or was cancelled.
|
// openProject; returns false if the load failed or was cancelled.
|
||||||
bool openProjectPath(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path);
|
bool openProjectPath(SessionState& session, QWidget& host, ViewportWindow& viewport, const QString& path);
|
||||||
// Pick a connector, then call pull_ifcfed_interactive and open the resulting
|
// Pick a connector, then call pull_ifcfed_interactive and open the resulting
|
||||||
// .ifcfed as a fresh project. Non-local models in the loaded federation are
|
// .ifcfed as a fresh project. Non-local models in the loaded federation are
|
||||||
// resolved asynchronously via pull_models.
|
// resolved asynchronously via pull_models.
|
||||||
bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
bool openCloudProject(SessionState& session, QWidget& host, ViewportWindow& viewport);
|
||||||
// pull_ifcfed using the current project's .ifcfed.manifest. Re-downloads
|
// pull_ifcfed using the current project's .ifcfed.manifest. Re-downloads
|
||||||
// the .ifcfed from the same cloud target it came from (typically without
|
// the .ifcfed from the same cloud target it came from (typically without
|
||||||
// user interaction), then opens it like a fresh project — discarding any
|
// user interaction), then opens it like a fresh project — discarding any
|
||||||
// local edits after the usual dirty-check prompt.
|
// local edits after the usual dirty-check prompt.
|
||||||
bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
bool syncCloudProject(SessionState& session, QWidget& host, ViewportWindow& viewport);
|
||||||
bool saveProject(SessionState& s, QWidget& host);
|
bool saveProject(SessionState& session, QWidget& host);
|
||||||
bool saveProjectAs(SessionState& s, QWidget& host);
|
bool saveProjectAs(SessionState& session, QWidget& host);
|
||||||
// Push the current federation to the cloud target named in its manifest
|
// Push the current federation to the cloud target named in its manifest
|
||||||
// (push_ifcfed). No user prompt for destination. Caller is responsible for
|
// (push_ifcfed). No user prompt for destination. Caller is responsible for
|
||||||
// gating this on Federation::hasManifest.
|
// gating this on Federation::hasManifest.
|
||||||
bool saveCloudProject(SessionState& s, QWidget& host);
|
bool saveCloudProject(SessionState& session, QWidget& host);
|
||||||
// Pick a connector and push the current federation to a fresh cloud target
|
// Pick a connector and push the current federation to a fresh cloud target
|
||||||
// (push_ifcfed_interactive). The connector returns a new path + manifest;
|
// (push_ifcfed_interactive). The connector returns a new path + manifest;
|
||||||
// Federation repoints to that location.
|
// Federation repoints to that location.
|
||||||
bool saveAsCloudProject(SessionState& s, QWidget& host);
|
bool saveAsCloudProject(SessionState& session, QWidget& host);
|
||||||
// Show the four-way Save dialog (Local / Save As Local / To Cloud / Save
|
// Show the four-way Save dialog (Local / Save As Local / To Cloud / Save
|
||||||
// As To Cloud) and dispatch to one of the above. This is what the "Save
|
// As To Cloud) and dispatch to one of the above. This is what the "Save
|
||||||
// Project" ribbon button is wired to.
|
// Project" ribbon button is wired to.
|
||||||
bool saveProjectDialog(SessionState& s, QWidget& host);
|
bool saveProjectDialog(SessionState& session, QWidget& host);
|
||||||
|
|
||||||
} // namespace bonsaiviewer::modules::project::commands
|
} // namespace bonsaiviewer::modules::project::commands
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@
|
|||||||
|
|
||||||
namespace bonsaiviewer::modules::viewport::commands {
|
namespace bonsaiviewer::modules::viewport::commands {
|
||||||
|
|
||||||
void setHome(SessionState& session, ViewportWindow& vp) {
|
void setHome(SessionState& session, ViewportWindow& viewport) {
|
||||||
auto camera = vp.cameraState();
|
auto camera = viewport.cameraState();
|
||||||
Federation::HomeView home_view;
|
Federation::HomeView home_view;
|
||||||
home_view.target = camera.target;
|
home_view.target = camera.target;
|
||||||
home_view.distance = camera.distance;
|
home_view.distance = camera.distance;
|
||||||
@@ -37,66 +37,66 @@ void setHome(SessionState& session, ViewportWindow& vp) {
|
|||||||
session.setStatusMessage("Camera", "Home view updated");
|
session.setStatusMessage("Camera", "Home view updated");
|
||||||
}
|
}
|
||||||
|
|
||||||
void goHome(SessionState& session, ViewportWindow& vp) {
|
void goHome(SessionState& session, ViewportWindow& viewport) {
|
||||||
Federation* federation = session.federation();
|
Federation* federation = session.federation();
|
||||||
if (!federation->hasHomeView()) {
|
if (!federation->hasHomeView()) {
|
||||||
session.setStatusMessage("Camera", "No home view set for this project");
|
session.setStatusMessage("Camera", "No home view set for this project");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto& home_view = federation->homeView();
|
const auto& home_view = federation->homeView();
|
||||||
vp.setCamera(
|
viewport.setCamera(
|
||||||
home_view.target.x(), home_view.target.y(), home_view.target.z(),
|
home_view.target.x(), home_view.target.y(), home_view.target.z(),
|
||||||
home_view.distance, home_view.yaw, home_view.pitch);
|
home_view.distance, home_view.yaw, home_view.pitch);
|
||||||
session.setStatusMessage("Camera", "Home view restored");
|
session.setStatusMessage("Camera", "Home view restored");
|
||||||
}
|
}
|
||||||
|
|
||||||
void viewSelected(ViewportWindow& vp) {
|
void viewSelected(ViewportWindow& viewport) {
|
||||||
vp.focusOnSelectedObject();
|
viewport.focusOnSelectedObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
void fly(SessionState& session, ViewportWindow& vp) {
|
void fly(SessionState& session, ViewportWindow& viewport) {
|
||||||
vp.requestActivate();
|
viewport.requestActivate();
|
||||||
vp.enterFpsMode();
|
viewport.enterFpsMode();
|
||||||
session.setStatusMessage("Mode", "Fly mode active");
|
session.setStatusMessage("Mode", "Fly mode active");
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleSection(SessionState& session, ViewportWindow& vp) {
|
void toggleSection(SessionState& session, ViewportWindow& viewport) {
|
||||||
vp.toggleSectionTool();
|
viewport.toggleSectionTool();
|
||||||
session.setStatusMessage("Section",
|
session.setStatusMessage("Section",
|
||||||
vp.sectionToolActive() ? "Section tool active" : "Section tool off");
|
viewport.sectionToolActive() ? "Section tool active" : "Section tool off");
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearSection(SessionState& session, ViewportWindow& vp) {
|
void clearSection(SessionState& session, ViewportWindow& viewport) {
|
||||||
vp.clearSectionPlanes();
|
viewport.clearSectionPlanes();
|
||||||
session.setStatusMessage("Section", "Section planes cleared");
|
session.setStatusMessage("Section", "Section planes cleared");
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleDistance(ViewportWindow& vp) {
|
void toggleDistance(ViewportWindow& viewport) {
|
||||||
vp.toggleLengthTool();
|
viewport.toggleLengthTool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleArea(ViewportWindow& vp) {
|
void toggleArea(ViewportWindow& viewport) {
|
||||||
vp.toggleAreaTool();
|
viewport.toggleAreaTool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleVolume(ViewportWindow& vp) {
|
void toggleVolume(ViewportWindow& viewport) {
|
||||||
vp.toggleVolumeTool();
|
viewport.toggleVolumeTool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void hideSelected(ViewportWindow& vp) {
|
void hideSelected(ViewportWindow& viewport) {
|
||||||
vp.hideSelectedElements();
|
viewport.hideSelectedElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
void isolateSelected(ViewportWindow& vp) {
|
void isolateSelected(ViewportWindow& viewport) {
|
||||||
vp.isolateSelectedElements();
|
viewport.isolateSelectedElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
void showAll(ViewportWindow& vp) {
|
void showAll(ViewportWindow& viewport) {
|
||||||
vp.showAllElements();
|
viewport.showAllElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
void invertVisibility(ViewportWindow& vp) {
|
void invertVisibility(ViewportWindow& viewport) {
|
||||||
vp.invertElementVisibility();
|
viewport.invertElementVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace bonsaiviewer::modules::viewport::commands
|
} // namespace bonsaiviewer::modules::viewport::commands
|
||||||
|
|||||||
@@ -26,22 +26,22 @@ namespace bonsaiviewer { class SessionState; }
|
|||||||
|
|
||||||
namespace bonsaiviewer::modules::viewport::commands {
|
namespace bonsaiviewer::modules::viewport::commands {
|
||||||
|
|
||||||
void setHome(SessionState& session, ViewportWindow& vp);
|
void setHome(SessionState& session, ViewportWindow& viewport);
|
||||||
void goHome(SessionState& session, ViewportWindow& vp);
|
void goHome(SessionState& session, ViewportWindow& viewport);
|
||||||
void viewSelected(ViewportWindow& vp);
|
void viewSelected(ViewportWindow& viewport);
|
||||||
|
|
||||||
void fly(SessionState& session, ViewportWindow& vp);
|
void fly(SessionState& session, ViewportWindow& viewport);
|
||||||
void toggleSection(SessionState& session, ViewportWindow& vp);
|
void toggleSection(SessionState& session, ViewportWindow& viewport);
|
||||||
void clearSection(SessionState& session, ViewportWindow& vp);
|
void clearSection(SessionState& session, ViewportWindow& viewport);
|
||||||
|
|
||||||
void toggleDistance(ViewportWindow& vp);
|
void toggleDistance(ViewportWindow& viewport);
|
||||||
void toggleArea(ViewportWindow& vp);
|
void toggleArea(ViewportWindow& viewport);
|
||||||
void toggleVolume(ViewportWindow& vp);
|
void toggleVolume(ViewportWindow& viewport);
|
||||||
|
|
||||||
void hideSelected(ViewportWindow& vp);
|
void hideSelected(ViewportWindow& viewport);
|
||||||
void isolateSelected(ViewportWindow& vp);
|
void isolateSelected(ViewportWindow& viewport);
|
||||||
void showAll(ViewportWindow& vp);
|
void showAll(ViewportWindow& viewport);
|
||||||
void invertVisibility(ViewportWindow& vp);
|
void invertVisibility(ViewportWindow& viewport);
|
||||||
|
|
||||||
} // namespace bonsaiviewer::modules::viewport::commands
|
} // namespace bonsaiviewer::modules::viewport::commands
|
||||||
|
|
||||||
|
|||||||
@@ -67,9 +67,9 @@ ViewportView::ViewportView(bonsaiviewer::SessionState* session_state,
|
|||||||
// first geometry-ready consumes the arm). refresh() stays terminal —
|
// first geometry-ready consumes the arm). refresh() stays terminal —
|
||||||
// any federation mutation from the guess propagates through
|
// any federation mutation from the guess propagates through
|
||||||
// SessionState's federatedFalseOriginChanged relay.
|
// SessionState's federatedFalseOriginChanged relay.
|
||||||
connect(session_state_, &SessionState::modelGeometryReady, this, [this](uint32_t mid) {
|
connect(session_state_, &SessionState::modelGeometryReady, this, [this](uint32_t model_id) {
|
||||||
if (modules::models::consumeFederatedFalseOriginGuess()) {
|
if (modules::models::consumeFederatedFalseOriginGuess()) {
|
||||||
guessFederatedFalseOriginFromFirstModel(mid);
|
guessFederatedFalseOriginFromFirstModel(model_id);
|
||||||
}
|
}
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
@@ -136,34 +136,34 @@ void ViewportView::refresh() {
|
|||||||
viewport_->setFederatedFalseOrigin(
|
viewport_->setFederatedFalseOrigin(
|
||||||
composeFederatedFalseOrigin(federation->federatedFalseOrigin(), federation->config()));
|
composeFederatedFalseOrigin(federation->federatedFalseOrigin(), federation->config()));
|
||||||
|
|
||||||
for (uint32_t mid : session_state_->modelIds()) {
|
for (uint32_t model_id : session_state_->modelIds()) {
|
||||||
applyCoordinateOperation(mid);
|
applyCoordinateOperation(model_id);
|
||||||
applyModelVisibility(mid);
|
applyModelVisibility(model_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewportView::applyCoordinateOperation(uint32_t mid) {
|
void ViewportView::applyCoordinateOperation(uint32_t model_id) {
|
||||||
SceneLoader* loader = session_state_->loader();
|
SceneLoader* loader = session_state_->loader();
|
||||||
Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity();
|
Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity();
|
||||||
if (const ModelGeoref* georef = loader->modelGeoref(mid)) {
|
if (const ModelGeoref* georef = loader->modelGeoref(model_id)) {
|
||||||
if (georef->has_coordinate_operation) {
|
if (georef->has_coordinate_operation) {
|
||||||
matrix = georef->coordinate_operation_meters;
|
matrix = georef->coordinate_operation_meters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewport_->setModelCoordinateOperation(mid, matrix);
|
viewport_->setModelCoordinateOperation(model_id, matrix);
|
||||||
applyModelTransformation(mid);
|
applyModelTransformation(model_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewportView::applyModelTransformation(uint32_t mid) {
|
void ViewportView::applyModelTransformation(uint32_t model_id) {
|
||||||
Federation* federation = session_state_->federation();
|
Federation* federation = session_state_->federation();
|
||||||
SceneLoader* loader = session_state_->loader();
|
SceneLoader* loader = session_state_->loader();
|
||||||
Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity();
|
Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity();
|
||||||
const QString fed_id = session_state_->fedIdForModelId(mid);
|
const QString fed_id = session_state_->fedIdForModelId(model_id);
|
||||||
if (!fed_id.isEmpty()) {
|
if (!fed_id.isEmpty()) {
|
||||||
if (const Federation::Model* model = federation->findById(fed_id)) {
|
if (const Federation::Model* model = federation->findById(fed_id)) {
|
||||||
ModelUnits units;
|
ModelUnits units;
|
||||||
Eigen::Matrix4d coordinate_operation = Eigen::Matrix4d::Identity();
|
Eigen::Matrix4d coordinate_operation = Eigen::Matrix4d::Identity();
|
||||||
if (const ModelGeoref* georef = loader->modelGeoref(mid)) {
|
if (const ModelGeoref* georef = loader->modelGeoref(model_id)) {
|
||||||
units = georef->units;
|
units = georef->units;
|
||||||
if (georef->has_coordinate_operation) {
|
if (georef->has_coordinate_operation) {
|
||||||
coordinate_operation = georef->coordinate_operation_meters;
|
coordinate_operation = georef->coordinate_operation_meters;
|
||||||
@@ -173,18 +173,18 @@ void ViewportView::applyModelTransformation(uint32_t mid) {
|
|||||||
model->model_transformation, federation->config(), units, coordinate_operation);
|
model->model_transformation, federation->config(), units, coordinate_operation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewport_->setModelTransformation(mid, matrix);
|
viewport_->setModelTransformation(model_id, matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewportView::applyModelVisibility(uint32_t mid) {
|
void ViewportView::applyModelVisibility(uint32_t model_id) {
|
||||||
Federation* federation = session_state_->federation();
|
Federation* federation = session_state_->federation();
|
||||||
const QString fed_id = session_state_->fedIdForModelId(mid);
|
const QString fed_id = session_state_->fedIdForModelId(model_id);
|
||||||
if (fed_id.isEmpty()) return;
|
if (fed_id.isEmpty()) return;
|
||||||
|
|
||||||
if (federation->isModelEffectivelyVisible(fed_id)) {
|
if (federation->isModelEffectivelyVisible(fed_id)) {
|
||||||
viewport_->showModel(mid);
|
viewport_->showModel(model_id);
|
||||||
} else {
|
} else {
|
||||||
viewport_->hideModel(mid);
|
viewport_->hideModel(model_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ void ViewportView::applyModelVisibility(uint32_t mid) {
|
|||||||
// mutation here propagates through SessionState's federation relay
|
// mutation here propagates through SessionState's federation relay
|
||||||
// (federatedFalseOriginChanged → notifyFederationChanged) without
|
// (federatedFalseOriginChanged → notifyFederationChanged) without
|
||||||
// re-entering this function.
|
// re-entering this function.
|
||||||
void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t mid) {
|
void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t model_id) {
|
||||||
Federation* federation = session_state_->federation();
|
Federation* federation = session_state_->federation();
|
||||||
if (!federation->filePath().isEmpty()) return;
|
if (!federation->filePath().isEmpty()) return;
|
||||||
|
|
||||||
@@ -218,10 +218,10 @@ void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t mid) {
|
|||||||
if (current.xyz != defaults.xyz || current.rz_deg != defaults.rz_deg) return;
|
if (current.xyz != defaults.xyz || current.rz_deg != defaults.rz_deg) return;
|
||||||
|
|
||||||
Eigen::Vector3d first_geometry_point_m;
|
Eigen::Vector3d first_geometry_point_m;
|
||||||
if (!viewport_->firstGeometryPointWorldM(mid, first_geometry_point_m)) return;
|
if (!viewport_->firstGeometryPointWorldM(model_id, first_geometry_point_m)) return;
|
||||||
|
|
||||||
SceneLoader* loader = session_state_->loader();
|
SceneLoader* loader = session_state_->loader();
|
||||||
const ModelGeoref* georef = loader->modelGeoref(mid);
|
const ModelGeoref* georef = loader->modelGeoref(model_id);
|
||||||
if (georef == nullptr) return;
|
if (georef == nullptr) return;
|
||||||
|
|
||||||
federation->setFederatedFalseOrigin(::guessFederatedFalseOrigin(
|
federation->setFederatedFalseOrigin(::guessFederatedFalseOrigin(
|
||||||
@@ -236,42 +236,42 @@ void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t mid) {
|
|||||||
// (0,0,0) — the federated false origin in render space — capped at
|
// (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
|
// 100 m so a model with crazy-coord geometry can't pull the camera
|
||||||
// back into nothing.
|
// back into nothing.
|
||||||
viewport_->frameOnFederatedOrigin(mid, 100.0f);
|
viewport_->frameOnFederatedOrigin(model_id, 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewportView::updateVolumeReadout() {
|
void ViewportView::updateVolumeReadout() {
|
||||||
if (viewport_->toolMode() != ViewportWindow::ToolMode::Volume) return;
|
if (viewport_->toolMode() != ViewportWindow::ToolMode::Volume) return;
|
||||||
|
|
||||||
const auto& sel = viewport_->selection().selectionIds();
|
const auto& selection_ids = viewport_->selection().selectionIds();
|
||||||
if (sel.empty()) {
|
if (selection_ids.empty()) {
|
||||||
viewport_->setHudText(std::string());
|
viewport_->setHudText(std::string());
|
||||||
viewport_->setOverlayLabels({});
|
viewport_->setOverlayLabels({});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint32_t> ids(sel.begin(), sel.end());
|
std::vector<uint32_t> object_ids(selection_ids.begin(), selection_ids.end());
|
||||||
const auto per_obj = volumesPerObject(*viewport_, ids);
|
const auto volumes_by_object = volumesPerObject(*viewport_, object_ids);
|
||||||
|
|
||||||
double total = 0.0;
|
double total = 0.0;
|
||||||
std::vector<OverlayRenderer::Label> labels;
|
std::vector<OverlayRenderer::Label> labels;
|
||||||
labels.reserve(per_obj.size());
|
labels.reserve(volumes_by_object.size());
|
||||||
for (const auto& [oid, v] : per_obj) {
|
for (const auto& [object_id, volume] : volumes_by_object) {
|
||||||
total += v;
|
total += volume;
|
||||||
Eigen::Vector3f mn, mx;
|
Eigen::Vector3f mn, mx;
|
||||||
if (!viewport_->computeObjectAabb(oid, mn, mx)) continue;
|
if (!viewport_->computeObjectAabb(object_id, mn, mx)) continue;
|
||||||
OverlayRenderer::Label lbl;
|
OverlayRenderer::Label lbl;
|
||||||
const Eigen::Vector3f c = (mn + mx) * 0.5f;
|
const Eigen::Vector3f center = (mn + mx) * 0.5f;
|
||||||
lbl.world_pos[0] = c.x();
|
lbl.world_pos[0] = center.x();
|
||||||
lbl.world_pos[1] = c.y();
|
lbl.world_pos[1] = center.y();
|
||||||
lbl.world_pos[2] = c.z();
|
lbl.world_pos[2] = center.z();
|
||||||
lbl.text = QString::number(v, 'f', 4) + " m³";
|
lbl.text = QString::number(volume, 'f', 4) + " m³";
|
||||||
labels.push_back(std::move(lbl));
|
labels.push_back(std::move(lbl));
|
||||||
}
|
}
|
||||||
|
|
||||||
viewport_->setHudText(QString("Volume: %1 m³ (%2 object%3)")
|
viewport_->setHudText(QString("Volume: %1 m³ (%2 object%3)")
|
||||||
.arg(total, 0, 'f', 4)
|
.arg(total, 0, 'f', 4)
|
||||||
.arg(per_obj.size())
|
.arg(volumes_by_object.size())
|
||||||
.arg(per_obj.size() == 1 ? "" : "s").toStdString());
|
.arg(volumes_by_object.size() == 1 ? "" : "s").toStdString());
|
||||||
viewport_->setOverlayLabels(labels);
|
viewport_->setOverlayLabels(labels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void refresh();
|
void refresh();
|
||||||
void applyCoordinateOperation(uint32_t mid);
|
void applyCoordinateOperation(uint32_t model_id);
|
||||||
void applyModelTransformation(uint32_t mid);
|
void applyModelTransformation(uint32_t model_id);
|
||||||
void applyModelVisibility(uint32_t mid);
|
void applyModelVisibility(uint32_t model_id);
|
||||||
void guessFederatedFalseOriginFromFirstModel(uint32_t mid);
|
void guessFederatedFalseOriginFromFirstModel(uint32_t model_id);
|
||||||
void updateVolumeReadout();
|
void updateVolumeReadout();
|
||||||
|
|
||||||
bonsaiviewer::SessionState* session_state_ = nullptr;
|
bonsaiviewer::SessionState* session_state_ = nullptr;
|
||||||
|
|||||||
@@ -82,10 +82,11 @@ int main(int argc, char* argv[]) {
|
|||||||
const QStringList parts = parser.value("camera").split(',');
|
const QStringList parts = parser.value("camera").split(',');
|
||||||
if (parts.size() == 6) {
|
if (parts.size() == 6) {
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
float v[6];
|
float camera_values[6];
|
||||||
for (int i = 0; i < 6 && ok; ++i) v[i] = parts[i].toFloat(&ok);
|
for (int i = 0; i < 6 && ok; ++i) camera_values[i] = parts[i].toFloat(&ok);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
viewport->setCamera(v[0], v[1], v[2], v[3], v[4], v[5]);
|
viewport->setCamera(camera_values[0], camera_values[1], camera_values[2],
|
||||||
|
camera_values[3], camera_values[4], camera_values[5]);
|
||||||
} else {
|
} else {
|
||||||
Log::warn() << "--camera: failed to parse "
|
Log::warn() << "--camera: failed to parse "
|
||||||
<< parser.value("camera");
|
<< parser.value("camera");
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ void BufferPool::configure(WGPUInstance instance, WGPUDevice device,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BufferPool::destroy() {
|
void BufferPool::destroy() {
|
||||||
for (auto& sp : sub_pools_) {
|
for (auto& sub_pool : sub_pools_) {
|
||||||
if (sp.buffer) wgpuBufferRelease(sp.buffer);
|
if (sub_pool.buffer) wgpuBufferRelease(sub_pool.buffer);
|
||||||
}
|
}
|
||||||
sub_pools_.clear();
|
sub_pools_.clear();
|
||||||
device_ = nullptr;
|
device_ = nullptr;
|
||||||
@@ -140,7 +140,7 @@ bool BufferPool::addSubBuffer() {
|
|||||||
WGPUBuffer buf = wgpuDeviceCreateBuffer(device_, &desc);
|
WGPUBuffer buf = wgpuDeviceCreateBuffer(device_, &desc);
|
||||||
|
|
||||||
struct PopResult { bool done = false; bool error = false; };
|
struct PopResult { bool done = false; bool error = false; };
|
||||||
auto pop = [&](PopResult& pr) {
|
auto pop = [&](PopResult& pop_result) {
|
||||||
WGPUPopErrorScopeCallbackInfo pcb = {};
|
WGPUPopErrorScopeCallbackInfo pcb = {};
|
||||||
pcb.mode = WGPUCallbackMode_AllowProcessEvents;
|
pcb.mode = WGPUCallbackMode_AllowProcessEvents;
|
||||||
pcb.callback = [](WGPUPopErrorScopeStatus, WGPUErrorType type,
|
pcb.callback = [](WGPUPopErrorScopeStatus, WGPUErrorType type,
|
||||||
@@ -149,9 +149,9 @@ bool BufferPool::addSubBuffer() {
|
|||||||
p->done = true;
|
p->done = true;
|
||||||
p->error = (type != WGPUErrorType_NoError);
|
p->error = (type != WGPUErrorType_NoError);
|
||||||
};
|
};
|
||||||
pcb.userdata1 = ≺
|
pcb.userdata1 = &pop_result;
|
||||||
wgpuDevicePopErrorScope(device_, pcb);
|
wgpuDevicePopErrorScope(device_, pcb);
|
||||||
while (!pr.done) wgpuInstanceProcessEvents(instance_);
|
while (!pop_result.done) wgpuInstanceProcessEvents(instance_);
|
||||||
};
|
};
|
||||||
PopResult oom_pop, validation_pop;
|
PopResult oom_pop, validation_pop;
|
||||||
pop(oom_pop);
|
pop(oom_pop);
|
||||||
@@ -205,11 +205,12 @@ void BufferPool::resolveProvisionalGrowth(bool failed) {
|
|||||||
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)),
|
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)),
|
||||||
sub_pools_.size());
|
sub_pools_.size());
|
||||||
} else {
|
} else {
|
||||||
sub_pools_[i].provisional = false;
|
SubPool& sub_pool = sub_pools_[i];
|
||||||
|
sub_pool.provisional = false;
|
||||||
std::fprintf(stderr,
|
std::fprintf(stderr,
|
||||||
"[wgpu pool] added sub-buffer %zu (%llu MB); pool total now %llu MB\n",
|
"[wgpu pool] added sub-buffer %zu (%llu MB); pool total now %llu MB\n",
|
||||||
i,
|
i,
|
||||||
(unsigned long long)(sub_pools_[i].capacity / (1024 * 1024)),
|
(unsigned long long)(sub_pool.capacity / (1024 * 1024)),
|
||||||
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)));
|
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -225,34 +226,34 @@ BufferPool::Slice BufferPool::alloc(uint64_t size, uint64_t align) {
|
|||||||
// adding another sub-buffer and retry once.
|
// adding another sub-buffer and retry once.
|
||||||
for (int attempt = 0; attempt < 2; ++attempt) {
|
for (int attempt = 0; attempt < 2; ++attempt) {
|
||||||
for (size_t sp_idx = 0; sp_idx < sub_pools_.size(); ++sp_idx) {
|
for (size_t sp_idx = 0; sp_idx < sub_pools_.size(); ++sp_idx) {
|
||||||
SubPool& sp = sub_pools_[sp_idx];
|
SubPool& sub_pool = sub_pools_[sp_idx];
|
||||||
// Web: never allocate out of a sub-buffer still awaiting OOM
|
// Web: never allocate out of a sub-buffer still awaiting OOM
|
||||||
// validation — its handle may be a Dawn error buffer.
|
// validation — its handle may be a Dawn error buffer.
|
||||||
if (sp.provisional) continue;
|
if (sub_pool.provisional) continue;
|
||||||
for (size_t i = 0; i < sp.free_ranges.size(); ++i) {
|
for (size_t i = 0; i < sub_pool.free_ranges.size(); ++i) {
|
||||||
const FreeRange& r = sp.free_ranges[i];
|
const FreeRange& free_range = sub_pool.free_ranges[i];
|
||||||
const uint64_t aligned = (r.offset + (align - 1)) & ~(align - 1);
|
const uint64_t aligned = (free_range.offset + (align - 1)) & ~(align - 1);
|
||||||
const uint64_t pad = aligned - r.offset;
|
const uint64_t alignment_padding = aligned - free_range.offset;
|
||||||
if (pad >= r.size) continue;
|
if (alignment_padding >= free_range.size) continue;
|
||||||
if (size > r.size - pad) continue;
|
if (size > free_range.size - alignment_padding) continue;
|
||||||
|
|
||||||
const uint64_t post_off = aligned + size;
|
const uint64_t post_off = aligned + size;
|
||||||
const uint64_t post_size = (r.offset + r.size) - post_off;
|
const uint64_t post_size = (free_range.offset + free_range.size) - post_off;
|
||||||
|
|
||||||
if (pad == 0 && post_size == 0) {
|
if (alignment_padding == 0 && post_size == 0) {
|
||||||
sp.free_ranges.erase(sp.free_ranges.begin() + i);
|
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i);
|
||||||
} else if (pad == 0) {
|
} else if (alignment_padding == 0) {
|
||||||
sp.free_ranges[i] = {post_off, post_size};
|
sub_pool.free_ranges[i] = {post_off, post_size};
|
||||||
} else if (post_size == 0) {
|
} else if (post_size == 0) {
|
||||||
sp.free_ranges[i] = {r.offset, pad};
|
sub_pool.free_ranges[i] = {free_range.offset, alignment_padding};
|
||||||
} else {
|
} else {
|
||||||
sp.free_ranges[i] = {r.offset, pad};
|
sub_pool.free_ranges[i] = {free_range.offset, alignment_padding};
|
||||||
sp.free_ranges.insert(sp.free_ranges.begin() + i + 1,
|
sub_pool.free_ranges.insert(sub_pool.free_ranges.begin() + i + 1,
|
||||||
{post_off, post_size});
|
{post_off, post_size});
|
||||||
}
|
}
|
||||||
|
|
||||||
sp.used += size;
|
sub_pool.used += size;
|
||||||
out.buffer = sp.buffer;
|
out.buffer = sub_pool.buffer;
|
||||||
out.offset = aligned;
|
out.offset = aligned;
|
||||||
out.size = size;
|
out.size = size;
|
||||||
out.sub_idx = int(sp_idx);
|
out.sub_idx = int(sp_idx);
|
||||||
@@ -270,50 +271,56 @@ BufferPool::Slice BufferPool::alloc(uint64_t size, uint64_t align) {
|
|||||||
void BufferPool::free(const Slice& s) {
|
void BufferPool::free(const Slice& s) {
|
||||||
if (!s.valid()) return;
|
if (!s.valid()) return;
|
||||||
if (s.sub_idx < 0 || size_t(s.sub_idx) >= sub_pools_.size()) return;
|
if (s.sub_idx < 0 || size_t(s.sub_idx) >= sub_pools_.size()) return;
|
||||||
SubPool& sp = sub_pools_[size_t(s.sub_idx)];
|
SubPool& sub_pool = sub_pools_[size_t(s.sub_idx)];
|
||||||
assert(s.offset + s.size <= sp.capacity);
|
assert(s.offset + s.size <= sub_pool.capacity);
|
||||||
|
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
while (i < sp.free_ranges.size() && sp.free_ranges[i].offset < s.offset) ++i;
|
while (i < sub_pool.free_ranges.size() && sub_pool.free_ranges[i].offset < s.offset) ++i;
|
||||||
sp.free_ranges.insert(sp.free_ranges.begin() + i, {s.offset, s.size});
|
sub_pool.free_ranges.insert(sub_pool.free_ranges.begin() + i, {s.offset, s.size});
|
||||||
sp.used -= s.size;
|
sub_pool.used -= s.size;
|
||||||
|
|
||||||
if (i + 1 < sp.free_ranges.size()
|
if (i + 1 < sub_pool.free_ranges.size()
|
||||||
&& sp.free_ranges[i].offset + sp.free_ranges[i].size == sp.free_ranges[i + 1].offset) {
|
&& sub_pool.free_ranges[i].offset + sub_pool.free_ranges[i].size
|
||||||
sp.free_ranges[i].size += sp.free_ranges[i + 1].size;
|
== sub_pool.free_ranges[i + 1].offset) {
|
||||||
sp.free_ranges.erase(sp.free_ranges.begin() + i + 1);
|
sub_pool.free_ranges[i].size += sub_pool.free_ranges[i + 1].size;
|
||||||
|
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i + 1);
|
||||||
}
|
}
|
||||||
if (i > 0
|
if (i > 0
|
||||||
&& sp.free_ranges[i - 1].offset + sp.free_ranges[i - 1].size == sp.free_ranges[i].offset) {
|
&& sub_pool.free_ranges[i - 1].offset + sub_pool.free_ranges[i - 1].size
|
||||||
sp.free_ranges[i - 1].size += sp.free_ranges[i].size;
|
== sub_pool.free_ranges[i].offset) {
|
||||||
sp.free_ranges.erase(sp.free_ranges.begin() + i);
|
sub_pool.free_ranges[i - 1].size += sub_pool.free_ranges[i].size;
|
||||||
|
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t BufferPool::total_capacity_bytes() const {
|
uint64_t BufferPool::total_capacity_bytes() const {
|
||||||
uint64_t s = 0;
|
uint64_t total_capacity = 0;
|
||||||
// Skip provisional sub-pools (web, awaiting OOM validation) — their
|
// Skip provisional sub-pools (web, awaiting OOM validation) — their
|
||||||
// capacity isn't usable yet, so counting it would mislead the
|
// capacity isn't usable yet, so counting it would mislead the
|
||||||
// evictor's "is there room?" heuristics.
|
// evictor's "is there room?" heuristics.
|
||||||
for (const auto& sp : sub_pools_) if (!sp.provisional) s += sp.capacity;
|
for (const auto& sub_pool : sub_pools_) {
|
||||||
return s;
|
if (!sub_pool.provisional) total_capacity += sub_pool.capacity;
|
||||||
|
}
|
||||||
|
return total_capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t BufferPool::total_used_bytes() const {
|
uint64_t BufferPool::total_used_bytes() const {
|
||||||
uint64_t s = 0;
|
uint64_t total_used = 0;
|
||||||
for (const auto& sp : sub_pools_) if (!sp.provisional) s += sp.used;
|
for (const auto& sub_pool : sub_pools_) {
|
||||||
return s;
|
if (!sub_pool.provisional) total_used += sub_pool.used;
|
||||||
|
}
|
||||||
|
return total_used;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t BufferPool::largest_free_run_bytes() const {
|
uint64_t BufferPool::largest_free_run_bytes() const {
|
||||||
uint64_t m = 0;
|
uint64_t largest_free_run = 0;
|
||||||
for (const auto& sp : sub_pools_) {
|
for (const auto& sub_pool : sub_pools_) {
|
||||||
if (sp.provisional) continue;
|
if (sub_pool.provisional) continue;
|
||||||
for (const auto& r : sp.free_ranges) {
|
for (const auto& free_range : sub_pool.free_ranges) {
|
||||||
if (r.size > m) m = r.size;
|
if (free_range.size > largest_free_run) largest_free_run = free_range.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m;
|
return largest_free_run;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferPool::addSubBufferForTesting(WGPUBuffer fake_buffer, uint64_t capacity) {
|
void BufferPool::addSubBufferForTesting(WGPUBuffer fake_buffer, uint64_t capacity) {
|
||||||
|
|||||||
@@ -42,29 +42,29 @@ struct MaterialInfo {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static MaterialInfo materialFromStyle(const ifcopenshell::geometry::taxonomy::style::ptr& style) {
|
static MaterialInfo materialFromStyle(const ifcopenshell::geometry::taxonomy::style::ptr& style) {
|
||||||
MaterialInfo m;
|
MaterialInfo material;
|
||||||
if (!style) return m;
|
if (!style) return material;
|
||||||
const auto& color = style->get_color();
|
const auto& color = style->get_color();
|
||||||
if (color) {
|
if (color) {
|
||||||
m.r = static_cast<float>(color.r());
|
material.r = static_cast<float>(color.r());
|
||||||
m.g = static_cast<float>(color.g());
|
material.g = static_cast<float>(color.g());
|
||||||
m.b = static_cast<float>(color.b());
|
material.b = static_cast<float>(color.b());
|
||||||
}
|
}
|
||||||
if (!std::isnan(style->transparency)) {
|
if (!std::isnan(style->transparency)) {
|
||||||
m.a = 1.0f - static_cast<float>(style->transparency);
|
material.a = 1.0f - static_cast<float>(style->transparency);
|
||||||
}
|
}
|
||||||
return m;
|
return material;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint32_t packRGBA8(const MaterialInfo& m) {
|
static inline uint32_t packRGBA8(const MaterialInfo& material) {
|
||||||
auto to_byte = [](float v) -> uint32_t {
|
auto to_byte = [](float channel_value) -> uint32_t {
|
||||||
float c = std::clamp(v, 0.0f, 1.0f);
|
float clamped_value = std::clamp(channel_value, 0.0f, 1.0f);
|
||||||
return static_cast<uint32_t>(c * 255.0f + 0.5f);
|
return static_cast<uint32_t>(clamped_value * 255.0f + 0.5f);
|
||||||
};
|
};
|
||||||
uint32_t r = to_byte(m.r);
|
uint32_t r = to_byte(material.r);
|
||||||
uint32_t g = to_byte(m.g);
|
uint32_t g = to_byte(material.g);
|
||||||
uint32_t b = to_byte(m.b);
|
uint32_t b = to_byte(material.b);
|
||||||
uint32_t a = to_byte(m.a);
|
uint32_t a = to_byte(material.a);
|
||||||
// Little-endian byte layout [r,g,b,a] for GL_UNSIGNED_BYTE * 4 normalized.
|
// Little-endian byte layout [r,g,b,a] for GL_UNSIGNED_BYTE * 4 normalized.
|
||||||
return r | (g << 8) | (b << 16) | (a << 24);
|
return r | (g << 8) | (b << 16) | (a << 24);
|
||||||
}
|
}
|
||||||
@@ -188,12 +188,12 @@ static MeshChunk buildMeshChunk(uint32_t model_id,
|
|||||||
chunk.indices.reserve(faces.size());
|
chunk.indices.reserve(faces.size());
|
||||||
|
|
||||||
// Track local AABB as we emit vertices.
|
// Track local AABB as we emit vertices.
|
||||||
float amin[3] = { std::numeric_limits<float>::max(),
|
float local_aabb_min[3] = { std::numeric_limits<float>::max(),
|
||||||
std::numeric_limits<float>::max(),
|
std::numeric_limits<float>::max(),
|
||||||
std::numeric_limits<float>::max() };
|
std::numeric_limits<float>::max() };
|
||||||
float amax[3] = { -std::numeric_limits<float>::max(),
|
float local_aabb_max[3] = { -std::numeric_limits<float>::max(),
|
||||||
-std::numeric_limits<float>::max(),
|
-std::numeric_limits<float>::max(),
|
||||||
-std::numeric_limits<float>::max() };
|
-std::numeric_limits<float>::max() };
|
||||||
|
|
||||||
auto emit_vertex = [&](uint32_t orig_idx, int mat_id) -> uint32_t {
|
auto emit_vertex = [&](uint32_t orig_idx, int mat_id) -> uint32_t {
|
||||||
const uint64_t key = make_key(orig_idx, mat_id);
|
const uint64_t key = make_key(orig_idx, mat_id);
|
||||||
@@ -211,9 +211,12 @@ static MeshChunk buildMeshChunk(uint32_t model_id,
|
|||||||
chunk.vertices.push_back(px);
|
chunk.vertices.push_back(px);
|
||||||
chunk.vertices.push_back(py);
|
chunk.vertices.push_back(py);
|
||||||
chunk.vertices.push_back(pz);
|
chunk.vertices.push_back(pz);
|
||||||
if (px < amin[0]) amin[0] = px; if (px > amax[0]) amax[0] = px;
|
if (px < local_aabb_min[0]) local_aabb_min[0] = px;
|
||||||
if (py < amin[1]) amin[1] = py; if (py > amax[1]) amax[1] = py;
|
if (px > local_aabb_max[0]) local_aabb_max[0] = px;
|
||||||
if (pz < amin[2]) amin[2] = pz; if (pz > amax[2]) amax[2] = pz;
|
if (py < local_aabb_min[1]) local_aabb_min[1] = py;
|
||||||
|
if (py > local_aabb_max[1]) local_aabb_max[1] = py;
|
||||||
|
if (pz < local_aabb_min[2]) local_aabb_min[2] = pz;
|
||||||
|
if (pz > local_aabb_max[2]) local_aabb_max[2] = pz;
|
||||||
|
|
||||||
if (orig_idx * 3 + 2 < normals.size()) {
|
if (orig_idx * 3 + 2 < normals.size()) {
|
||||||
chunk.vertices.push_back(static_cast<float>(normals[orig_idx * 3 + 0]));
|
chunk.vertices.push_back(static_cast<float>(normals[orig_idx * 3 + 0]));
|
||||||
@@ -246,11 +249,11 @@ static MeshChunk buildMeshChunk(uint32_t model_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (chunk.vertices.empty()) {
|
if (chunk.vertices.empty()) {
|
||||||
for (int a = 0; a < 3; ++a) amin[a] = amax[a] = 0.0f;
|
for (int a = 0; a < 3; ++a) local_aabb_min[a] = local_aabb_max[a] = 0.0f;
|
||||||
}
|
}
|
||||||
for (int a = 0; a < 3; ++a) {
|
for (int a = 0; a < 3; ++a) {
|
||||||
chunk.local_aabb_min[a] = amin[a];
|
chunk.local_aabb_min[a] = local_aabb_min[a];
|
||||||
chunk.local_aabb_max[a] = amax[a];
|
chunk.local_aabb_max[a] = local_aabb_max[a];
|
||||||
}
|
}
|
||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
@@ -527,7 +530,6 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
|
|||||||
emit errorOccurred(QString("Failed to create geometry iterator: %1").arg(e.what()));
|
emit errorOccurred(QString("Failed to create geometry iterator: %1").arg(e.what()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!iterator->initialize()) {
|
if (!iterator->initialize()) {
|
||||||
// No geometry survived this context for the remaining ids.
|
// No geometry survived this context for the remaining ids.
|
||||||
// Subsequent contexts will pick them up; nothing to emit.
|
// Subsequent contexts will pick them up; nothing to emit.
|
||||||
@@ -604,15 +606,15 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
|
|||||||
|
|
||||||
MeshChunk mesh_chunk =
|
MeshChunk mesh_chunk =
|
||||||
buildMeshChunk(model_id_, local_mesh_id, tri_elem, offset);
|
buildMeshChunk(model_id_, local_mesh_id, tri_elem, offset);
|
||||||
MeshAabb ma;
|
MeshAabb mesh_aabb;
|
||||||
for (int a = 0; a < 3; ++a) {
|
for (int a = 0; a < 3; ++a) {
|
||||||
ma.lmin[a] = mesh_chunk.local_aabb_min[a];
|
mesh_aabb.lmin[a] = mesh_chunk.local_aabb_min[a];
|
||||||
ma.lmax[a] = mesh_chunk.local_aabb_max[a];
|
mesh_aabb.lmax[a] = mesh_chunk.local_aabb_max[a];
|
||||||
ma.offset[a] = offset[a];
|
mesh_aabb.offset[a] = offset[a];
|
||||||
}
|
}
|
||||||
ma.has_offset = (offset.squaredNorm() > 0.0);
|
mesh_aabb.has_offset = (offset.squaredNorm() > 0.0);
|
||||||
if (mesh_aabbs.size() <= local_mesh_id) mesh_aabbs.resize(local_mesh_id + 1);
|
if (mesh_aabbs.size() <= local_mesh_id) mesh_aabbs.resize(local_mesh_id + 1);
|
||||||
mesh_aabbs[local_mesh_id] = ma;
|
mesh_aabbs[local_mesh_id] = mesh_aabb;
|
||||||
if (!mesh_chunk.indices.empty()) {
|
if (!mesh_chunk.indices.empty()) {
|
||||||
emit meshReady(std::move(mesh_chunk));
|
emit meshReady(std::move(mesh_chunk));
|
||||||
}
|
}
|
||||||
@@ -626,11 +628,11 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
|
|||||||
Eigen::Matrix4d mat_d =
|
Eigen::Matrix4d mat_d =
|
||||||
tri_elem->transformation().data()->ccomponents();
|
tri_elem->transformation().data()->ccomponents();
|
||||||
if (mesh_aabbs[local_mesh_id].has_offset) {
|
if (mesh_aabbs[local_mesh_id].has_offset) {
|
||||||
const Eigen::Vector3d off(
|
const Eigen::Vector3d mesh_rebase_offset(
|
||||||
mesh_aabbs[local_mesh_id].offset[0],
|
mesh_aabbs[local_mesh_id].offset[0],
|
||||||
mesh_aabbs[local_mesh_id].offset[1],
|
mesh_aabbs[local_mesh_id].offset[1],
|
||||||
mesh_aabbs[local_mesh_id].offset[2]);
|
mesh_aabbs[local_mesh_id].offset[2]);
|
||||||
mat_d.block<3, 1>(0, 3) += mat_d.block<3, 3>(0, 0) * off;
|
mat_d.block<3, 1>(0, 3) += mat_d.block<3, 3>(0, 0) * mesh_rebase_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
InstanceChunk inst;
|
InstanceChunk inst;
|
||||||
@@ -642,25 +644,25 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
|
|||||||
inst.transform[i] = mat_d.data()[i];
|
inst.transform[i] = mat_d.data()[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
const MeshAabb& ma = mesh_aabbs[local_mesh_id];
|
const MeshAabb& mesh_aabb = mesh_aabbs[local_mesh_id];
|
||||||
float mat_f[16];
|
float mat_f[16];
|
||||||
for (int i = 0; i < 16; ++i) {
|
for (int i = 0; i < 16; ++i) {
|
||||||
mat_f[i] = static_cast<float>(inst.transform[i]);
|
mat_f[i] = static_cast<float>(inst.transform[i]);
|
||||||
}
|
}
|
||||||
worldAabbFromLocal(ma.lmin, ma.lmax, mat_f,
|
worldAabbFromLocal(mesh_aabb.lmin, mesh_aabb.lmax, mat_f,
|
||||||
inst.world_aabb_min, inst.world_aabb_max);
|
inst.world_aabb_min, inst.world_aabb_max);
|
||||||
|
|
||||||
emit instanceReady(std::move(inst));
|
emit instanceReady(std::move(inst));
|
||||||
total_shapes++;
|
total_shapes++;
|
||||||
yielded_count++;
|
yielded_count++;
|
||||||
|
|
||||||
const int p = total_count > 0
|
const int progress_percent = total_count > 0
|
||||||
? static_cast<int>((100 * yielded_count) / total_count)
|
? static_cast<int>((100 * yielded_count) / total_count)
|
||||||
: 100;
|
: 100;
|
||||||
if (p != last_emitted_progress) {
|
if (progress_percent != last_emitted_progress) {
|
||||||
last_emitted_progress = p;
|
last_emitted_progress = progress_percent;
|
||||||
progress_ = p;
|
progress_ = progress_percent;
|
||||||
emit progressChanged(p);
|
emit progressChanged(progress_percent);
|
||||||
}
|
}
|
||||||
} while (iterator->next());
|
} while (iterator->next());
|
||||||
|
|
||||||
|
|||||||
@@ -79,16 +79,16 @@ bool findInstanceInModels(
|
|||||||
const std::unordered_map<uint32_t, ModelGpuData>& models,
|
const std::unordered_map<uint32_t, ModelGpuData>& models,
|
||||||
InstanceLookup& out) {
|
InstanceLookup& out) {
|
||||||
if (object_id == 0) return false;
|
if (object_id == 0) return false;
|
||||||
for (const auto& [mid, m] : models) {
|
for (const auto& [model_id, model_data] : models) {
|
||||||
auto it = m.object_id_to_instance.find(object_id);
|
auto it = model_data.object_id_to_instance.find(object_id);
|
||||||
if (it == m.object_id_to_instance.end()) continue;
|
if (it == model_data.object_id_to_instance.end()) continue;
|
||||||
const uint32_t inst_idx = it->second;
|
const uint32_t instance_index = it->second;
|
||||||
if (inst_idx >= m.instances.size()) continue;
|
if (instance_index >= model_data.instances.size()) continue;
|
||||||
const InstanceCpu& inst = m.instances[inst_idx];
|
const InstanceCpu& instance = model_data.instances[instance_index];
|
||||||
out.model_id = mid;
|
out.model_id = model_id;
|
||||||
out.mesh_id = inst.mesh_id;
|
out.mesh_id = instance.mesh_id;
|
||||||
std::memcpy(out.placement_transformation,
|
std::memcpy(out.placement_transformation,
|
||||||
inst.placement_transformation,
|
instance.placement_transformation,
|
||||||
sizeof(out.placement_transformation));
|
sizeof(out.placement_transformation));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,10 +53,10 @@ void SidecarBuilder::onMeshReady(const MeshChunk& chunk) {
|
|||||||
-std::numeric_limits<float>::infinity(),
|
-std::numeric_limits<float>::infinity(),
|
||||||
-std::numeric_limits<float>::infinity() };
|
-std::numeric_limits<float>::infinity() };
|
||||||
for (size_t i = 0; i < n_verts; ++i) {
|
for (size_t i = 0; i < n_verts; ++i) {
|
||||||
const float* v = chunk.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS;
|
const float* vertex = chunk.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS;
|
||||||
for (int a = 0; a < 3; ++a) {
|
for (int a = 0; a < 3; ++a) {
|
||||||
if (v[a] < bmin[a]) bmin[a] = v[a];
|
if (vertex[a] < bmin[a]) bmin[a] = vertex[a];
|
||||||
if (v[a] > bmax[a]) bmax[a] = v[a];
|
if (vertex[a] > bmax[a]) bmax[a] = vertex[a];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float extent_recip[3];
|
float extent_recip[3];
|
||||||
@@ -99,25 +99,25 @@ void SidecarBuilder::onMeshReady(const MeshChunk& chunk) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SidecarBuilder::onInstanceReady(const InstanceChunk& chunk) {
|
void SidecarBuilder::onInstanceReady(const InstanceChunk& chunk) {
|
||||||
InstanceCpu inst;
|
InstanceCpu instance;
|
||||||
inst.mesh_id = chunk.local_mesh_id;
|
instance.mesh_id = chunk.local_mesh_id;
|
||||||
inst.object_id = chunk.object_id;
|
instance.object_id = chunk.object_id;
|
||||||
inst.color_override_rgba8 = chunk.color_override_rgba8;
|
instance.color_override_rgba8 = chunk.color_override_rgba8;
|
||||||
inst.model_id = chunk.model_id;
|
instance.model_id = chunk.model_id;
|
||||||
|
|
||||||
// The streamer's chunk.transform is the double-precision
|
// The streamer's chunk.transform is the double-precision
|
||||||
// placement_transformation. The cached float transform/world_aabb is only
|
// placement_transformation. The cached float transform/world_aabb is only
|
||||||
// an identity-stage baseline; applyCachedModel recomposes from placement
|
// an identity-stage baseline; applyCachedModel recomposes from placement
|
||||||
// against the consumer's stage matrices at load time.
|
// against the consumer's stage matrices at load time.
|
||||||
std::memcpy(inst.placement_transformation, chunk.transform,
|
std::memcpy(instance.placement_transformation, chunk.transform,
|
||||||
sizeof(inst.placement_transformation));
|
sizeof(instance.placement_transformation));
|
||||||
for (int i = 0; i < 16; ++i) {
|
for (int i = 0; i < 16; ++i) {
|
||||||
inst.transform[i] = static_cast<float>(chunk.transform[i]);
|
instance.transform[i] = static_cast<float>(chunk.transform[i]);
|
||||||
}
|
}
|
||||||
std::memcpy(inst.world_aabb_min, chunk.world_aabb_min, sizeof(inst.world_aabb_min));
|
std::memcpy(instance.world_aabb_min, chunk.world_aabb_min, sizeof(instance.world_aabb_min));
|
||||||
std::memcpy(inst.world_aabb_max, chunk.world_aabb_max, sizeof(inst.world_aabb_max));
|
std::memcpy(instance.world_aabb_max, chunk.world_aabb_max, sizeof(instance.world_aabb_max));
|
||||||
|
|
||||||
sidecar_data_.instances.push_back(inst);
|
sidecar_data_.instances.push_back(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
SidecarData SidecarBuilder::finalize(const ModelGeoref& georef,
|
SidecarData SidecarBuilder::finalize(const ModelGeoref& georef,
|
||||||
|
|||||||
+138
-103
@@ -59,50 +59,61 @@ static constexpr int kSidecarZstdLevel = 19;
|
|||||||
|
|
||||||
// --- In-memory serialisation (a block is built in RAM, then compressed) ------
|
// --- In-memory serialisation (a block is built in RAM, then compressed) ------
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static void appendVec(std::vector<std::uint8_t>& b, const std::vector<T>& v) {
|
static void appendVec(std::vector<std::uint8_t>& buffer, const std::vector<T>& values) {
|
||||||
std::uint32_t n = static_cast<std::uint32_t>(v.size());
|
std::uint32_t count = static_cast<std::uint32_t>(values.size());
|
||||||
const auto* np = reinterpret_cast<const std::uint8_t*>(&n);
|
const auto* count_bytes = reinterpret_cast<const std::uint8_t*>(&count);
|
||||||
b.insert(b.end(), np, np + 4);
|
buffer.insert(buffer.end(), count_bytes, count_bytes + 4);
|
||||||
if (n > 0) {
|
if (count > 0) {
|
||||||
const auto* p = reinterpret_cast<const std::uint8_t*>(v.data());
|
const auto* value_bytes = reinterpret_cast<const std::uint8_t*>(values.data());
|
||||||
b.insert(b.end(), p, p + std::size_t(sizeof(T)) * n);
|
buffer.insert(buffer.end(), value_bytes, value_bytes + std::size_t(sizeof(T)) * count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static void appendBytes(std::vector<std::uint8_t>& b, const void* p, std::size_t n) {
|
static void appendBytes(std::vector<std::uint8_t>& buffer, const void* data, std::size_t byte_count) {
|
||||||
const auto* c = static_cast<const std::uint8_t*>(p);
|
const auto* bytes = static_cast<const std::uint8_t*>(data);
|
||||||
b.insert(b.end(), c, c + n);
|
buffer.insert(buffer.end(), bytes, bytes + byte_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull one chunk's geometry out of the whole-model vertex/index arrays into the
|
// Pull one chunk's geometry out of the whole-model vertex/index arrays into the
|
||||||
// chunk-LOCAL layout applyStreamedChunk expects: vertices of its meshes in chunk
|
// chunk-LOCAL layout applyStreamedChunk expects: vertices of its meshes in chunk
|
||||||
// order, then indices as LOD0 (per mesh) followed by LOD1 (per mesh).
|
// order, then indices as LOD0 (per mesh) followed by LOD1 (per mesh).
|
||||||
static void extractChunkGeometry(const SidecarData& d, const SidecarChunk& c,
|
static void extractChunkGeometry(const SidecarData& sidecar_data, const SidecarChunk& sidecar_chunk,
|
||||||
std::vector<std::uint8_t>& vbytes,
|
std::vector<std::uint8_t>& vbytes,
|
||||||
std::vector<std::uint8_t>& ibytes) {
|
std::vector<std::uint8_t>& ibytes) {
|
||||||
vbytes.clear();
|
vbytes.clear();
|
||||||
ibytes.clear();
|
ibytes.clear();
|
||||||
const std::uint32_t end = c.first_mesh + c.mesh_count;
|
const std::uint32_t end = sidecar_chunk.first_mesh + sidecar_chunk.mesh_count;
|
||||||
for (std::uint32_t mi = c.first_mesh; mi < end && mi < d.meshes.size(); ++mi) {
|
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
|
||||||
const MeshInfo& m = d.meshes[mi];
|
mesh_index < end && mesh_index < sidecar_data.meshes.size();
|
||||||
const std::size_t voff = m.vbo_byte_offset;
|
++mesh_index) {
|
||||||
const std::size_t vn = std::size_t(m.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
|
const MeshInfo& mesh_info = sidecar_data.meshes[mesh_index];
|
||||||
if (voff + vn <= d.vertices.size())
|
const std::size_t vertex_offset = mesh_info.vbo_byte_offset;
|
||||||
vbytes.insert(vbytes.end(), d.vertices.begin() + voff,
|
const std::size_t vertex_byte_count =
|
||||||
d.vertices.begin() + voff + vn);
|
std::size_t(mesh_info.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
|
||||||
|
if (vertex_offset + vertex_byte_count <= sidecar_data.vertices.size())
|
||||||
|
vbytes.insert(vbytes.end(), sidecar_data.vertices.begin() + vertex_offset,
|
||||||
|
sidecar_data.vertices.begin() + vertex_offset + vertex_byte_count);
|
||||||
}
|
}
|
||||||
auto appendIdx = [&](std::size_t first_u32, std::size_t count) {
|
auto appendIdx = [&](std::size_t first_u32, std::size_t count) {
|
||||||
if (first_u32 + count > d.indices.size()) return;
|
if (first_u32 + count > sidecar_data.indices.size()) return;
|
||||||
const auto* p = reinterpret_cast<const std::uint8_t*>(d.indices.data() + first_u32);
|
const auto* index_bytes =
|
||||||
ibytes.insert(ibytes.end(), p, p + count * sizeof(std::uint32_t));
|
reinterpret_cast<const std::uint8_t*>(sidecar_data.indices.data() + first_u32);
|
||||||
|
ibytes.insert(ibytes.end(), index_bytes, index_bytes + count * sizeof(std::uint32_t));
|
||||||
};
|
};
|
||||||
for (std::uint32_t mi = c.first_mesh; mi < end && mi < d.meshes.size(); ++mi) {
|
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
|
||||||
const MeshInfo& m = d.meshes[mi];
|
mesh_index < end && mesh_index < sidecar_data.meshes.size();
|
||||||
if (m.index_count) appendIdx(m.ebo_byte_offset / sizeof(std::uint32_t), m.index_count);
|
++mesh_index) {
|
||||||
|
const MeshInfo& mesh_info = sidecar_data.meshes[mesh_index];
|
||||||
|
if (mesh_info.index_count) {
|
||||||
|
appendIdx(mesh_info.ebo_byte_offset / sizeof(std::uint32_t), mesh_info.index_count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (std::uint32_t mi = c.first_mesh; mi < end && mi < d.meshes.size(); ++mi) {
|
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
|
||||||
const MeshInfo& m = d.meshes[mi];
|
mesh_index < end && mesh_index < sidecar_data.meshes.size();
|
||||||
if (m.lod1_index_count)
|
++mesh_index) {
|
||||||
appendIdx(m.lod1_ebo_byte_offset / sizeof(std::uint32_t), m.lod1_index_count);
|
const MeshInfo& mesh_info = sidecar_data.meshes[mesh_index];
|
||||||
|
if (mesh_info.lod1_index_count) {
|
||||||
|
appendIdx(mesh_info.lod1_ebo_byte_offset / sizeof(std::uint32_t), mesh_info.lod1_index_count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // !__EMSCRIPTEN__ (bake-only serialisation helpers)
|
#endif // !__EMSCRIPTEN__ (bake-only serialisation helpers)
|
||||||
@@ -118,14 +129,14 @@ struct SidecarHeader {
|
|||||||
// foo.ifcdb -> foo.ifcview
|
// foo.ifcdb -> foo.ifcview
|
||||||
// foo (no ext) -> foo.ifcview
|
// foo (no ext) -> foo.ifcview
|
||||||
static std::string sidecarPath(const std::string& ifc_path) {
|
static std::string sidecarPath(const std::string& ifc_path) {
|
||||||
std::string p = ifc_path;
|
std::string path = ifc_path;
|
||||||
while (!p.empty() && (p.back() == '/' || p.back() == '\\')) p.pop_back();
|
while (!path.empty() && (path.back() == '/' || path.back() == '\\')) path.pop_back();
|
||||||
auto slash = p.find_last_of("/\\");
|
auto slash = path.find_last_of("/\\");
|
||||||
auto dot = p.find_last_of('.');
|
auto dot = path.find_last_of('.');
|
||||||
std::string stem = (dot != std::string::npos &&
|
std::string stem = (dot != std::string::npos &&
|
||||||
(slash == std::string::npos || dot > slash))
|
(slash == std::string::npos || dot > slash))
|
||||||
? p.substr(0, dot)
|
? path.substr(0, dot)
|
||||||
: p;
|
: path;
|
||||||
return stem + ".ifcview";
|
return stem + ".ifcview";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,18 +163,18 @@ bool writeSidecar(const std::string& ifc_path, const SidecarData& data) {
|
|||||||
FILE* f = fopen(path.c_str(), "wb");
|
FILE* f = fopen(path.c_str(), "wb");
|
||||||
if (!f) return false;
|
if (!f) return false;
|
||||||
|
|
||||||
auto wr = [&](const void* p, std::size_t n) {
|
auto write_bytes = [&](const void* data, std::size_t byte_count) {
|
||||||
return fwrite(p, 1, n, f) == n;
|
return fwrite(data, 1, byte_count, f) == byte_count;
|
||||||
};
|
};
|
||||||
auto wrU64 = [&](std::uint64_t v) { return wr(&v, sizeof(v)); };
|
auto wrU64 = [&](std::uint64_t v) { return write_bytes(&v, sizeof(v)); };
|
||||||
auto wrBlock = [&](const std::vector<std::uint8_t>& raw) -> bool {
|
auto wrBlock = [&](const std::vector<std::uint8_t>& raw) -> bool {
|
||||||
auto z = SidecarCompress::compress(raw.data(), raw.size(), kSidecarZstdLevel);
|
auto z = SidecarCompress::compress(raw.data(), raw.size(), kSidecarZstdLevel);
|
||||||
if (raw.size() > 0 && z.empty()) return false; // compress failed
|
if (raw.size() > 0 && z.empty()) return false; // compress failed
|
||||||
return wrU64(z.size()) && wrU64(raw.size()) && (z.empty() || wr(z.data(), z.size()));
|
return wrU64(z.size()) && wrU64(raw.size()) && (z.empty() || write_bytes(z.data(), z.size()));
|
||||||
};
|
};
|
||||||
|
|
||||||
SidecarHeader hdr = { SIDECAR_MAGIC, SIDECAR_VERSION, SIDECAR_ENDIAN };
|
SidecarHeader hdr = { SIDECAR_MAGIC, SIDECAR_VERSION, SIDECAR_ENDIAN };
|
||||||
if (!wr(&hdr, sizeof(hdr))) { fclose(f); return false; }
|
if (!write_bytes(&hdr, sizeof(hdr))) { fclose(f); return false; }
|
||||||
|
|
||||||
// --- Geometry section: per-chunk zstd(vertex) + zstd(index) frames -------
|
// --- Geometry section: per-chunk zstd(vertex) + zstd(index) frames -------
|
||||||
// Offsets in the chunk TOC are relative to the geometry section start, so
|
// Offsets in the chunk TOC are relative to the geometry section start, so
|
||||||
@@ -174,19 +185,19 @@ bool writeSidecar(const std::string& ifc_path, const SidecarData& data) {
|
|||||||
|
|
||||||
std::vector<SidecarChunk> chunks = data.chunks; // fill blob offsets below
|
std::vector<SidecarChunk> chunks = data.chunks; // fill blob offsets below
|
||||||
std::vector<std::uint8_t> vraw, iraw;
|
std::vector<std::uint8_t> vraw, iraw;
|
||||||
for (auto& c : chunks) {
|
for (auto& sidecar_chunk : chunks) {
|
||||||
extractChunkGeometry(data, c, vraw, iraw);
|
extractChunkGeometry(data, sidecar_chunk, vraw, iraw);
|
||||||
auto vz = SidecarCompress::compress(vraw.data(), vraw.size(), kSidecarZstdLevel);
|
auto vz = SidecarCompress::compress(vraw.data(), vraw.size(), kSidecarZstdLevel);
|
||||||
auto iz = SidecarCompress::compress(iraw.data(), iraw.size(), kSidecarZstdLevel);
|
auto iz = SidecarCompress::compress(iraw.data(), iraw.size(), kSidecarZstdLevel);
|
||||||
if ((vraw.size() && vz.empty()) || (iraw.size() && iz.empty())) { fclose(f); return false; }
|
if ((vraw.size() && vz.empty()) || (iraw.size() && iz.empty())) { fclose(f); return false; }
|
||||||
c.v_comp_off = std::uint64_t(ftell(f) - geom_start);
|
sidecar_chunk.v_comp_off = std::uint64_t(ftell(f) - geom_start);
|
||||||
c.v_comp_size = vz.size();
|
sidecar_chunk.v_comp_size = vz.size();
|
||||||
c.v_raw_size = vraw.size();
|
sidecar_chunk.v_raw_size = vraw.size();
|
||||||
if (!vz.empty() && !wr(vz.data(), vz.size())) { fclose(f); return false; }
|
if (!vz.empty() && !write_bytes(vz.data(), vz.size())) { fclose(f); return false; }
|
||||||
c.i_comp_off = std::uint64_t(ftell(f) - geom_start);
|
sidecar_chunk.i_comp_off = std::uint64_t(ftell(f) - geom_start);
|
||||||
c.i_comp_size = iz.size();
|
sidecar_chunk.i_comp_size = iz.size();
|
||||||
c.i_raw_size = iraw.size();
|
sidecar_chunk.i_raw_size = iraw.size();
|
||||||
if (!iz.empty() && !wr(iz.data(), iz.size())) { fclose(f); return false; }
|
if (!iz.empty() && !write_bytes(iz.data(), iz.size())) { fclose(f); return false; }
|
||||||
}
|
}
|
||||||
const long geom_end = ftell(f);
|
const long geom_end = ftell(f);
|
||||||
if (geom_start < 0 || geom_end < 0) { fclose(f); return false; }
|
if (geom_start < 0 || geom_end < 0) { fclose(f); return false; }
|
||||||
@@ -195,23 +206,23 @@ bool writeSidecar(const std::string& ifc_path, const SidecarData& data) {
|
|||||||
if (fseek(f, geom_end, SEEK_SET) != 0) { fclose(f); return false; }
|
if (fseek(f, geom_end, SEEK_SET) != 0) { fclose(f); return false; }
|
||||||
|
|
||||||
// --- Critical metadata block (zstd): meshes, instances, georef, chunk TOC
|
// --- Critical metadata block (zstd): meshes, instances, georef, chunk TOC
|
||||||
std::vector<std::uint8_t> crit;
|
std::vector<std::uint8_t> critical_metadata;
|
||||||
appendVec(crit, data.meshes);
|
appendVec(critical_metadata, data.meshes);
|
||||||
appendVec(crit, data.instances);
|
appendVec(critical_metadata, data.instances);
|
||||||
appendBytes(crit, &data.has_coordinate_operation, 4);
|
appendBytes(critical_metadata, &data.has_coordinate_operation, 4);
|
||||||
appendBytes(crit, data.coordinate_operation_meters, sizeof(double) * 16);
|
appendBytes(critical_metadata, data.coordinate_operation_meters, sizeof(double) * 16);
|
||||||
appendBytes(crit, &data.project_length_to_meters, sizeof(double));
|
appendBytes(critical_metadata, &data.project_length_to_meters, sizeof(double));
|
||||||
appendBytes(crit, &data.map_unit_to_meters, sizeof(double));
|
appendBytes(critical_metadata, &data.map_unit_to_meters, sizeof(double));
|
||||||
appendVec(crit, chunks);
|
appendVec(critical_metadata, chunks);
|
||||||
if (!wrBlock(crit)) { fclose(f); return false; }
|
if (!wrBlock(critical_metadata)) { fclose(f); return false; }
|
||||||
|
|
||||||
// --- Deferred metadata block (zstd): element tree + string table ---------
|
// --- Deferred metadata block (zstd): element tree + string table ---------
|
||||||
std::vector<std::uint8_t> def;
|
std::vector<std::uint8_t> deferred_metadata;
|
||||||
appendVec(def, data.elements);
|
appendVec(deferred_metadata, data.elements);
|
||||||
std::uint32_t stbl_len = static_cast<std::uint32_t>(data.string_table.size());
|
std::uint32_t stbl_len = static_cast<std::uint32_t>(data.string_table.size());
|
||||||
appendBytes(def, &stbl_len, 4);
|
appendBytes(deferred_metadata, &stbl_len, 4);
|
||||||
appendBytes(def, data.string_table.data(), stbl_len);
|
appendBytes(deferred_metadata, data.string_table.data(), stbl_len);
|
||||||
if (!wrBlock(def)) { fclose(f); return false; }
|
if (!wrBlock(deferred_metadata)) { fclose(f); return false; }
|
||||||
|
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return true;
|
return true;
|
||||||
@@ -257,28 +268,30 @@ std::optional<SidecarData> readSidecar(const std::string& ifc_path) {
|
|||||||
if (hdr.magic != SIDECAR_MAGIC || hdr.version != SIDECAR_VERSION ||
|
if (hdr.magic != SIDECAR_MAGIC || hdr.version != SIDECAR_VERSION ||
|
||||||
hdr.endian != SIDECAR_ENDIAN) return fail();
|
hdr.endian != SIDECAR_ENDIAN) return fail();
|
||||||
|
|
||||||
auto rd = [&](void* p, std::size_t k) { return fread(p, 1, k, f) == k; };
|
auto read_bytes = [&](void* data, std::size_t byte_count) {
|
||||||
auto rdU64 = [&](std::uint64_t& v) { return rd(&v, sizeof(v)); };
|
return fread(data, 1, byte_count, f) == byte_count;
|
||||||
|
};
|
||||||
|
auto rdU64 = [&](std::uint64_t& v) { return read_bytes(&v, sizeof(v)); };
|
||||||
|
|
||||||
std::uint64_t geom_bytes = 0;
|
std::uint64_t geom_bytes = 0;
|
||||||
if (!rdU64(geom_bytes)) return fail();
|
if (!rdU64(geom_bytes)) return fail();
|
||||||
std::vector<std::uint8_t> geom(static_cast<std::size_t>(geom_bytes));
|
std::vector<std::uint8_t> geom(static_cast<std::size_t>(geom_bytes));
|
||||||
if (geom_bytes && !rd(geom.data(), geom.size())) return fail();
|
if (geom_bytes && !read_bytes(geom.data(), geom.size())) return fail();
|
||||||
|
|
||||||
auto readBlock = [&](std::vector<std::uint8_t>& out) -> bool {
|
auto readBlock = [&](std::vector<std::uint8_t>& out) -> bool {
|
||||||
std::uint64_t comp = 0, raw = 0;
|
std::uint64_t comp = 0, raw = 0;
|
||||||
if (!rdU64(comp) || !rdU64(raw)) return false;
|
if (!rdU64(comp) || !rdU64(raw)) return false;
|
||||||
std::vector<std::uint8_t> z(static_cast<std::size_t>(comp));
|
std::vector<std::uint8_t> z(static_cast<std::size_t>(comp));
|
||||||
if (comp && !rd(z.data(), z.size())) return false;
|
if (comp && !read_bytes(z.data(), z.size())) return false;
|
||||||
out.assign(std::size_t(raw), 0);
|
out.assign(std::size_t(raw), 0);
|
||||||
return SidecarCompress::decompress(z.data(), z.size(), out.data(), out.size());
|
return SidecarCompress::decompress(z.data(), z.size(), out.data(), out.size());
|
||||||
};
|
};
|
||||||
std::vector<std::uint8_t> crit, def;
|
std::vector<std::uint8_t> critical_metadata, deferred_metadata;
|
||||||
if (!readBlock(crit) || !readBlock(def)) return fail();
|
if (!readBlock(critical_metadata) || !readBlock(deferred_metadata)) return fail();
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
SidecarData data;
|
SidecarData data;
|
||||||
BufReader cr{ crit.data(), crit.size() };
|
BufReader cr{ critical_metadata.data(), critical_metadata.size() };
|
||||||
if (!cr.takeVec(data.meshes)) return std::nullopt;
|
if (!cr.takeVec(data.meshes)) return std::nullopt;
|
||||||
if (!cr.takeVec(data.instances)) return std::nullopt;
|
if (!cr.takeVec(data.instances)) return std::nullopt;
|
||||||
if (!cr.take(&data.has_coordinate_operation, 4)) return std::nullopt;
|
if (!cr.take(&data.has_coordinate_operation, 4)) return std::nullopt;
|
||||||
@@ -287,7 +300,7 @@ std::optional<SidecarData> readSidecar(const std::string& ifc_path) {
|
|||||||
if (!cr.take(&data.map_unit_to_meters, sizeof(double))) return std::nullopt;
|
if (!cr.take(&data.map_unit_to_meters, sizeof(double))) return std::nullopt;
|
||||||
if (!cr.takeVec(data.chunks)) return std::nullopt;
|
if (!cr.takeVec(data.chunks)) return std::nullopt;
|
||||||
|
|
||||||
BufReader dr{ def.data(), def.size() };
|
BufReader dr{ deferred_metadata.data(), deferred_metadata.size() };
|
||||||
if (!dr.takeVec(data.elements)) return std::nullopt;
|
if (!dr.takeVec(data.elements)) return std::nullopt;
|
||||||
std::uint32_t stbl_len = 0;
|
std::uint32_t stbl_len = 0;
|
||||||
if (!dr.take(&stbl_len, 4)) return std::nullopt;
|
if (!dr.take(&stbl_len, 4)) return std::nullopt;
|
||||||
@@ -296,46 +309,68 @@ std::optional<SidecarData> readSidecar(const std::string& ifc_path) {
|
|||||||
|
|
||||||
// Reconstruct the whole-model vertex/index arrays from the per-chunk blobs.
|
// Reconstruct the whole-model vertex/index arrays from the per-chunk blobs.
|
||||||
std::size_t vsize = 0, isize = 0;
|
std::size_t vsize = 0, isize = 0;
|
||||||
for (const auto& m : data.meshes) {
|
for (const auto& mesh_info : data.meshes) {
|
||||||
vsize = std::max<std::size_t>(vsize,
|
vsize = std::max<std::size_t>(vsize,
|
||||||
std::size_t(m.vbo_byte_offset) + std::size_t(m.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES);
|
std::size_t(mesh_info.vbo_byte_offset) +
|
||||||
isize = std::max<std::size_t>(isize, m.ebo_byte_offset / sizeof(std::uint32_t) + m.index_count);
|
std::size_t(mesh_info.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES);
|
||||||
if (m.lod1_index_count)
|
isize = std::max<std::size_t>(
|
||||||
isize = std::max<std::size_t>(isize, m.lod1_ebo_byte_offset / sizeof(std::uint32_t) + m.lod1_index_count);
|
isize, mesh_info.ebo_byte_offset / sizeof(std::uint32_t) + mesh_info.index_count);
|
||||||
|
if (mesh_info.lod1_index_count) {
|
||||||
|
isize = std::max<std::size_t>(
|
||||||
|
isize,
|
||||||
|
mesh_info.lod1_ebo_byte_offset / sizeof(std::uint32_t) + mesh_info.lod1_index_count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
data.vertices.assign(vsize, 0);
|
data.vertices.assign(vsize, 0);
|
||||||
data.indices.assign(isize, 0);
|
data.indices.assign(isize, 0);
|
||||||
for (const auto& c : data.chunks) {
|
for (const auto& sidecar_chunk : data.chunks) {
|
||||||
if (c.v_comp_off + c.v_comp_size > geom.size() ||
|
if (sidecar_chunk.v_comp_off + sidecar_chunk.v_comp_size > geom.size() ||
|
||||||
c.i_comp_off + c.i_comp_size > geom.size()) return std::nullopt;
|
sidecar_chunk.i_comp_off + sidecar_chunk.i_comp_size > geom.size()) return std::nullopt;
|
||||||
std::vector<std::uint8_t> vraw(static_cast<std::size_t>(c.v_raw_size));
|
std::vector<std::uint8_t> vraw(static_cast<std::size_t>(sidecar_chunk.v_raw_size));
|
||||||
std::vector<std::uint8_t> iraw(static_cast<std::size_t>(c.i_raw_size));
|
std::vector<std::uint8_t> iraw(static_cast<std::size_t>(sidecar_chunk.i_raw_size));
|
||||||
if (!SidecarCompress::decompress(geom.data() + c.v_comp_off, c.v_comp_size, vraw.data(), vraw.size()) ||
|
if (!SidecarCompress::decompress(
|
||||||
!SidecarCompress::decompress(geom.data() + c.i_comp_off, c.i_comp_size, iraw.data(), iraw.size()))
|
geom.data() + sidecar_chunk.v_comp_off, sidecar_chunk.v_comp_size, vraw.data(), vraw.size()) ||
|
||||||
|
!SidecarCompress::decompress(
|
||||||
|
geom.data() + sidecar_chunk.i_comp_off, sidecar_chunk.i_comp_size, iraw.data(), iraw.size()))
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
const auto* iu = reinterpret_cast<const std::uint32_t*>(iraw.data());
|
const auto* iu = reinterpret_cast<const std::uint32_t*>(iraw.data());
|
||||||
std::size_t vcur = 0, icur = 0;
|
std::size_t vcur = 0, icur = 0;
|
||||||
const std::uint32_t end = c.first_mesh + c.mesh_count;
|
const std::uint32_t end = sidecar_chunk.first_mesh + sidecar_chunk.mesh_count;
|
||||||
for (std::uint32_t mi = c.first_mesh; mi < end && mi < data.meshes.size(); ++mi) {
|
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
|
||||||
const MeshInfo& m = data.meshes[mi];
|
mesh_index < end && mesh_index < data.meshes.size();
|
||||||
const std::size_t vn = std::size_t(m.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
|
++mesh_index) {
|
||||||
if (vcur + vn <= vraw.size() && m.vbo_byte_offset + vn <= data.vertices.size())
|
const MeshInfo& mesh_info = data.meshes[mesh_index];
|
||||||
std::memcpy(&data.vertices[m.vbo_byte_offset], vraw.data() + vcur, vn);
|
const std::size_t vertex_byte_count =
|
||||||
vcur += vn;
|
std::size_t(mesh_info.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
|
||||||
|
if (vcur + vertex_byte_count <= vraw.size() &&
|
||||||
|
mesh_info.vbo_byte_offset + vertex_byte_count <= data.vertices.size()) {
|
||||||
|
std::memcpy(&data.vertices[mesh_info.vbo_byte_offset], vraw.data() + vcur, vertex_byte_count);
|
||||||
|
}
|
||||||
|
vcur += vertex_byte_count;
|
||||||
}
|
}
|
||||||
for (std::uint32_t mi = c.first_mesh; mi < end && mi < data.meshes.size(); ++mi) {
|
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
|
||||||
const MeshInfo& m = data.meshes[mi];
|
mesh_index < end && mesh_index < data.meshes.size();
|
||||||
if (!m.index_count) continue;
|
++mesh_index) {
|
||||||
if (icur + m.index_count <= iraw.size() / 4)
|
const MeshInfo& mesh_info = data.meshes[mesh_index];
|
||||||
std::memcpy(&data.indices[m.ebo_byte_offset / sizeof(std::uint32_t)], iu + icur, m.index_count * 4);
|
if (!mesh_info.index_count) continue;
|
||||||
icur += m.index_count;
|
if (icur + mesh_info.index_count <= iraw.size() / 4) {
|
||||||
|
std::memcpy(&data.indices[mesh_info.ebo_byte_offset / sizeof(std::uint32_t)],
|
||||||
|
iu + icur,
|
||||||
|
mesh_info.index_count * 4);
|
||||||
|
}
|
||||||
|
icur += mesh_info.index_count;
|
||||||
}
|
}
|
||||||
for (std::uint32_t mi = c.first_mesh; mi < end && mi < data.meshes.size(); ++mi) {
|
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
|
||||||
const MeshInfo& m = data.meshes[mi];
|
mesh_index < end && mesh_index < data.meshes.size();
|
||||||
if (!m.lod1_index_count) continue;
|
++mesh_index) {
|
||||||
if (icur + m.lod1_index_count <= iraw.size() / 4)
|
const MeshInfo& mesh_info = data.meshes[mesh_index];
|
||||||
std::memcpy(&data.indices[m.lod1_ebo_byte_offset / sizeof(std::uint32_t)], iu + icur, m.lod1_index_count * 4);
|
if (!mesh_info.lod1_index_count) continue;
|
||||||
icur += m.lod1_index_count;
|
if (icur + mesh_info.lod1_index_count <= iraw.size() / 4) {
|
||||||
|
std::memcpy(&data.indices[mesh_info.lod1_ebo_byte_offset / sizeof(std::uint32_t)],
|
||||||
|
iu + icur,
|
||||||
|
mesh_info.lod1_index_count * 4);
|
||||||
|
}
|
||||||
|
icur += mesh_info.lod1_index_count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -26,37 +26,42 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
void reorderSidecarByMorton(SidecarData& sd) {
|
void reorderSidecarByMorton(SidecarData& sd) {
|
||||||
const std::size_t n = sd.meshes.size();
|
const std::size_t mesh_count = sd.meshes.size();
|
||||||
if (n < 2) return;
|
if (mesh_count < 2) return;
|
||||||
|
|
||||||
// Per-mesh centroid + instance count, exactly as the loader computes them
|
// Per-mesh centroid + instance count, exactly as the loader computes them
|
||||||
// before chunk planning (average of instance world-AABB centres).
|
// before chunk planning (average of instance world-AABB centres).
|
||||||
std::vector<float> cx(n, 0.0f), cy(n, 0.0f), cz(n, 0.0f);
|
std::vector<float> mesh_centroid_x(mesh_count, 0.0f),
|
||||||
std::vector<std::uint32_t> cnt(n, 0);
|
mesh_centroid_y(mesh_count, 0.0f),
|
||||||
|
mesh_centroid_z(mesh_count, 0.0f);
|
||||||
|
std::vector<std::uint32_t> mesh_instance_count(mesh_count, 0);
|
||||||
for (const auto& inst : sd.instances) {
|
for (const auto& inst : sd.instances) {
|
||||||
if (inst.mesh_id >= n) continue;
|
if (inst.mesh_id >= mesh_count) continue;
|
||||||
cx[inst.mesh_id] += 0.5f * (inst.world_aabb_min[0] + inst.world_aabb_max[0]);
|
mesh_centroid_x[inst.mesh_id] += 0.5f * (inst.world_aabb_min[0] + inst.world_aabb_max[0]);
|
||||||
cy[inst.mesh_id] += 0.5f * (inst.world_aabb_min[1] + inst.world_aabb_max[1]);
|
mesh_centroid_y[inst.mesh_id] += 0.5f * (inst.world_aabb_min[1] + inst.world_aabb_max[1]);
|
||||||
cz[inst.mesh_id] += 0.5f * (inst.world_aabb_min[2] + inst.world_aabb_max[2]);
|
mesh_centroid_z[inst.mesh_id] += 0.5f * (inst.world_aabb_min[2] + inst.world_aabb_max[2]);
|
||||||
++cnt[inst.mesh_id];
|
++mesh_instance_count[inst.mesh_id];
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < n; ++i) {
|
for (std::size_t i = 0; i < mesh_count; ++i) {
|
||||||
if (cnt[i] > 0) {
|
if (mesh_instance_count[i] > 0) {
|
||||||
const float inv = 1.0f / float(cnt[i]);
|
const float inv = 1.0f / float(mesh_instance_count[i]);
|
||||||
cx[i] *= inv; cy[i] *= inv; cz[i] *= inv;
|
mesh_centroid_x[i] *= inv;
|
||||||
|
mesh_centroid_y[i] *= inv;
|
||||||
|
mesh_centroid_z[i] *= inv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// order[new_id] = old mesh id, in the loader's Morton order.
|
// order[new_id] = old mesh id, in the loader's Morton order.
|
||||||
const std::vector<std::uint32_t> order =
|
const std::vector<std::uint32_t> order =
|
||||||
ChunkPlanner::sortMeshIdsByMorton(n, cx, cy, cz, cnt);
|
ChunkPlanner::sortMeshIdsByMorton(
|
||||||
|
mesh_count, mesh_centroid_x, mesh_centroid_y, mesh_centroid_z, mesh_instance_count);
|
||||||
|
|
||||||
// Greedy-pack the sorted order into chunks (the same plan the loader used
|
// Greedy-pack the sorted order into chunks (the same plan the loader used
|
||||||
// to derive). Each chunk is a CONSECUTIVE run of `order`, so once we lay
|
// to derive). Each chunk is a CONSECUTIVE run of `order`, so once we lay
|
||||||
// meshes out in `order` the chunk is a contiguous mesh range — recorded in
|
// meshes out in `order` the chunk is a contiguous mesh range — recorded in
|
||||||
// the TOC as {first_mesh, mesh_count}.
|
// the TOC as {first_mesh, mesh_count}.
|
||||||
std::vector<std::uint32_t> mesh_vertex_count(n, 0);
|
std::vector<std::uint32_t> mesh_vertex_count(mesh_count, 0);
|
||||||
for (std::size_t i = 0; i < n; ++i) mesh_vertex_count[i] = sd.meshes[i].vertex_count;
|
for (std::size_t i = 0; i < mesh_count; ++i) mesh_vertex_count[i] = sd.meshes[i].vertex_count;
|
||||||
const std::vector<std::vector<std::uint32_t>> packed = ChunkPlanner::greedyPackChunks(
|
const std::vector<std::vector<std::uint32_t>> packed = ChunkPlanner::greedyPackChunks(
|
||||||
order, mesh_vertex_count, INSTANCED_VERTEX_STRIDE_BYTES,
|
order, mesh_vertex_count, INSTANCED_VERTEX_STRIDE_BYTES,
|
||||||
WGPU_CHUNK_VERTEX_BYTES_LIMIT);
|
WGPU_CHUNK_VERTEX_BYTES_LIMIT);
|
||||||
@@ -74,58 +79,61 @@ void reorderSidecarByMorton(SidecarData& sd) {
|
|||||||
// MeshInfo.first_instance: the baker leaves it 0 for every mesh and stores
|
// MeshInfo.first_instance: the baker leaves it 0 for every mesh and stores
|
||||||
// instances ungrouped, so first_instance describes nothing. Grouping here
|
// instances ungrouped, so first_instance describes nothing. Grouping here
|
||||||
// by mesh_id both reorders instances correctly AND fixes first_instance.
|
// by mesh_id both reorders instances correctly AND fixes first_instance.
|
||||||
std::vector<std::vector<std::uint32_t>> insts_by_mesh(n);
|
std::vector<std::vector<std::uint32_t>> insts_by_mesh(mesh_count);
|
||||||
for (std::uint32_t ii = 0; ii < sd.instances.size(); ++ii) {
|
for (std::uint32_t instance_index = 0; instance_index < sd.instances.size(); ++instance_index) {
|
||||||
const std::uint32_t mid = sd.instances[ii].mesh_id;
|
const std::uint32_t mesh_id = sd.instances[instance_index].mesh_id;
|
||||||
if (mid < n) insts_by_mesh[mid].push_back(ii);
|
if (mesh_id < mesh_count) insts_by_mesh[mesh_id].push_back(instance_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::uint8_t> new_vertices; new_vertices.reserve(sd.vertices.size());
|
std::vector<std::uint8_t> new_vertices; new_vertices.reserve(sd.vertices.size());
|
||||||
std::vector<std::uint32_t> new_indices; new_indices.reserve(sd.indices.size());
|
std::vector<std::uint32_t> new_indices; new_indices.reserve(sd.indices.size());
|
||||||
std::vector<MeshInfo> new_meshes(n);
|
std::vector<MeshInfo> new_meshes(mesh_count);
|
||||||
std::vector<InstanceCpu> new_instances; new_instances.reserve(sd.instances.size());
|
std::vector<InstanceCpu> new_instances; new_instances.reserve(sd.instances.size());
|
||||||
|
|
||||||
// Pass A: vertices + LOD0 indices + instances, mesh-by-mesh in the new
|
// Pass A: vertices + LOD0 indices + instances, mesh-by-mesh in the new
|
||||||
// order, recording the new offsets on each MeshInfo.
|
// order, recording the new offsets on each MeshInfo.
|
||||||
for (std::uint32_t ni = 0; ni < n; ++ni) {
|
for (std::uint32_t new_mesh_index = 0; new_mesh_index < mesh_count; ++new_mesh_index) {
|
||||||
const std::uint32_t old = order[ni];
|
const std::uint32_t old = order[new_mesh_index];
|
||||||
const MeshInfo& om = sd.meshes[old];
|
const MeshInfo& old_mesh_info = sd.meshes[old];
|
||||||
MeshInfo nm = om; // carries AABB; offsets/instance fields overwritten below
|
MeshInfo new_mesh_info = old_mesh_info; // carries AABB; offsets/instance fields overwritten below
|
||||||
|
|
||||||
nm.vbo_byte_offset = std::uint32_t(new_vertices.size());
|
new_mesh_info.vbo_byte_offset = std::uint32_t(new_vertices.size());
|
||||||
const std::size_t vbytes = std::size_t(om.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
|
const std::size_t vbytes = std::size_t(old_mesh_info.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
|
||||||
new_vertices.insert(new_vertices.end(),
|
new_vertices.insert(new_vertices.end(),
|
||||||
sd.vertices.begin() + om.vbo_byte_offset,
|
sd.vertices.begin() + old_mesh_info.vbo_byte_offset,
|
||||||
sd.vertices.begin() + om.vbo_byte_offset + vbytes);
|
sd.vertices.begin() + old_mesh_info.vbo_byte_offset + vbytes);
|
||||||
|
|
||||||
nm.ebo_byte_offset = std::uint32_t(new_indices.size() * sizeof(std::uint32_t));
|
new_mesh_info.ebo_byte_offset = std::uint32_t(new_indices.size() * sizeof(std::uint32_t));
|
||||||
const std::size_t i0 = om.ebo_byte_offset / sizeof(std::uint32_t);
|
const std::size_t i0 = old_mesh_info.ebo_byte_offset / sizeof(std::uint32_t);
|
||||||
new_indices.insert(new_indices.end(),
|
new_indices.insert(new_indices.end(),
|
||||||
sd.indices.begin() + i0,
|
sd.indices.begin() + i0,
|
||||||
sd.indices.begin() + i0 + om.index_count);
|
sd.indices.begin() + i0 + old_mesh_info.index_count);
|
||||||
|
|
||||||
nm.first_instance = std::uint32_t(new_instances.size());
|
new_mesh_info.first_instance = std::uint32_t(new_instances.size());
|
||||||
nm.instance_count = std::uint32_t(insts_by_mesh[old].size());
|
new_mesh_info.instance_count = std::uint32_t(insts_by_mesh[old].size());
|
||||||
for (std::uint32_t ii : insts_by_mesh[old]) {
|
for (std::uint32_t instance_index : insts_by_mesh[old]) {
|
||||||
InstanceCpu ic = sd.instances[ii];
|
InstanceCpu instance = sd.instances[instance_index];
|
||||||
ic.mesh_id = ni;
|
instance.mesh_id = new_mesh_index;
|
||||||
new_instances.push_back(ic);
|
new_instances.push_back(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
new_meshes[ni] = nm;
|
new_meshes[new_mesh_index] = new_mesh_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass B: LOD1 indices appended after all LOD0 (same global layout as the
|
// Pass B: LOD1 indices appended after all LOD0 (same global layout as the
|
||||||
// baker), in the new order, so a chunk's LOD1 slice is contiguous too.
|
// baker), in the new order, so a chunk's LOD1 slice is contiguous too.
|
||||||
for (std::uint32_t ni = 0; ni < n; ++ni) {
|
for (std::uint32_t new_mesh_index = 0; new_mesh_index < mesh_count; ++new_mesh_index) {
|
||||||
const MeshInfo& om = sd.meshes[order[ni]];
|
const MeshInfo& old_mesh_info = sd.meshes[order[new_mesh_index]];
|
||||||
MeshInfo& nm = new_meshes[ni];
|
MeshInfo& new_mesh_info = new_meshes[new_mesh_index];
|
||||||
if (om.lod1_index_count == 0) { nm.lod1_ebo_byte_offset = 0; continue; }
|
if (old_mesh_info.lod1_index_count == 0) {
|
||||||
nm.lod1_ebo_byte_offset = std::uint32_t(new_indices.size() * sizeof(std::uint32_t));
|
new_mesh_info.lod1_ebo_byte_offset = 0;
|
||||||
const std::size_t l0 = om.lod1_ebo_byte_offset / sizeof(std::uint32_t);
|
continue;
|
||||||
|
}
|
||||||
|
new_mesh_info.lod1_ebo_byte_offset = std::uint32_t(new_indices.size() * sizeof(std::uint32_t));
|
||||||
|
const std::size_t l0 = old_mesh_info.lod1_ebo_byte_offset / sizeof(std::uint32_t);
|
||||||
new_indices.insert(new_indices.end(),
|
new_indices.insert(new_indices.end(),
|
||||||
sd.indices.begin() + l0,
|
sd.indices.begin() + l0,
|
||||||
sd.indices.begin() + l0 + om.lod1_index_count);
|
sd.indices.begin() + l0 + old_mesh_info.lod1_index_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
sd.vertices = std::move(new_vertices);
|
sd.vertices = std::move(new_vertices);
|
||||||
|
|||||||
@@ -52,25 +52,25 @@ struct SidecarHeaderRaw {
|
|||||||
// walks the metadata tail through one of these so a truncated buffer fails
|
// walks the metadata tail through one of these so a truncated buffer fails
|
||||||
// cleanly (return false) instead of reading out of bounds.
|
// cleanly (return false) instead of reading out of bounds.
|
||||||
struct BufCursor {
|
struct BufCursor {
|
||||||
const uint8_t* p;
|
const uint8_t* cursor;
|
||||||
size_t remaining;
|
size_t remaining_bytes;
|
||||||
|
|
||||||
bool take(void* dst, size_t bytes) {
|
bool take(void* dst, size_t bytes) {
|
||||||
if (bytes > remaining) return false;
|
if (bytes > remaining_bytes) return false;
|
||||||
std::memcpy(dst, p, bytes);
|
std::memcpy(dst, cursor, bytes);
|
||||||
p += bytes;
|
cursor += bytes;
|
||||||
remaining -= bytes;
|
remaining_bytes -= bytes;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read a uint32 length prefix followed by length*sizeof(T) elements.
|
// Read a uint32 length prefix followed by length*sizeof(T) elements.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
bool takeVec(std::vector<T>& v) {
|
bool takeVec(std::vector<T>& values) {
|
||||||
uint32_t n;
|
uint32_t n;
|
||||||
if (!take(&n, 4)) return false;
|
if (!take(&n, 4)) return false;
|
||||||
if (uint64_t(n) * sizeof(T) > remaining) return false;
|
if (uint64_t(n) * sizeof(T) > remaining_bytes) return false;
|
||||||
v.resize(n);
|
values.resize(n);
|
||||||
if (n > 0 && !take(v.data(), size_t(n) * sizeof(T))) return false;
|
if (n > 0 && !take(values.data(), size_t(n) * sizeof(T))) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -119,7 +119,7 @@ bool parseSidecarDeferred(const uint8_t* data, size_t n, SidecarData& out) {
|
|||||||
if (!c.takeVec(out.elements)) return false;
|
if (!c.takeVec(out.elements)) return false;
|
||||||
uint32_t stbl_len = 0;
|
uint32_t stbl_len = 0;
|
||||||
if (!c.take(&stbl_len, 4)) return false;
|
if (!c.take(&stbl_len, 4)) return false;
|
||||||
if (stbl_len > c.remaining) return false;
|
if (stbl_len > c.remaining_bytes) return false;
|
||||||
out.string_table.resize(stbl_len);
|
out.string_table.resize(stbl_len);
|
||||||
if (stbl_len > 0 && !c.take(out.string_table.data(), stbl_len)) return false;
|
if (stbl_len > 0 && !c.take(out.string_table.data(), stbl_len)) return false;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -26,23 +26,23 @@ StreamingThread::~StreamingThread() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void StreamingThread::start() {
|
void StreamingThread::start() {
|
||||||
std::unique_lock lk(mu_);
|
std::unique_lock lock(mu_);
|
||||||
if (running_) return;
|
if (running_) return;
|
||||||
shutdown_ = false;
|
shutdown_ = false;
|
||||||
running_ = true;
|
running_ = true;
|
||||||
lk.unlock();
|
lock.unlock();
|
||||||
worker_ = std::thread(&StreamingThread::workerLoop, this);
|
worker_ = std::thread(&StreamingThread::workerLoop, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamingThread::stop() {
|
void StreamingThread::stop() {
|
||||||
{
|
{
|
||||||
std::unique_lock lk(mu_);
|
std::unique_lock lock(mu_);
|
||||||
if (!running_) return;
|
if (!running_) return;
|
||||||
shutdown_ = true;
|
shutdown_ = true;
|
||||||
}
|
}
|
||||||
cv_.notify_all();
|
cv_.notify_all();
|
||||||
if (worker_.joinable()) worker_.join();
|
if (worker_.joinable()) worker_.join();
|
||||||
std::unique_lock lk(mu_);
|
std::unique_lock lock(mu_);
|
||||||
running_ = false;
|
running_ = false;
|
||||||
requests_.clear();
|
requests_.clear();
|
||||||
results_.clear();
|
results_.clear();
|
||||||
@@ -50,7 +50,7 @@ void StreamingThread::stop() {
|
|||||||
|
|
||||||
bool StreamingThread::enqueue(Request req) {
|
bool StreamingThread::enqueue(Request req) {
|
||||||
{
|
{
|
||||||
std::unique_lock lk(mu_);
|
std::unique_lock lock(mu_);
|
||||||
if (!running_ || shutdown_) return false;
|
if (!running_ || shutdown_) return false;
|
||||||
requests_.push_back(std::move(req));
|
requests_.push_back(std::move(req));
|
||||||
}
|
}
|
||||||
@@ -59,20 +59,20 @@ bool StreamingThread::enqueue(Request req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<StreamingThread::Result> StreamingThread::drainResults() {
|
std::vector<StreamingThread::Result> StreamingThread::drainResults() {
|
||||||
std::vector<Result> out;
|
std::vector<Result> results;
|
||||||
{
|
{
|
||||||
std::unique_lock lk(mu_);
|
std::unique_lock lock(mu_);
|
||||||
out.reserve(results_.size());
|
results.reserve(results_.size());
|
||||||
while (!results_.empty()) {
|
while (!results_.empty()) {
|
||||||
out.push_back(std::move(results_.front()));
|
results.push_back(std::move(results_.front()));
|
||||||
results_.pop_front();
|
results_.pop_front();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t StreamingThread::inFlightApprox() const {
|
std::size_t StreamingThread::inFlightApprox() const {
|
||||||
std::unique_lock lk(mu_);
|
std::unique_lock lock(mu_);
|
||||||
return requests_.size() + (in_progress_ ? 1u : 0u);
|
return requests_.size() + (in_progress_ ? 1u : 0u);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +80,8 @@ void StreamingThread::workerLoop() {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
Request req;
|
Request req;
|
||||||
{
|
{
|
||||||
std::unique_lock lk(mu_);
|
std::unique_lock lock(mu_);
|
||||||
cv_.wait(lk, [this]() { return shutdown_ || !requests_.empty(); });
|
cv_.wait(lock, [this]() { return shutdown_ || !requests_.empty(); });
|
||||||
if (shutdown_ && requests_.empty()) return;
|
if (shutdown_ && requests_.empty()) return;
|
||||||
req = std::move(requests_.front());
|
req = std::move(requests_.front());
|
||||||
requests_.pop_front();
|
requests_.pop_front();
|
||||||
@@ -94,18 +94,18 @@ void StreamingThread::workerLoop() {
|
|||||||
// us. The vbytes / idx buffers are allocated here on the worker
|
// us. The vbytes / idx buffers are allocated here on the worker
|
||||||
// thread — they cross back to the main thread when the result
|
// thread — they cross back to the main thread when the result
|
||||||
// is drained and applied (pool.alloc + queueWriteBuffer).
|
// is drained and applied (pool.alloc + queueWriteBuffer).
|
||||||
Result res;
|
Result result;
|
||||||
res.model_id = req.model_id;
|
result.model_id = req.model_id;
|
||||||
res.chunk_idx = req.chunk_idx;
|
result.chunk_idx = req.chunk_idx;
|
||||||
res.success = readChunkGeometryCompressed(
|
result.success = readChunkGeometryCompressed(
|
||||||
req.file_path, req.geometry_section_offset,
|
req.file_path, req.geometry_section_offset,
|
||||||
req.v_comp_off, req.v_comp_size, req.v_raw_size,
|
req.v_comp_off, req.v_comp_size, req.v_raw_size,
|
||||||
req.i_comp_off, req.i_comp_size, req.i_raw_size,
|
req.i_comp_off, req.i_comp_size, req.i_raw_size,
|
||||||
res.vbytes, res.idx);
|
result.vbytes, result.idx);
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unique_lock lk(mu_);
|
std::unique_lock lock(mu_);
|
||||||
results_.push_back(std::move(res));
|
results_.push_back(std::move(result));
|
||||||
in_progress_ = false;
|
in_progress_ = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user