diff --git a/src/ifcviewer-full/MainWindow.cpp b/src/ifcviewer-full/MainWindow.cpp index 3105af7cf5..98bcc9224c 100644 --- a/src/ifcviewer-full/MainWindow.cpp +++ b/src/ifcviewer-full/MainWindow.cpp @@ -89,6 +89,16 @@ MainWindow::MainWindow(QWidget* parent) if (!show) stats_label_->clear(); }); + // Toggling the CoordinateOperation setting walks every loaded model + // and pushes either its georef matrix or identity to the viewport. + connect(&AppSettings::instance(), + &AppSettings::applyCoordinateOperationChanged, + this, [this](bool /*enabled*/) { + for (const auto& kv : fed_id_to_model_id_) { + applyCoordinateOperationToViewport(kv.second); + } + }); + updateWindowTitle(); resize(1400, 900); } @@ -457,6 +467,13 @@ void MainWindow::onSidecarElementsReady(uint32_t mid, } void MainWindow::onDataSourceReady(uint32_t mid) { + // The IFC file is now available — push the model's CoordinateOperation + // (or identity) to the viewport. Sidecar-hit models get here for the + // first time; stream-loaded models also pass through here when a + // separate data source opens, but applyCoordinateOperationToViewport + // is idempotent so double-applying is harmless. + applyCoordinateOperationToViewport(mid); + // Re-populate if the current selection belongs to this model, since // populateProperties() now has an ifcFile() to query. auto items = element_tree_->selectedItems(); @@ -468,6 +485,18 @@ void MainWindow::onDataSourceReady(uint32_t mid) { } } +void MainWindow::applyCoordinateOperationToViewport(uint32_t mid) { + Eigen::Matrix4d M = Eigen::Matrix4d::Identity(); + if (AppSettings::instance().applyCoordinateOperation()) { + if (const ModelGeoref* gr = loader_->modelGeoref(mid)) { + if (gr->has_coordinate_operation) { + M = gr->coordinate_operation_meters; + } + } + } + viewport_->setModelCoordinateOperation(mid, M); +} + void MainWindow::onLoadedFromSidecar(uint32_t /*mid*/, qint64 elapsed_ms) { progress_bar_->setVisible(false); status_label_->setText(QString("%1 elements across %2 model(s) — loaded from cache in %3") @@ -572,6 +601,10 @@ void MainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) { .arg(loader_->modelCount()) .arg(formatElapsed(elapsed_ms))); + // Stream path: the IFC is owned by the streamer, so georef is + // computable now. (Sidecar-hit models defer to onDataSourceReady.) + applyCoordinateOperationToViewport(mid); + writeSidecarForModel(mid); } diff --git a/src/ifcviewer-full/MainWindow.h b/src/ifcviewer-full/MainWindow.h index 13dfecf061..03dac8f4f2 100644 --- a/src/ifcviewer-full/MainWindow.h +++ b/src/ifcviewer-full/MainWindow.h @@ -96,6 +96,13 @@ private: void writeSidecarForModel(uint32_t mid); void removeModelUi(uint32_t mid); void applyPendingBenchmark(); + + // Push a model's CoordinateOperation matrix to the viewport (or + // identity, when the AppSettings toggle is off or the model has no + // map conversion). No-op if the model isn't yet known to the loader + // or its IFC file isn't available (sidecar-hit before data-source + // load); the call retries on onDataSourceReady. + void applyCoordinateOperationToViewport(uint32_t mid); QString formatElapsed(qint64 ms) const; ViewportWindow* viewport_ = nullptr; diff --git a/src/ifcviewer-full/SettingsWindow.cpp b/src/ifcviewer-full/SettingsWindow.cpp index 8fd867b3ae..9a7032fd86 100644 --- a/src/ifcviewer-full/SettingsWindow.cpp +++ b/src/ifcviewer-full/SettingsWindow.cpp @@ -59,6 +59,14 @@ void SettingsWindow::setupUi() { "and, on sidecar hits, avoids a second file read."); form->addRow("Load Property Data Source", load_data_source_check_); + apply_coordinate_operation_check_ = new QCheckBox(this); + apply_coordinate_operation_check_->setToolTip( + "Apply each model's IfcCoordinateOperation (e.g. IfcMapConversion) " + "after load so it lands in georeferenced map coordinates. " + "Disable to keep models in their local engineering frame."); + form->addRow("Apply Coordinate Operation", + apply_coordinate_operation_check_); + void_limit_spin_ = new QSpinBox(this); void_limit_spin_->setRange(0, 100000); void_limit_spin_->setToolTip( @@ -109,6 +117,8 @@ void SettingsWindow::syncFromSettings() { show_stats_check_->setChecked(AppSettings::instance().showStats()); backface_culling_check_->setChecked(AppSettings::instance().backfaceCulling()); load_data_source_check_->setChecked(AppSettings::instance().loadDataSource()); + apply_coordinate_operation_check_->setChecked( + AppSettings::instance().applyCoordinateOperation()); void_limit_spin_->setValue(AppSettings::instance().voidLimit()); deflection_tolerance_spin_->setValue(AppSettings::instance().deflectionTolerance()); angular_tolerance_spin_->setValue(AppSettings::instance().angularTolerance()); @@ -119,6 +129,8 @@ void SettingsWindow::onAccepted() { AppSettings::instance().setShowStats(show_stats_check_->isChecked()); AppSettings::instance().setBackfaceCulling(backface_culling_check_->isChecked()); AppSettings::instance().setLoadDataSource(load_data_source_check_->isChecked()); + AppSettings::instance().setApplyCoordinateOperation( + apply_coordinate_operation_check_->isChecked()); AppSettings::instance().setVoidLimit(void_limit_spin_->value()); AppSettings::instance().setDeflectionTolerance(deflection_tolerance_spin_->value()); AppSettings::instance().setAngularTolerance(angular_tolerance_spin_->value()); diff --git a/src/ifcviewer-full/SettingsWindow.h b/src/ifcviewer-full/SettingsWindow.h index e9a996e94d..070e90867e 100644 --- a/src/ifcviewer-full/SettingsWindow.h +++ b/src/ifcviewer-full/SettingsWindow.h @@ -47,6 +47,7 @@ private: QCheckBox* show_stats_check_ = nullptr; QCheckBox* backface_culling_check_ = nullptr; QCheckBox* load_data_source_check_ = nullptr; + QCheckBox* apply_coordinate_operation_check_ = nullptr; QSpinBox* void_limit_spin_ = nullptr; QDoubleSpinBox* deflection_tolerance_spin_ = nullptr; QDoubleSpinBox* angular_tolerance_spin_ = nullptr; diff --git a/src/ifcviewer/AppSettings.cpp b/src/ifcviewer/AppSettings.cpp index 3f7dfea9e0..d6383ee4c5 100644 --- a/src/ifcviewer/AppSettings.cpp +++ b/src/ifcviewer/AppSettings.cpp @@ -27,6 +27,7 @@ constexpr const char* kGeometryLibraryDefault = "hybrid-cgal-simple-opencascade" constexpr const char* kShowStatsKey = "viewport/show_stats"; constexpr const char* kBackfaceCullingKey = "viewport/backface_culling"; constexpr const char* kLoadDataSourceKey = "loading/load_data_source"; +constexpr const char* kApplyCoordinateOperationKey = "loading/apply_coordinate_operation"; constexpr const char* kVoidLimitKey = "loading/void_limit"; constexpr int kVoidLimitDefault = 30; constexpr const char* kDeflectionToleranceKey = "loading/deflection_tolerance"; @@ -88,6 +89,17 @@ void AppSettings::setLoadDataSource(bool value) { emit loadDataSourceChanged(value); } +bool AppSettings::applyCoordinateOperation() const { + return apply_coordinate_operation_; +} + +void AppSettings::setApplyCoordinateOperation(bool value) { + if (apply_coordinate_operation_ == value) return; + apply_coordinate_operation_ = value; + persist(); + emit applyCoordinateOperationChanged(value); +} + int AppSettings::voidLimit() const { return void_limit_; } @@ -130,6 +142,8 @@ void AppSettings::load() { show_stats_ = settings.value(kShowStatsKey, false).toBool(); backface_culling_ = settings.value(kBackfaceCullingKey, true).toBool(); load_data_source_ = settings.value(kLoadDataSourceKey, true).toBool(); + apply_coordinate_operation_ = + settings.value(kApplyCoordinateOperationKey, false).toBool(); void_limit_ = settings.value(kVoidLimitKey, kVoidLimitDefault).toInt(); if (void_limit_ < 0) void_limit_ = 0; deflection_tolerance_ = settings.value(kDeflectionToleranceKey, kDeflectionToleranceDefault).toDouble(); @@ -144,6 +158,7 @@ void AppSettings::persist() { settings.setValue(kShowStatsKey, show_stats_); settings.setValue(kBackfaceCullingKey, backface_culling_); settings.setValue(kLoadDataSourceKey, load_data_source_); + settings.setValue(kApplyCoordinateOperationKey, apply_coordinate_operation_); settings.setValue(kVoidLimitKey, void_limit_); settings.setValue(kDeflectionToleranceKey, deflection_tolerance_); settings.setValue(kAngularToleranceKey, angular_tolerance_); diff --git a/src/ifcviewer/AppSettings.h b/src/ifcviewer/AppSettings.h index d435158f70..ac96ab3392 100644 --- a/src/ifcviewer/AppSettings.h +++ b/src/ifcviewer/AppSettings.h @@ -47,6 +47,14 @@ public: bool loadDataSource() const; void setLoadDataSource(bool value); + // When true, each loaded model's IfcCoordinateOperation (e.g. + // IfcMapConversion) is applied to the per-instance transform after + // load, lifting the model into map (georeferenced) coordinates. + // When false, models render in their local engineering frame — + // useful for previewing geometry without translating to e.g. UTM. + bool applyCoordinateOperation() const; + void setApplyCoordinateOperation(bool value); + // Skip elements with more than this many voids (HasOpenings inverse). // Boolean subtraction of many openings is the dominant cost in some // pathological exports; dropping those elements keeps load times sane. @@ -69,6 +77,7 @@ signals: void showStatsChanged(bool value); void backfaceCullingChanged(bool value); void loadDataSourceChanged(bool value); + void applyCoordinateOperationChanged(bool value); void voidLimitChanged(int value); void deflectionToleranceChanged(double value); void angularToleranceChanged(double value); @@ -82,6 +91,7 @@ private: bool show_stats_ = false; bool backface_culling_ = true; bool load_data_source_ = true; + bool apply_coordinate_operation_ = false; int void_limit_ = 30; double deflection_tolerance_ = 0.001; double angular_tolerance_ = 0.5;