From 35be7f41903b5c94f0577c92959bf2ecc617e23b Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 22 Apr 2026 16:22:48 +1000 Subject: [PATCH] ifcviewer: load RocksDB-backed IFC models The viewer can now open a .rdb directory (as produced by RocksDbSerializer / convert_path_to_rocksdb) anywhere it accepts an .ifc file. The full GUI gets an "Add Database..." File menu entry that opens a directory chooser; the streamer lets the file constructor autodetect the format and opens the store read-only so multiple viewers can share a database without taking the exclusive RocksDB lock. Parallel mapping on RocksDB-backed files still produces non-deterministic shape counts (the race is outside the instance cache), so force num_threads=1 for the iterator when the storage is RocksDB. Serial RocksDB (~2.6s) and parallel SPF (~0.7s) both produce 107 shapes on AC20-FZK-Haus; @todo in-source points at the remaining thread-safety work. Co-Authored-By: Claude Opus 4.7 --- src/ifcviewer-full/MainWindow.cpp | 10 ++++++++++ src/ifcviewer-full/MainWindow.h | 1 + src/ifcviewer/GeometryStreamer.cpp | 14 ++++++++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/ifcviewer-full/MainWindow.cpp b/src/ifcviewer-full/MainWindow.cpp index 8a818f948e..8379b77b2c 100644 --- a/src/ifcviewer-full/MainWindow.cpp +++ b/src/ifcviewer-full/MainWindow.cpp @@ -132,6 +132,7 @@ void MainWindow::setupMenus() { auto* file_menu = menuBar()->addMenu("&File"); auto* open_action = file_menu->addAction("&Add Files...", this, &MainWindow::onFileOpen); open_action->setShortcut(QKeySequence::Open); + file_menu->addAction("Add &Database...", this, &MainWindow::onDatabaseOpen); file_menu->addAction("&Settings...", this, &MainWindow::onFileSettings); file_menu->addSeparator(); file_menu->addAction("&Quit", QKeySequence::Quit, qApp, &QApplication::quit); @@ -146,6 +147,15 @@ void MainWindow::onFileOpen() { } } +void MainWindow::onDatabaseOpen() { + QString path = QFileDialog::getExistingDirectory( + this, "Add IFC Database", QString(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (!path.isEmpty()) { + addFiles({ path }); + } +} + void MainWindow::onFileSettings() { if (settings_ == nullptr) { settings_ = new SettingsWindow(this); diff --git a/src/ifcviewer-full/MainWindow.h b/src/ifcviewer-full/MainWindow.h index 71de00dbdf..031764d1c4 100644 --- a/src/ifcviewer-full/MainWindow.h +++ b/src/ifcviewer-full/MainWindow.h @@ -48,6 +48,7 @@ public: private slots: void onFileOpen(); + void onDatabaseOpen(); void onFileSettings(); void onObjectPicked(uint32_t object_id); void onTreeSelectionChanged(); diff --git a/src/ifcviewer/GeometryStreamer.cpp b/src/ifcviewer/GeometryStreamer.cpp index 3f836b3490..62a276dad3 100644 --- a/src/ifcviewer/GeometryStreamer.cpp +++ b/src/ifcviewer/GeometryStreamer.cpp @@ -263,7 +263,10 @@ static void worldAabbFromLocal(const float local_min[3], void GeometryStreamer::run(const std::string& path, int num_threads) { try { - ifc_file_ = std::make_unique(path); + // read_only is a no-op for SPF; for RocksDB it allows concurrent + // readers and avoids acquiring the exclusive DB lock. + ifc_file_ = std::make_unique( + path, ifcopenshell::FT_AUTODETECT, /*read_only=*/true); } catch (const std::exception& e) { emit errorOccurred(QString("Failed to parse IFC file: %1").arg(e.what())); return; @@ -281,6 +284,12 @@ void GeometryStreamer::run(const std::string& path, int num_threads) { // time, but results are cached in the sidecar so it's a one-shot hit. settings.set("reorient-shells", true); + // @todo parallel mapping on RocksDB-backed files still races somewhere + // outside the instance cache, producing inconsistent shape counts. Force + // serial iteration for RocksDB until the read path is fully thread-safe. + const bool is_rocksdb = std::holds_alternative(ifc_file_->storage_); + const int effective_threads = is_rocksdb ? 1 : num_threads; + std::unique_ptr iterator; try { const std::string geometry_library = @@ -288,7 +297,8 @@ void GeometryStreamer::run(const std::string& path, int num_threads) { auto kernel = ifcopenshell::geometry::kernels::construct( ifc_file_.get(), geometry_library, settings); iterator = std::make_unique( - std::move(kernel), settings, ifc_file_.get(), std::vector(), num_threads); + std::move(kernel), settings, ifc_file_.get(), + std::vector(), effective_threads); } catch (const std::exception& e) { emit errorOccurred(QString("Failed to create geometry iterator: %1").arg(e.what())); return;