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;