diff --git a/src/ifcviewer-full/SettingsWindow.cpp b/src/ifcviewer-full/SettingsWindow.cpp index b5b22279fd..8fd867b3ae 100644 --- a/src/ifcviewer-full/SettingsWindow.cpp +++ b/src/ifcviewer-full/SettingsWindow.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -66,6 +67,25 @@ void SettingsWindow::setupUi() { "time; dropping them keeps load times sane."); form->addRow("Void Limit", void_limit_spin_); + deflection_tolerance_spin_ = new QDoubleSpinBox(this); + deflection_tolerance_spin_->setRange(0.000001, 1000.0); + deflection_tolerance_spin_->setDecimals(6); + deflection_tolerance_spin_->setSingleStep(0.001); + deflection_tolerance_spin_->setToolTip( + "Linear chord error between curved geometry and its triangulation, " + "in model length units. Smaller = smoother curves but more " + "triangles and slower load."); + form->addRow("Deflection Tolerance", deflection_tolerance_spin_); + + angular_tolerance_spin_ = new QDoubleSpinBox(this); + angular_tolerance_spin_->setRange(0.000001, 3.141592); + angular_tolerance_spin_->setDecimals(6); + angular_tolerance_spin_->setSingleStep(0.05); + angular_tolerance_spin_->setToolTip( + "Maximum angle (radians) between adjacent facet normals on a " + "curved surface. Smaller = smoother shading but more triangles."); + form->addRow("Angular Tolerance", angular_tolerance_spin_); + auto* button_box = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); @@ -90,6 +110,8 @@ void SettingsWindow::syncFromSettings() { backface_culling_check_->setChecked(AppSettings::instance().backfaceCulling()); load_data_source_check_->setChecked(AppSettings::instance().loadDataSource()); void_limit_spin_->setValue(AppSettings::instance().voidLimit()); + deflection_tolerance_spin_->setValue(AppSettings::instance().deflectionTolerance()); + angular_tolerance_spin_->setValue(AppSettings::instance().angularTolerance()); } void SettingsWindow::onAccepted() { @@ -98,5 +120,7 @@ void SettingsWindow::onAccepted() { AppSettings::instance().setBackfaceCulling(backface_culling_check_->isChecked()); AppSettings::instance().setLoadDataSource(load_data_source_check_->isChecked()); AppSettings::instance().setVoidLimit(void_limit_spin_->value()); + AppSettings::instance().setDeflectionTolerance(deflection_tolerance_spin_->value()); + AppSettings::instance().setAngularTolerance(angular_tolerance_spin_->value()); accept(); } diff --git a/src/ifcviewer-full/SettingsWindow.h b/src/ifcviewer-full/SettingsWindow.h index 70c4442fc2..e9a996e94d 100644 --- a/src/ifcviewer-full/SettingsWindow.h +++ b/src/ifcviewer-full/SettingsWindow.h @@ -23,6 +23,7 @@ #include class QCheckBox; +class QDoubleSpinBox; class QLineEdit; class QShowEvent; class QSpinBox; @@ -47,6 +48,8 @@ private: QCheckBox* backface_culling_check_ = nullptr; QCheckBox* load_data_source_check_ = nullptr; QSpinBox* void_limit_spin_ = nullptr; + QDoubleSpinBox* deflection_tolerance_spin_ = nullptr; + QDoubleSpinBox* angular_tolerance_spin_ = nullptr; }; #endif diff --git a/src/ifcviewer/AppSettings.cpp b/src/ifcviewer/AppSettings.cpp index 58dbfe33be..3f7dfea9e0 100644 --- a/src/ifcviewer/AppSettings.cpp +++ b/src/ifcviewer/AppSettings.cpp @@ -29,6 +29,10 @@ constexpr const char* kBackfaceCullingKey = "viewport/backface_culling"; constexpr const char* kLoadDataSourceKey = "loading/load_data_source"; constexpr const char* kVoidLimitKey = "loading/void_limit"; constexpr int kVoidLimitDefault = 30; +constexpr const char* kDeflectionToleranceKey = "loading/deflection_tolerance"; +constexpr double kDeflectionToleranceDefault = 0.001; +constexpr const char* kAngularToleranceKey = "loading/angular_tolerance"; +constexpr double kAngularToleranceDefault = 0.5; } AppSettings& AppSettings::instance() { @@ -96,6 +100,30 @@ void AppSettings::setVoidLimit(int value) { emit voidLimitChanged(value); } +double AppSettings::deflectionTolerance() const { + return deflection_tolerance_; +} + +void AppSettings::setDeflectionTolerance(double value) { + if (value <= 0.0) value = kDeflectionToleranceDefault; + if (deflection_tolerance_ == value) return; + deflection_tolerance_ = value; + persist(); + emit deflectionToleranceChanged(value); +} + +double AppSettings::angularTolerance() const { + return angular_tolerance_; +} + +void AppSettings::setAngularTolerance(double value) { + if (value <= 0.0) value = kAngularToleranceDefault; + if (angular_tolerance_ == value) return; + angular_tolerance_ = value; + persist(); + emit angularToleranceChanged(value); +} + void AppSettings::load() { QSettings settings; geometry_library_ = settings.value(kGeometryLibraryKey, kGeometryLibraryDefault).toString(); @@ -104,6 +132,10 @@ void AppSettings::load() { load_data_source_ = settings.value(kLoadDataSourceKey, true).toBool(); void_limit_ = settings.value(kVoidLimitKey, kVoidLimitDefault).toInt(); if (void_limit_ < 0) void_limit_ = 0; + deflection_tolerance_ = settings.value(kDeflectionToleranceKey, kDeflectionToleranceDefault).toDouble(); + if (deflection_tolerance_ <= 0.0) deflection_tolerance_ = kDeflectionToleranceDefault; + angular_tolerance_ = settings.value(kAngularToleranceKey, kAngularToleranceDefault).toDouble(); + if (angular_tolerance_ <= 0.0) angular_tolerance_ = kAngularToleranceDefault; } void AppSettings::persist() { @@ -113,4 +145,6 @@ void AppSettings::persist() { settings.setValue(kBackfaceCullingKey, backface_culling_); settings.setValue(kLoadDataSourceKey, load_data_source_); 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 5e796280aa..d435158f70 100644 --- a/src/ifcviewer/AppSettings.h +++ b/src/ifcviewer/AppSettings.h @@ -53,12 +53,25 @@ public: int voidLimit() const; void setVoidLimit(int value); + // Mesher tolerances passed straight to the IfcOpenShell iterator. + // Linear deflection bounds the chord error between a curve and its + // triangulation, in model length units; angular deflection bounds the + // angle (radians) between adjacent facet normals on a curved surface. + // Smaller values mean smoother geometry at the cost of more triangles + // and slower iteration. + double deflectionTolerance() const; + void setDeflectionTolerance(double value); + double angularTolerance() const; + void setAngularTolerance(double value); + signals: void geometryLibraryChanged(const QString& value); void showStatsChanged(bool value); void backfaceCullingChanged(bool value); void loadDataSourceChanged(bool value); void voidLimitChanged(int value); + void deflectionToleranceChanged(double value); + void angularToleranceChanged(double value); private: AppSettings(); @@ -70,6 +83,8 @@ private: bool backface_culling_ = true; bool load_data_source_ = true; int void_limit_ = 30; + double deflection_tolerance_ = 0.001; + double angular_tolerance_ = 0.5; }; #endif // APPSETTINGS_H diff --git a/src/ifcviewer/GeometryStreamer.cpp b/src/ifcviewer/GeometryStreamer.cpp index 8de928164c..786bce22a1 100644 --- a/src/ifcviewer/GeometryStreamer.cpp +++ b/src/ifcviewer/GeometryStreamer.cpp @@ -284,12 +284,17 @@ void GeometryStreamer::run(const std::string& path, int num_threads) { // applied on the GPU per instance. settings.set("use-world-coords", false); settings.set("weld-vertices", false); - settings.set("apply-default-materials", true); + settings.set("apply-default-materials", false); // Off by default in IfcOpenShell — makes face winding consistent within // each shell, which we need for GL_CULL_FACE and for per-vertex normals // to shade a solid without dark inside-out patches. Costs some iterator // time, but results are cached in the sidecar so it's a one-shot hit. settings.set("reorient-shells", true); + settings.set("layerset-first", true); + settings.set("mesher-linear-deflection", AppSettings::instance().deflectionTolerance()); + settings.set("mesher-angular-deflection", AppSettings::instance().angularTolerance()); + // Wire intersection checks is prohibitively slow on advanced breps. See bug #5999. + settings.set("no-wire-intersection-check", true); // @todo parallel mapping on RocksDB-backed files still races somewhere // outside the instance cache, producing inconsistent shape counts. Force