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 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-04-22 16:22:48 +10:00
parent 4f929e90a7
commit 35be7f4190
3 changed files with 23 additions and 2 deletions
+10
View File
@@ -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);
+1
View File
@@ -48,6 +48,7 @@ public:
private slots:
void onFileOpen();
void onDatabaseOpen();
void onFileSettings();
void onObjectPicked(uint32_t object_id);
void onTreeSelectionChanged();
+12 -2
View File
@@ -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<ifcopenshell::file>(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<ifcopenshell::file>(
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<ifcopenshell::impl::rocks_db_file_storage>(ifc_file_->storage_);
const int effective_threads = is_rocksdb ? 1 : num_threads;
std::unique_ptr<IfcGeom::Iterator> 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<IfcGeom::Iterator>(
std::move(kernel), settings, ifc_file_.get(), std::vector<ifcopenshell::geometry::filter_t>(), num_threads);
std::move(kernel), settings, ifc_file_.get(),
std::vector<ifcopenshell::geometry::filter_t>(), effective_threads);
} catch (const std::exception& e) {
emit errorOccurred(QString("Failed to create geometry iterator: %1").arg(e.what()));
return;