diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 4d3f05fd10..e4377c5dd2 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -547,9 +547,9 @@ if(BUILD_IFCGEOM) add_subdirectory(../src/ifcgeom ifcgeom) endif(BUILD_IFCGEOM) -if(BUILD_CONVERT OR BUILD_IFCPYTHON) +if(BUILD_CONVERT OR BUILD_IFCPYTHON OR BUILD_IFCVIEWER) add_subdirectory(../src/serializers serializers) -endif(BUILD_CONVERT OR BUILD_IFCPYTHON) +endif(BUILD_CONVERT OR BUILD_IFCPYTHON OR BUILD_IFCVIEWER) if(BUILD_CONVERT) add_subdirectory(../src/ifcconvert ifcconvert) diff --git a/src/ifcviewer-full/modules/models/AddModelDialog.cpp b/src/ifcviewer-full/modules/models/AddModelDialog.cpp index 841ddc26ab..2210c35b2f 100644 --- a/src/ifcviewer-full/modules/models/AddModelDialog.cpp +++ b/src/ifcviewer-full/modules/models/AddModelDialog.cpp @@ -120,8 +120,9 @@ void AddModelDialog::setupUi() { default_description)); auto* convert_database = components::buttons::makeButton("Convert IFC File\nto Database", ":/icons/database-restore.svg", choices); - connect(convert_database, &QToolButton::clicked, this, [description]() { - description->setText("IFC-to-database conversion is coming soon."); + connect(convert_database, &QToolButton::clicked, this, [this]() { + selected_mode_ = SourceMode::ConvertToDatabase; + accept(); }); convert_database->installEventFilter(new HoverDescriptionFilter( description, diff --git a/src/ifcviewer-full/modules/models/AddModelDialog.h b/src/ifcviewer-full/modules/models/AddModelDialog.h index 905b562349..4092da684e 100644 --- a/src/ifcviewer-full/modules/models/AddModelDialog.h +++ b/src/ifcviewer-full/modules/models/AddModelDialog.h @@ -30,6 +30,7 @@ enum class SourceMode { IfcFile, IfcDatabase, GeometryOnly, + ConvertToDatabase, }; class AddModelDialog : public components::Dialog { diff --git a/src/ifcviewer-full/modules/models/Controller.cpp b/src/ifcviewer-full/modules/models/Controller.cpp index 4815c1ca97..39e7deb082 100644 --- a/src/ifcviewer-full/modules/models/Controller.cpp +++ b/src/ifcviewer-full/modules/models/Controller.cpp @@ -31,16 +31,23 @@ #include "../../../ifcviewer/SceneLoader.h" #include "../../../ifcviewer/SidecarCache.h" #include "../../../ifcviewer/ViewportWindow.h" +#include "../../../ifcgeom/Serializer.h" +#include "../../../serializers/document_serializer_plugin.h" +#include #include #include -#include +#include #include #include +#include +#include #include #include +#include + namespace ifcviewerfull::modules::models { ModelsPanelController::ModelsPanelController(QWidget* host, @@ -195,6 +202,9 @@ void ModelsPanelController::addFiles() { } break; } + case modules::models::SourceMode::ConvertToDatabase: + convertIfcToDatabase(); + return; case modules::models::SourceMode::None: return; } @@ -202,6 +212,123 @@ void ModelsPanelController::addFiles() { addFiles(paths); } +void ModelsPanelController::convertIfcToDatabase() { + QFileDialog input_dialog(host_, "Select IFC File to Convert"); + input_dialog.setFileMode(QFileDialog::ExistingFile); + input_dialog.setNameFilter("IFC Files (*.ifc);;All Files (*)"); + input_dialog.setOption(QFileDialog::DontUseNativeDialog, true); + if (input_dialog.exec() != QDialog::Accepted) return; + const QStringList inputs = input_dialog.selectedFiles(); + if (inputs.isEmpty()) return; + const QString input_path = inputs.first(); + + const QFileInfo input_info(input_path); + const QString default_output = input_info.absoluteDir().filePath(input_info.completeBaseName() + ".rdb"); + + QFileDialog output_dialog(host_, "Save IFC Database As"); + output_dialog.setAcceptMode(QFileDialog::AcceptSave); + output_dialog.setFileMode(QFileDialog::AnyFile); + output_dialog.setNameFilter("IFC Database (*.rdb);;All Files (*)"); + output_dialog.setDefaultSuffix("rdb"); + output_dialog.selectFile(default_output); + output_dialog.setOption(QFileDialog::DontUseNativeDialog, true); + output_dialog.setOption(QFileDialog::DontConfirmOverwrite, true); + if (output_dialog.exec() != QDialog::Accepted) return; + const QStringList outputs = output_dialog.selectedFiles(); + if (outputs.isEmpty()) return; + QString output_path = outputs.first(); + if (!output_path.endsWith(".rdb", Qt::CaseInsensitive)) { + output_path += ".rdb"; + } + + const QFileInfo output_info(output_path); + if (output_info.exists()) { + const QString message = QString("'%1' already exists. Overwrite?").arg(output_info.fileName()); + if (QMessageBox::question(host_, "Convert IFC to Database", message, + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + + runIfcToDatabaseConversion(input_path, output_path); +} + +void ModelsPanelController::runIfcToDatabaseConversion(const QString& input_path, const QString& output_path) { + auto* progress = new QProgressDialog(host_); + progress->setWindowTitle("Convert IFC to Database"); + progress->setLabelText(QString("Converting %1 to %2…") + .arg(QFileInfo(input_path).fileName(), + QFileInfo(output_path).fileName())); + progress->setRange(0, 0); + progress->setCancelButton(nullptr); + progress->setMinimumDuration(0); + progress->setWindowModality(Qt::ApplicationModal); + progress->setAutoClose(false); + progress->setAutoReset(false); + progress->show(); + + session_state_->setStatusMessage("Converting", + QString("%1 → %2").arg(QFileInfo(input_path).fileName(), QFileInfo(output_path).fileName())); + + auto timer = std::make_shared(); + timer->start(); + auto error_message = std::make_shared(); + + QThread* thread = QThread::create([input_path, output_path, error_message]() { + try { + ifcopenshell::serializers::document_serializer_context context; + context.file = nullptr; + context.input_filename = input_path.toStdString(); + context.output_filename = output_path.toStdString(); + context.stream = true; + + auto& registry = ifcopenshell::serializers::document_serializer_registry_instance(); + const auto* info = registry.find("rdb"); + if (!info) { + throw ifcopenshell::exception( + "No 'rdb' document serializer is registered. The RocksDB serializer plugin may not be installed."); + } + if (!info->supports_input_filename) { + throw ifcopenshell::exception("RDB serializer does not support streaming from an input filename"); + } + + boost::shared_ptr serializer = registry.create("rdb", context); + serializer->finalize(); + } catch (const std::exception& e) { + *error_message = QString::fromUtf8(e.what()); + } catch (...) { + *error_message = "Unknown error during IFC to RDB conversion"; + } + }); + + connect(thread, &QThread::finished, this, + [this, thread, progress, timer, error_message, input_path, output_path]() { + const qint64 elapsed = timer->elapsed(); + + progress->close(); + progress->deleteLater(); + thread->deleteLater(); + + if (!error_message->isEmpty()) { + session_state_->setStatusMessage("Error", *error_message); + QMessageBox::warning(host_, "Convert IFC to Database", + QString("Conversion failed:\n%1").arg(*error_message)); + return; + } + + session_state_->setStatusMessage( + "Converted", + QString("%1 → %2 in %3") + .arg(QFileInfo(input_path).fileName(), + QFileInfo(output_path).fileName(), + formatElapsed(elapsed))); + QMessageBox::information(host_, "Convert IFC to Database", + QString("Database written to:\n%1").arg(output_path)); + }); + + thread->start(); +} + void ModelsPanelController::addFiles(const QStringList& paths) { QStringList accepted_paths; QStringList accepted_fed_ids; diff --git a/src/ifcviewer-full/modules/models/Controller.h b/src/ifcviewer-full/modules/models/Controller.h index 95c1b06dff..bf23724d6b 100644 --- a/src/ifcviewer-full/modules/models/Controller.h +++ b/src/ifcviewer-full/modules/models/Controller.h @@ -51,10 +51,12 @@ public: void loadModels(const QStringList& paths, const QStringList& fed_ids); void removeLoadedModel(const QString& fed_id); void openSettings(); + void convertIfcToDatabase(); private: QString formatElapsed(qint64 ms) const; void writeSidecarForModel(SceneLoader* loader, uint32_t mid) const; + void runIfcToDatabaseConversion(const QString& input_path, const QString& output_path); QWidget* host_ = nullptr; ModelsPanel* widget_ = nullptr; diff --git a/src/serializers/CMakeLists.txt b/src/serializers/CMakeLists.txt index ccdeb08bf8..b7f9a7675e 100644 --- a/src/serializers/CMakeLists.txt +++ b/src/serializers/CMakeLists.txt @@ -1,5 +1,5 @@ -set(document_serializer_plugin_runtime_dir "${CMAKE_BINARY_DIR}/serializers/$") -set(geometry_serializer_plugin_runtime_dir "${CMAKE_BINARY_DIR}/serializers/$") +set(document_serializer_plugin_runtime_dir "$") +set(geometry_serializer_plugin_runtime_dir "$") file(GLOB SERIALIZERS_H_FILES *.h) file(GLOB SERIALIZERS_S_H_FILES schema_dependent/*.h)