mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-17 22:11:36 +00:00
Fix viewer load termination
Handle streamer success, failure, and cancellation as distinct terminal states so failed or cancelled loads do not finalize as successful models. Clean up partial model/UI state in the full and minimal viewer apps when a load is cancelled or fails. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -53,6 +53,8 @@ MainWindow::MainWindow(QWidget* parent)
|
|||||||
this, &MainWindow::onStreamedElementsReady);
|
this, &MainWindow::onStreamedElementsReady);
|
||||||
connect(loader_, &SceneLoader::loadedFromStream,
|
connect(loader_, &SceneLoader::loadedFromStream,
|
||||||
this, &MainWindow::onLoadedFromStream);
|
this, &MainWindow::onLoadedFromStream);
|
||||||
|
connect(loader_, &SceneLoader::loadCancelled,
|
||||||
|
this, &MainWindow::onLoadCancelled);
|
||||||
connect(loader_, &SceneLoader::loadError,
|
connect(loader_, &SceneLoader::loadError,
|
||||||
this, &MainWindow::onLoadError);
|
this, &MainWindow::onLoadError);
|
||||||
connect(loader_, &SceneLoader::allLoadsFinished,
|
connect(loader_, &SceneLoader::allLoadsFinished,
|
||||||
@@ -297,6 +299,42 @@ void MainWindow::writeSidecarForModel(uint32_t mid) {
|
|||||||
qDebug(" Sidecar write: %lld ms (%s)", t.elapsed(), ok ? "ok" : "FAILED");
|
qDebug(" Sidecar write: %lld ms (%s)", t.elapsed(), ok ? "ok" : "FAILED");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::removeModelUi(uint32_t mid) {
|
||||||
|
auto root_it = tree_roots_.find(mid);
|
||||||
|
if (root_it != tree_roots_.end()) {
|
||||||
|
delete root_it->second;
|
||||||
|
tree_roots_.erase(root_it);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = tree_items_.begin(); it != tree_items_.end();) {
|
||||||
|
auto info_it = element_map_.find(it->first);
|
||||||
|
if (info_it != element_map_.end() && info_it->second.model_id == mid) {
|
||||||
|
it = tree_items_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = element_map_.begin(); it != element_map_.end();) {
|
||||||
|
if (it->second.model_id == mid) {
|
||||||
|
it = element_map_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = scoped_ifc_id_to_object_id_.begin(); it != scoped_ifc_id_to_object_id_.end();) {
|
||||||
|
if (static_cast<uint32_t>(it->first >> 32) == mid) {
|
||||||
|
it = scoped_ifc_id_to_object_id_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewport_->setSelectedObjectId(0);
|
||||||
|
property_table_->setRowCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) {
|
void MainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) {
|
||||||
progress_bar_->setVisible(false);
|
progress_bar_->setVisible(false);
|
||||||
status_label_->setText(QString("%1 elements across %2 model(s) — last loaded in %3")
|
status_label_->setText(QString("%1 elements across %2 model(s) — last loaded in %3")
|
||||||
@@ -307,7 +345,16 @@ void MainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) {
|
|||||||
writeSidecarForModel(mid);
|
writeSidecarForModel(mid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onLoadError(uint32_t /*mid*/, QString message) {
|
void MainWindow::onLoadCancelled(uint32_t mid) {
|
||||||
|
progress_bar_->setVisible(false);
|
||||||
|
removeModelUi(mid);
|
||||||
|
status_label_->setText(QString("%1 load cancelled").arg(loader_->displayName(mid)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onLoadError(uint32_t mid, QString message) {
|
||||||
|
progress_bar_->setVisible(false);
|
||||||
|
removeModelUi(mid);
|
||||||
|
status_label_->setText("Error: " + message);
|
||||||
QMessageBox::warning(this, "Error", message);
|
QMessageBox::warning(this, "Error", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ private slots:
|
|||||||
void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms);
|
void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms);
|
||||||
void onStreamedElementsReady(uint32_t mid, std::vector<ElementInfo> elements);
|
void onStreamedElementsReady(uint32_t mid, std::vector<ElementInfo> elements);
|
||||||
void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms);
|
void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms);
|
||||||
|
void onLoadCancelled(uint32_t mid);
|
||||||
void onLoadError(uint32_t mid, QString message);
|
void onLoadError(uint32_t mid, QString message);
|
||||||
void onAllLoadsFinished();
|
void onAllLoadsFinished();
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ private:
|
|||||||
const std::string& name,
|
const std::string& name,
|
||||||
const std::string& type);
|
const std::string& type);
|
||||||
void writeSidecarForModel(uint32_t mid);
|
void writeSidecarForModel(uint32_t mid);
|
||||||
|
void removeModelUi(uint32_t mid);
|
||||||
void applyPendingBenchmark();
|
void applyPendingBenchmark();
|
||||||
QString formatElapsed(qint64 ms) const;
|
QString formatElapsed(qint64 ms) const;
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ MinimalWindow::MinimalWindow(QWidget* parent)
|
|||||||
this, &MinimalWindow::onLoadedFromSidecar);
|
this, &MinimalWindow::onLoadedFromSidecar);
|
||||||
connect(loader_, &SceneLoader::loadedFromStream,
|
connect(loader_, &SceneLoader::loadedFromStream,
|
||||||
this, &MinimalWindow::onLoadedFromStream);
|
this, &MinimalWindow::onLoadedFromStream);
|
||||||
|
connect(loader_, &SceneLoader::loadCancelled,
|
||||||
|
this, &MinimalWindow::onLoadCancelled);
|
||||||
connect(loader_, &SceneLoader::loadError,
|
connect(loader_, &SceneLoader::loadError,
|
||||||
this, &MinimalWindow::onLoadError);
|
this, &MinimalWindow::onLoadError);
|
||||||
connect(loader_, &SceneLoader::allLoadsFinished,
|
connect(loader_, &SceneLoader::allLoadsFinished,
|
||||||
@@ -100,6 +102,11 @@ void MinimalWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) {
|
|||||||
.arg(formatElapsed(elapsed_ms)));
|
.arg(formatElapsed(elapsed_ms)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::onLoadCancelled(uint32_t mid) {
|
||||||
|
status_label_->setText(QString("%1 load cancelled")
|
||||||
|
.arg(loader_->displayName(mid)));
|
||||||
|
}
|
||||||
|
|
||||||
void MinimalWindow::onLoadError(uint32_t /*mid*/, QString message) {
|
void MinimalWindow::onLoadError(uint32_t /*mid*/, QString message) {
|
||||||
qWarning("IfcViewerMinimal error: %s", qPrintable(message));
|
qWarning("IfcViewerMinimal error: %s", qPrintable(message));
|
||||||
status_label_->setText("Error: " + message);
|
status_label_->setText("Error: " + message);
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ private slots:
|
|||||||
void onLoadStarted(uint32_t mid, QString display_name);
|
void onLoadStarted(uint32_t mid, QString display_name);
|
||||||
void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms);
|
void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms);
|
||||||
void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms);
|
void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms);
|
||||||
|
void onLoadCancelled(uint32_t mid);
|
||||||
void onLoadError(uint32_t mid, QString message);
|
void onLoadError(uint32_t mid, QString message);
|
||||||
void onAllLoadsFinished();
|
void onAllLoadsFinished();
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ void GeometryStreamer::loadFile(const std::string& path, uint32_t start_object_i
|
|||||||
}
|
}
|
||||||
|
|
||||||
cancel_requested_ = false;
|
cancel_requested_ = false;
|
||||||
|
succeeded_ = false;
|
||||||
running_ = true;
|
running_ = true;
|
||||||
progress_ = 0;
|
progress_ = 0;
|
||||||
next_object_id_ = start_object_id;
|
next_object_id_ = start_object_id;
|
||||||
@@ -115,7 +116,11 @@ void GeometryStreamer::loadFile(const std::string& path, uint32_t start_object_i
|
|||||||
|
|
||||||
connect(worker_thread_.get(), &QThread::finished, this, [this]() {
|
connect(worker_thread_.get(), &QThread::finished, this, [this]() {
|
||||||
running_ = false;
|
running_ = false;
|
||||||
emit finished();
|
if (succeeded_.load()) {
|
||||||
|
emit finished();
|
||||||
|
} else if (cancel_requested_.load()) {
|
||||||
|
emit cancelled();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
worker_thread_->start();
|
worker_thread_->start();
|
||||||
@@ -402,4 +407,5 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
|
|||||||
qDebug("Streamer done: %s %.2fs shapes=%u unique_meshes=%u dedup=%.2fx",
|
qDebug("Streamer done: %s %.2fs shapes=%u unique_meshes=%u dedup=%.2fx",
|
||||||
path.c_str(), stream_timer.elapsed() / 1000.0,
|
path.c_str(), stream_timer.elapsed() / 1000.0,
|
||||||
total_shapes, total_meshes, dedup_ratio);
|
total_shapes, total_meshes, dedup_ratio);
|
||||||
|
succeeded_ = !cancel_requested_.load();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ signals:
|
|||||||
void meshReady(MeshChunk chunk);
|
void meshReady(MeshChunk chunk);
|
||||||
void instanceReady(InstanceChunk chunk);
|
void instanceReady(InstanceChunk chunk);
|
||||||
void finished();
|
void finished();
|
||||||
|
void cancelled();
|
||||||
void errorOccurred(const QString& message);
|
void errorOccurred(const QString& message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -77,6 +78,7 @@ private:
|
|||||||
std::unique_ptr<QThread> worker_thread_;
|
std::unique_ptr<QThread> worker_thread_;
|
||||||
std::atomic<bool> running_{false};
|
std::atomic<bool> running_{false};
|
||||||
std::atomic<bool> cancel_requested_{false};
|
std::atomic<bool> cancel_requested_{false};
|
||||||
|
std::atomic<bool> succeeded_{false};
|
||||||
std::atomic<int> progress_{0};
|
std::atomic<int> progress_{0};
|
||||||
|
|
||||||
std::mutex elements_mutex_;
|
std::mutex elements_mutex_;
|
||||||
|
|||||||
@@ -95,10 +95,19 @@ void SceneLoader::connectStreamer(GeometryStreamer* streamer) {
|
|||||||
this, &SceneLoader::onStreamerInstanceReady, Qt::QueuedConnection);
|
this, &SceneLoader::onStreamerInstanceReady, Qt::QueuedConnection);
|
||||||
connect(streamer, &GeometryStreamer::finished,
|
connect(streamer, &GeometryStreamer::finished,
|
||||||
this, &SceneLoader::onStreamerFinished, Qt::QueuedConnection);
|
this, &SceneLoader::onStreamerFinished, Qt::QueuedConnection);
|
||||||
|
connect(streamer, &GeometryStreamer::cancelled,
|
||||||
|
this, &SceneLoader::onStreamerCancelled, Qt::QueuedConnection);
|
||||||
connect(streamer, &GeometryStreamer::errorOccurred,
|
connect(streamer, &GeometryStreamer::errorOccurred,
|
||||||
this, &SceneLoader::onStreamerError, Qt::QueuedConnection);
|
this, &SceneLoader::onStreamerError, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SceneLoader::cancelCurrentLoad() {
|
||||||
|
if (loading_model_id_ == 0) return;
|
||||||
|
auto it = models_.find(loading_model_id_);
|
||||||
|
if (it == models_.end() || it->second.streamer == nullptr) return;
|
||||||
|
it->second.streamer->cancel();
|
||||||
|
}
|
||||||
|
|
||||||
void SceneLoader::startNextLoad() {
|
void SceneLoader::startNextLoad() {
|
||||||
if (load_queue_.empty()) {
|
if (load_queue_.empty()) {
|
||||||
loading_model_id_ = 0;
|
loading_model_id_ = 0;
|
||||||
@@ -232,6 +241,28 @@ void SceneLoader::onStreamerFinished() {
|
|||||||
startNextLoad();
|
startNextLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneLoader::onStreamerError(const QString& msg) {
|
void SceneLoader::onStreamerCancelled() {
|
||||||
emit loadError(loading_model_id_, msg);
|
element_poll_timer_.stop();
|
||||||
|
|
||||||
|
const uint32_t mid = loading_model_id_;
|
||||||
|
loading_model_id_ = 0;
|
||||||
|
|
||||||
|
if (mid != 0) {
|
||||||
|
viewport_->removeModel(mid);
|
||||||
|
emit loadCancelled(mid);
|
||||||
|
}
|
||||||
|
QTimer::singleShot(0, this, &SceneLoader::startNextLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneLoader::onStreamerError(const QString& msg) {
|
||||||
|
element_poll_timer_.stop();
|
||||||
|
|
||||||
|
const uint32_t mid = loading_model_id_;
|
||||||
|
loading_model_id_ = 0;
|
||||||
|
|
||||||
|
if (mid != 0) {
|
||||||
|
viewport_->removeModel(mid);
|
||||||
|
}
|
||||||
|
emit loadError(mid, msg);
|
||||||
|
QTimer::singleShot(0, this, &SceneLoader::startNextLoad);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ public:
|
|||||||
// Callers can use these to set up per-model UI state (tree roots, etc.)
|
// Callers can use these to set up per-model UI state (tree roots, etc.)
|
||||||
// before any load signal fires.
|
// before any load signal fires.
|
||||||
std::vector<uint32_t> addFiles(const QStringList& paths);
|
std::vector<uint32_t> addFiles(const QStringList& paths);
|
||||||
|
void cancelCurrentLoad();
|
||||||
bool isLoading() const { return loading_model_id_ != 0 || !load_queue_.empty(); }
|
bool isLoading() const { return loading_model_id_ != 0 || !load_queue_.empty(); }
|
||||||
size_t modelCount() const { return models_.size(); }
|
size_t modelCount() const { return models_.size(); }
|
||||||
|
|
||||||
@@ -87,6 +88,7 @@ signals:
|
|||||||
// elements to be known (e.g. sidecar write) — SceneLoader will only
|
// elements to be known (e.g. sidecar write) — SceneLoader will only
|
||||||
// start the next queued load after all slots return.
|
// start the next queued load after all slots return.
|
||||||
void loadedFromStream(uint32_t mid, qint64 elapsed_ms);
|
void loadedFromStream(uint32_t mid, qint64 elapsed_ms);
|
||||||
|
void loadCancelled(uint32_t mid);
|
||||||
|
|
||||||
void loadError(uint32_t mid, QString message);
|
void loadError(uint32_t mid, QString message);
|
||||||
void allLoadsFinished();
|
void allLoadsFinished();
|
||||||
@@ -96,6 +98,7 @@ private slots:
|
|||||||
void onStreamerMeshReady(MeshChunk chunk);
|
void onStreamerMeshReady(MeshChunk chunk);
|
||||||
void onStreamerInstanceReady(InstanceChunk chunk);
|
void onStreamerInstanceReady(InstanceChunk chunk);
|
||||||
void onStreamerFinished();
|
void onStreamerFinished();
|
||||||
|
void onStreamerCancelled();
|
||||||
void onStreamerError(const QString& msg);
|
void onStreamerError(const QString& msg);
|
||||||
void onElementPollTick();
|
void onElementPollTick();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user