From 03b8b9c1bd3bac071b010c1b1d7d8a8acfc4c167 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 15 May 2026 06:55:48 +1000 Subject: [PATCH] Refactor model settings georef view Move model georeferencing state and rendering into a dedicated settings view, and show live IFC coordinate operation and unit data in the dialog. Generated with the assistance of an AI coding tool. --- src/interface/CMakeLists.txt | 2 + .../modules/models/SettingsDialog.cpp | 105 +++----- src/interface/modules/models/SettingsDialog.h | 7 + src/interface/modules/models/SettingsView.cpp | 246 ++++++++++++++++++ src/interface/modules/models/SettingsView.h | 48 ++++ src/interface/modules/models/Types.h | 18 ++ 6 files changed, 351 insertions(+), 75 deletions(-) create mode 100644 src/interface/modules/models/SettingsView.cpp create mode 100644 src/interface/modules/models/SettingsView.h diff --git a/src/interface/CMakeLists.txt b/src/interface/CMakeLists.txt index a573e8fd88..0a6593dcfb 100644 --- a/src/interface/CMakeLists.txt +++ b/src/interface/CMakeLists.txt @@ -53,6 +53,8 @@ set(INTERFACE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/AddModelDialog.h ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/SettingsDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/SettingsDialog.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/SettingsView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/SettingsView.h ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Types.h ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Controller.h diff --git a/src/interface/modules/models/SettingsDialog.cpp b/src/interface/modules/models/SettingsDialog.cpp index 8935143f97..9ed4204ed0 100644 --- a/src/interface/modules/models/SettingsDialog.cpp +++ b/src/interface/modules/models/SettingsDialog.cpp @@ -19,6 +19,7 @@ ********************************************************************************/ #include "SettingsDialog.h" +#include "SettingsView.h" #include "../../SessionState.h" #include "../../../ifcviewer/Federation.h" @@ -45,8 +46,6 @@ #include #include -#include - namespace ifcinterface::modules::models { namespace { @@ -116,16 +115,6 @@ Eigen::Vector3d parseVector3(const QString& text) { return Eigen::Vector3d(x, y, z); } -QString formatAngleDms(double degrees) { - const double absolute = std::fabs(degrees); - const int d = static_cast(absolute); - const double minutes_total = (absolute - static_cast(d)) * 60.0; - const int m = static_cast(minutes_total); - const double s = (minutes_total - static_cast(m)) * 60.0; - const QString sign = degrees < 0.0 ? "-" : ""; - return QString("%1%2° %3' %4\"").arg(sign).arg(d).arg(m, 2, 10, QChar('0')).arg(formatNumber(s)); -} - QLabel* makeReadOnlyValue(QWidget* parent) { auto* label = new QLabel(parent); label->setTextInteractionFlags(Qt::TextSelectableByMouse); @@ -140,6 +129,7 @@ SettingsDialog::SettingsDialog(ifcinterface::SessionState* session_state, QWidge , session_state_(session_state) , federation_(session_state ? session_state->federation() : nullptr) , loader_(session_state ? session_state->loader() : nullptr) + , settings_view_(new SettingsView(this, session_state)) { setObjectName("appDialog"); setWindowTitle("Model Settings"); @@ -225,6 +215,8 @@ void SettingsDialog::setupUi() { georef_layout->setAlignment(Qt::AlignTop); georef_present_value_ = makeReadOnlyValue(georef_body); georef_type_value_ = makeReadOnlyValue(georef_body); + georef_project_unit_value_ = makeReadOnlyValue(georef_body); + georef_map_unit_value_ = makeReadOnlyValue(georef_body); georef_easting_value_ = makeReadOnlyValue(georef_body); georef_northing_value_ = makeReadOnlyValue(georef_body); georef_height_value_ = makeReadOnlyValue(georef_body); @@ -243,6 +235,8 @@ void SettingsDialog::setupUi() { georef_col_1->setVerticalSpacing(8); georef_col_1->addRow("Georeferenced", georef_present_value_); georef_col_1->addRow("Coordinate Operation", georef_type_value_); + georef_col_1->addRow("Project Unit", georef_project_unit_value_); + georef_col_1->addRow("Map Unit", georef_map_unit_value_); georef_col_1->addRow("Easting", georef_easting_value_); georef_col_1->addRow("Northing", georef_northing_value_); georef_col_1->addRow("OrthogonalHeight", georef_height_value_); @@ -422,78 +416,39 @@ void SettingsDialog::populateModelTable() { updateSelectedModelGeoref(); } -void SettingsDialog::updateSelectedModelGeoref() { - auto set_unknown = [this](const QString& georef, const QString& type) { - georef_present_value_->setText(georef); - georef_type_value_->setText(type); - georef_easting_value_->setText("—"); - georef_northing_value_->setText("—"); - georef_height_value_->setText("—"); - georef_x_axis_abscissa_value_->setText("—"); - georef_x_axis_ordinate_value_->setText("—"); - georef_rotation_dd_value_->setText("—"); - georef_rotation_dms_value_->setText("—"); - georef_scale_value_->setText("—"); - georef_factor_x_value_->setText("—"); - georef_factor_y_value_->setText("—"); - georef_factor_z_value_->setText("—"); - }; +void SettingsDialog::renderSelectedModelGeoref(const SelectedModelGeorefState& state) { + georef_present_value_->setText(state.georef_present); + georef_type_value_->setText(state.coordinate_operation_type); + georef_project_unit_value_->setText(state.project_unit); + georef_map_unit_value_->setText(state.map_unit); + georef_easting_value_->setText(state.easting); + georef_northing_value_->setText(state.northing); + georef_height_value_->setText(state.height); + georef_x_axis_abscissa_value_->setText(state.x_axis_abscissa); + georef_x_axis_ordinate_value_->setText(state.x_axis_ordinate); + georef_rotation_dd_value_->setText(state.rotation_dd); + georef_rotation_dms_value_->setText(state.rotation_dms); + georef_scale_value_->setText(state.scale); + georef_factor_x_value_->setText(state.factor_x); + georef_factor_y_value_->setText(state.factor_y); + georef_factor_z_value_->setText(state.factor_z); +} +void SettingsDialog::updateSelectedModelGeoref() { const int row = model_table_->currentRow(); if (row < 0 || row >= static_cast(model_rows_.size())) { - set_unknown("No model selected", "—"); + renderSelectedModelGeoref( + {"No model selected", "—", "—", "—", "—", "—", "—", "—", "—", "—", "—", "—", "—", "—", "—"}); return; } - const QString& fed_id = model_rows_[row].fed_id; - if (!session_state_) { - set_unknown("Unavailable", "No session state"); + if (!settings_view_) { + renderSelectedModelGeoref( + {"Unavailable", "No settings view", "—", "—", "—", "—", "—", "—", "—", "—", "—", "—", "—", "—", "—"}); return; } - const uint32_t mid = session_state_->modelIdForFedId(fed_id); - if (mid == 0 || !loader_) { - set_unknown("Not loaded", "No live model"); - return; - } - - const ModelGeoref* georef = loader_->modelGeoref(mid); - if (!georef) { - set_unknown("Not available yet", "No data source"); - return; - } - - if (!georef->has_coordinate_operation) { - set_unknown("No", "None"); - return; - } - - const Eigen::Matrix4d& m = georef->coordinate_operation_meters; - const Eigen::Vector3d translation = m.block<3, 1>(0, 3); - const Eigen::Vector3d x_axis = m.block<3, 1>(0, 0); - const Eigen::Vector3d y_axis = m.block<3, 1>(0, 1); - const double factor_x = x_axis.norm(); - const double factor_y = y_axis.norm(); - const double factor_z = m.block<3, 1>(0, 2).norm(); - const double scale = (factor_x + factor_y) * 0.5; - const double x_axis_abscissa = factor_x > 0.0 ? x_axis.x() / factor_x : 1.0; - const double x_axis_ordinate = factor_x > 0.0 ? x_axis.y() / factor_x : 0.0; - constexpr double kRadiansToDegrees = 57.29577951308232; - const double rotation_dd = std::atan2(x_axis_ordinate, x_axis_abscissa) * kRadiansToDegrees; - - georef_present_value_->setText("Yes"); - georef_type_value_->setText("IfcMapConversion"); - georef_easting_value_->setText(formatNumber(translation.x())); - georef_northing_value_->setText(formatNumber(translation.y())); - georef_height_value_->setText(formatNumber(translation.z())); - georef_x_axis_abscissa_value_->setText(formatNumber(x_axis_abscissa)); - georef_x_axis_ordinate_value_->setText(formatNumber(x_axis_ordinate)); - georef_rotation_dd_value_->setText(formatNumber(rotation_dd)); - georef_rotation_dms_value_->setText(formatAngleDms(rotation_dd)); - georef_scale_value_->setText(formatNumber(scale)); - georef_factor_x_value_->setText(formatNumber(factor_x)); - georef_factor_y_value_->setText(formatNumber(factor_y)); - georef_factor_z_value_->setText(formatNumber(factor_z)); + settings_view_->refresh(model_rows_[row].fed_id); } void SettingsDialog::onAccepted() { diff --git a/src/interface/modules/models/SettingsDialog.h b/src/interface/modules/models/SettingsDialog.h index 4c3170998d..f03815a0df 100644 --- a/src/interface/modules/models/SettingsDialog.h +++ b/src/interface/modules/models/SettingsDialog.h @@ -22,6 +22,7 @@ #define IFCINTERFACE_MODULES_MODELS_SETTINGSDIALOG_H #include "../../components/Dialog.h" +#include "Types.h" #include #include @@ -41,10 +42,13 @@ class SessionState; namespace ifcinterface::modules::models { +class SettingsView; + class SettingsDialog : public components::TabbedDialog { Q_OBJECT public: explicit SettingsDialog(ifcinterface::SessionState* session_state, QWidget* parent = nullptr); + void renderSelectedModelGeoref(const SelectedModelGeorefState& state); protected: void showEvent(QShowEvent* event) override; @@ -77,6 +81,8 @@ private: QLabel* georef_present_value_ = nullptr; QLabel* georef_type_value_ = nullptr; + QLabel* georef_project_unit_value_ = nullptr; + QLabel* georef_map_unit_value_ = nullptr; QLabel* georef_easting_value_ = nullptr; QLabel* georef_northing_value_ = nullptr; QLabel* georef_height_value_ = nullptr; @@ -91,6 +97,7 @@ private: QTableWidget* model_table_ = nullptr; std::vector model_rows_; + SettingsView* settings_view_ = nullptr; }; } // namespace ifcinterface::modules::models diff --git a/src/interface/modules/models/SettingsView.cpp b/src/interface/modules/models/SettingsView.cpp new file mode 100644 index 0000000000..42dbb5adff --- /dev/null +++ b/src/interface/modules/models/SettingsView.cpp @@ -0,0 +1,246 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include "SettingsView.h" +#include "SettingsDialog.h" + +#include "../../SessionState.h" +#include "../../../ifcviewer/Federation.h" +#include "../../../ifcviewer/Geolocation.h" +#include "../../../ifcviewer/SceneLoader.h" +#include "../../../ifcviewer/Unit.h" + +#include + +namespace ifcinterface::modules::models { + +namespace { + +QString formatNumber(double value) { + return QString::number(value, 'f', 6); +} + +QString formatAngleDms(double degrees) { + const double absolute = std::fabs(degrees); + const int d = static_cast(absolute); + const double minutes_total = (absolute - static_cast(d)) * 60.0; + const int m = static_cast(minutes_total); + const double s = (minutes_total - static_cast(m)) * 60.0; + const QString sign = degrees < 0.0 ? "-" : ""; + return QString("%1%2° %3' %4\"").arg(sign).arg(d).arg(m, 2, 10, QChar('0')).arg(formatNumber(s)); +} + +SelectedModelGeorefState unknownState(const QString& georef, const QString& type) { + return { + georef, + type, + "—", + "—", + "—", + "—", + "—", + "—", + "—", + "—", + "—", + "—", + "—", + "—", + "—", + }; +} + +QString formatCachedUnitScale(double meters_per_unit) { + return QString("Cached scale: 1 unit = %1 m").arg(formatNumber(meters_per_unit)); +} + +std::string enumString(const attribute_value& av) { + if (av.isNull()) return {}; + if (av.type() != ifcopenshell::Argument_ENUMERATION) return {}; + enumeration_reference er = av; + return std::string(er.value() ? er.value() : ""); +} + +QString formatNamedUnit(const express::Base& unit) { + if (!unit) return "—"; + auto entity = unit.as(); + if (unit.declaration().is("IfcSIUnit")) { + const std::string prefix = enumString(entity.get("Prefix")); + const std::string name = enumString(entity.get("Name")); + QString text; + if (!prefix.empty()) { + text += QString::fromStdString(prefix) + " "; + } + text += QString::fromStdString(name); + auto symbol_it = kUnitSymbols.find(name); + if (symbol_it != kUnitSymbols.end()) { + text += " (" + QString::fromStdString(symbol_it->second) + ")"; + } + return text; + } + + auto name_attr = entity.get("Name"); + if (name_attr.isNull()) return "—"; + const std::string name = static_cast(name_attr); + QString text = QString::fromStdString(name); + auto symbol_it = kUnitSymbols.find(name); + if (symbol_it != kUnitSymbols.end()) { + text += " (" + QString::fromStdString(symbol_it->second) + ")"; + } + return text; +} + +std::optional coordinateOperationType(ifcopenshell::file* ifc_file) { + if (!ifc_file) return std::nullopt; + try { + const auto coordops = ifc_file->instances_by_type("IfcCoordinateOperation"); + if (!coordops.empty()) { + return QString::fromStdString(coordops[0].declaration().name()); + } + } catch (...) { + return std::nullopt; + } + + if (ifc_file->schema()->name() == "IFC2X3") { + if (getHelmertTransformationParameters(ifc_file)) { + return QStringLiteral("ePSet_MapConversion"); + } + } + return QStringLiteral("None"); +} + +SelectedModelGeorefState stateFromLiveFile(ifcopenshell::file* ifc_file) { + if (!ifc_file) return unknownState("Not available yet", "No data source"); + + const auto params = getHelmertTransformationParameters(ifc_file); + const auto coordop_type = coordinateOperationType(ifc_file); + const auto project_unit_value = getProjectUnit(ifc_file, "LENGTHUNIT"); + const auto map_unit_value = getMapUnit(ifc_file); + const QString project_unit = project_unit_value ? formatNamedUnit(*project_unit_value) : QString("—"); + const QString map_unit = map_unit_value ? formatNamedUnit(*map_unit_value) : project_unit; + + if (!params) { + auto state = unknownState("No", coordop_type.value_or("Unknown")); + state.project_unit = project_unit; + state.map_unit = map_unit; + return state; + } + + const double rotation_dd = xaxis2angleDeg(params->xaa, params->xao); + return { + "Yes", + coordop_type.value_or("Unknown"), + project_unit, + map_unit, + formatNumber(params->e), + formatNumber(params->n), + formatNumber(params->h), + formatNumber(params->xaa), + formatNumber(params->xao), + formatNumber(rotation_dd), + formatAngleDms(rotation_dd), + formatNumber(params->scale), + formatNumber(params->factor_x), + formatNumber(params->factor_y), + formatNumber(params->factor_z), + }; +} + +SelectedModelGeorefState stateFromCachedGeoref(const ModelGeoref& georef) { + if (!georef.has_coordinate_operation) { + return unknownState("No", "None"); + } + + const Eigen::Matrix4d& m = georef.coordinate_operation_meters; + const Eigen::Vector3d translation = m.block<3, 1>(0, 3); + const Eigen::Vector3d x_axis = m.block<3, 1>(0, 0); + const Eigen::Vector3d y_axis = m.block<3, 1>(0, 1); + const double factor_x = x_axis.norm(); + const double factor_y = y_axis.norm(); + const double factor_z = m.block<3, 1>(0, 2).norm(); + const double scale = (factor_x + factor_y) * 0.5; + const double x_axis_abscissa = factor_x > 0.0 ? x_axis.x() / factor_x : 1.0; + const double x_axis_ordinate = factor_x > 0.0 ? x_axis.y() / factor_x : 0.0; + constexpr double kRadiansToDegrees = 57.29577951308232; + const double rotation_dd = std::atan2(x_axis_ordinate, x_axis_abscissa) * kRadiansToDegrees; + + return { + "Yes", + "Cached coordinate operation", + formatCachedUnitScale(georef.units.project_length_to_meters), + formatCachedUnitScale(georef.units.map_unit_to_meters), + formatNumber(translation.x()), + formatNumber(translation.y()), + formatNumber(translation.z()), + formatNumber(x_axis_abscissa), + formatNumber(x_axis_ordinate), + formatNumber(rotation_dd), + formatAngleDms(rotation_dd), + formatNumber(scale), + formatNumber(factor_x), + formatNumber(factor_y), + formatNumber(factor_z), + }; +} + +} // namespace + +SettingsView::SettingsView(SettingsDialog* widget, + ifcinterface::SessionState* session_state) + : widget_(widget), session_state_(session_state) +{ +} + +void SettingsView::refresh(const QString& fed_id) const { + if (!widget_) { + return; + } + + if (!session_state_) { + widget_->renderSelectedModelGeoref(unknownState("Unavailable", "No session state")); + return; + } + + SceneLoader* loader = session_state_->loader(); + if (!loader) { + widget_->renderSelectedModelGeoref(unknownState("Unavailable", "No loader")); + return; + } + + const uint32_t mid = session_state_->modelIdForFedId(fed_id); + if (mid == 0) { + widget_->renderSelectedModelGeoref(unknownState("Not loaded", "No live model")); + return; + } + + if (auto* ifc_file = loader->ifcFile(mid)) { + widget_->renderSelectedModelGeoref(stateFromLiveFile(ifc_file)); + return; + } + + const ModelGeoref* georef = loader->modelGeoref(mid); + if (!georef) { + widget_->renderSelectedModelGeoref(unknownState("Not available yet", "No data source")); + return; + } + widget_->renderSelectedModelGeoref(stateFromCachedGeoref(*georef)); +} + +} // namespace ifcinterface::modules::models diff --git a/src/interface/modules/models/SettingsView.h b/src/interface/modules/models/SettingsView.h new file mode 100644 index 0000000000..510865c6ff --- /dev/null +++ b/src/interface/modules/models/SettingsView.h @@ -0,0 +1,48 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef IFCINTERFACE_MODULES_MODELS_SETTINGSVIEW_H +#define IFCINTERFACE_MODULES_MODELS_SETTINGSVIEW_H + +#include "Types.h" + +namespace ifcinterface { +class SessionState; +} + +namespace ifcinterface::modules::models { + +class SettingsDialog; + +class SettingsView { +public: + explicit SettingsView(SettingsDialog* widget, + ifcinterface::SessionState* session_state); + + void refresh(const QString& fed_id) const; + +private: + SettingsDialog* widget_ = nullptr; + ifcinterface::SessionState* session_state_ = nullptr; +}; + +} // namespace ifcinterface::modules::models + +#endif diff --git a/src/interface/modules/models/Types.h b/src/interface/modules/models/Types.h index 927995d762..a9b963712b 100644 --- a/src/interface/modules/models/Types.h +++ b/src/interface/modules/models/Types.h @@ -39,6 +39,24 @@ struct TreeNode { QList children; }; +struct SelectedModelGeorefState { + QString georef_present; + QString coordinate_operation_type; + QString project_unit; + QString map_unit; + QString easting; + QString northing; + QString height; + QString x_axis_abscissa; + QString x_axis_ordinate; + QString rotation_dd; + QString rotation_dms; + QString scale; + QString factor_x; + QString factor_y; + QString factor_z; +}; + } // namespace ifcinterface::modules::models #endif