diff --git a/src/interface/CMakeLists.txt b/src/interface/CMakeLists.txt index d18066b5b6..5eee8a0824 100644 --- a/src/interface/CMakeLists.txt +++ b/src/interface/CMakeLists.txt @@ -66,6 +66,10 @@ set(INTERFACE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/Widget.h ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/View.cpp ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/View.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/viewport/Widget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/viewport/Widget.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/viewport/Controller.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/viewport/Controller.h ${CMAKE_CURRENT_SOURCE_DIR}/interface_resources.qrc ) diff --git a/src/interface/MainWindow.cpp b/src/interface/MainWindow.cpp index 901fcbf806..96f311401e 100644 --- a/src/interface/MainWindow.cpp +++ b/src/interface/MainWindow.cpp @@ -38,11 +38,12 @@ #include "panels/settings/Dialog.h" #include "panels/spatial_hierarchy/View.h" #include "panels/spatial_hierarchy/Widget.h" +#include "panels/viewport/Controller.h" +#include "panels/viewport/Widget.h" #include #include #include -#include #include #include #include @@ -163,33 +164,29 @@ QWidget* MainWindow::buildNavigateRibbonPage() { row->setSpacing(0); auto* set_home = makeRibbonAction("Set Home", ":/icons/home.svg"); - connect(set_home, &QToolButton::clicked, this, [this]() { - setStatusMessage("Camera", "Set home view coming soon"); - }); + connect(set_home, &QToolButton::clicked, this, &MainWindow::onSetHomeView); auto* go_home = makeRibbonAction("Go Home", ":/icons/home-alt.svg"); - connect(go_home, &QToolButton::clicked, this, [this]() { - setStatusMessage("Camera", "Go to home view coming soon"); - }); + connect(go_home, &QToolButton::clicked, this, &MainWindow::onGoHomeView); auto* view_all = makeRibbonAction("View All", ":/icons/cube-scan.svg"); connect(view_all, &QToolButton::clicked, this, [this]() { - if (viewport_) viewport_->viewAll(); + if (viewport_widget_) viewport_widget_->viewport()->viewAll(); }); auto* view_selected = makeRibbonAction("View Selected", ":/icons/cube-scan-solid.svg"); connect(view_selected, &QToolButton::clicked, this, [this]() { - if (viewport_) viewport_->focusOnSelectedObject(); + if (viewport_widget_) viewport_widget_->viewport()->focusOnSelectedObject(); }); auto* plan_view = makeRibbonAction("Plan", ":/icons/planimetry.svg"); connect(plan_view, &QToolButton::clicked, this, [this]() { - if (viewport_) viewport_->setStandardView(90.0f, 90.0f); + if (viewport_widget_) viewport_widget_->viewport()->setStandardView(90.0f, 90.0f); }); auto* front_view = makeRibbonAction("Front", ":/icons/city.svg"); connect(front_view, &QToolButton::clicked, this, [this]() { - if (viewport_) viewport_->setStandardView(0.0f, 0.0f); + if (viewport_widget_) viewport_widget_->viewport()->setStandardView(0.0f, 0.0f); }); auto* side_view = makeRibbonAction("Side", ":/icons/building.svg"); connect(side_view, &QToolButton::clicked, this, [this]() { - if (viewport_) viewport_->setStandardView(90.0f, 0.0f); + if (viewport_widget_) viewport_widget_->viewport()->setStandardView(90.0f, 0.0f); }); auto* align_object = makeRibbonAction("Align Object", ":/icons/cellar.svg"); connect(align_object, &QToolButton::clicked, this, [this]() { @@ -197,9 +194,10 @@ QWidget* MainWindow::buildNavigateRibbonPage() { }); auto* projection_button = makeRibbonAction("Perspective", ":/icons/perspective-view.svg"); connect(projection_button, &QToolButton::clicked, this, [this, projection_button]() { - if (!viewport_) return; - viewport_->toggleProjection(); - projection_button->setText(viewport_->projectionOrtho() ? "Ortho" : "Perspective"); + if (!viewport_widget_) return; + viewport_widget_->viewport()->toggleProjection(); + projection_button->setText( + viewport_widget_->viewport()->projectionOrtho() ? "Ortho" : "Perspective"); }); auto* orbit_mode = makeRibbonAction("Orbit", ":/icons/rotate-camera-right.svg"); @@ -326,25 +324,8 @@ void MainWindow::setupRibbon() { } void MainWindow::setupViewport() { - viewport_ = new ViewportWindow(); - viewport_container_ = QWidget::createWindowContainer(viewport_, this); - viewport_container_->setMinimumSize(400, 300); - viewport_container_->setFocusPolicy(Qt::StrongFocus); - - auto* shell = new QFrame(this); - shell->setObjectName("viewportShell"); - auto* root = new QVBoxLayout(shell); - root->setContentsMargins(10, 10, 10, 10); - root->setSpacing(0); - - auto* frame = new QFrame(shell); - frame->setObjectName("viewportFrame"); - auto* frame_layout = new QVBoxLayout(frame); - frame_layout->setContentsMargins(0, 0, 0, 0); - frame_layout->addWidget(viewport_container_); - - root->addWidget(frame); - setCentralWidget(shell); + viewport_widget_ = new panels::viewport::ViewportWidget(this); + setCentralWidget(viewport_widget_); } void MainWindow::setupPanels() { @@ -355,7 +336,7 @@ void MainWindow::setupPanels() { models_view_ = new panels::models::ModelsPanelView(models_panel_, this); spatial_view_ = new panels::spatial_hierarchy::SpatialHierarchyPanelView(spatial_panel_, this); properties_view_ = new panels::properties::PropertiesPanelView( - properties_panel_, viewport_, element_registry_, this); + properties_panel_, viewport_widget_->viewport(), element_registry_, this); connect(models_view_, &panels::models::ModelsPanelView::statusMessageRequested, this, &MainWindow::setStatusMessage); @@ -425,8 +406,11 @@ void MainWindow::setupStatus() { } void MainWindow::setupLoader() { - loader_ = new SceneLoader(viewport_, this); + loader_ = new SceneLoader(viewport_widget_->viewport(), this); element_registry_->bindLoader(loader_); + viewport_controller_ = new panels::viewport::ViewportController( + federation_, loader_, viewport_widget_->viewport(), + &fed_id_to_model_id_, &model_id_to_fed_id_, this); connect(loader_, &SceneLoader::loadStarted, this, &MainWindow::onLoadStarted); connect(loader_, &SceneLoader::loadedFromSidecar, this, &MainWindow::onLoadedFromSidecar); connect(loader_, &SceneLoader::loadedFromStream, this, &MainWindow::onLoadedFromStream); @@ -434,7 +418,7 @@ void MainWindow::setupLoader() { connect(loader_, &SceneLoader::loadError, this, &MainWindow::onLoadError); connect(loader_, &SceneLoader::allLoadsFinished, this, &MainWindow::onAllLoadsFinished); - connect(viewport_, &ViewportWindow::frameStatsUpdated, this, + connect(viewport_widget_->viewport(), &ViewportWindow::frameStatsUpdated, this, [this](const ViewportWindow::FrameStats& s) { if (!status_perf_label_->isVisible()) return; status_perf_label_->setText( @@ -447,6 +431,7 @@ void MainWindow::setupLoader() { .arg(s.total_triangles) .arg(s.gl_draw_calls)); }); + } void MainWindow::addFiles(const QStringList& paths) { @@ -518,12 +503,12 @@ void MainWindow::onAddFiles() { } void MainWindow::clearScene() { - if (viewport_) viewport_->setSelectedObjectId(0); + if (viewport_widget_) viewport_widget_->viewport()->setSelectedObjectId(0); if (properties_view_) properties_view_->clearSelection(); const auto model_ids = model_id_to_fed_id_.keys(); for (uint32_t mid : model_ids) { - viewport_->removeModel(mid); + viewport_widget_->viewport()->removeModel(mid); loader_->removeModel(mid); } @@ -595,10 +580,11 @@ bool MainWindow::openProject(const QString& path) { } federation_->markClean(); + viewport_controller_->applyFederatedFalseOrigin(); if (federation_->hasHomeView()) { const auto& hv = federation_->homeView(); - viewport_->setCamera(hv.target.x(), hv.target.y(), hv.target.z(), - hv.distance, hv.yaw, hv.pitch); + viewport_widget_->viewport()->setCamera( + hv.target.x(), hv.target.y(), hv.target.z(), hv.distance, hv.yaw, hv.pitch); } updateWindowTitle(); setStatusMessage("Project", QFileInfo(path).fileName()); @@ -665,6 +651,7 @@ void MainWindow::onNewProject() { if (!confirmDiscardIfDirty()) return; clearScene(); federation_->clear(); + viewport_controller_->applyFederatedFalseOrigin(); updateWindowTitle(); setStatusMessage("Project", "Untitled"); } @@ -688,6 +675,31 @@ void MainWindow::onSaveProjectAs() { saveProjectAs(); } +void MainWindow::onSetHomeView() { + auto camera = viewport_widget_->viewport()->cameraState(); + Federation::HomeView home_view; + home_view.target = camera.target; + home_view.distance = camera.distance; + home_view.yaw = camera.yaw; + home_view.pitch = camera.pitch; + federation_->setHomeView(home_view); + updateWindowTitle(); + setStatusMessage("Camera", "Home view updated"); +} + +void MainWindow::onGoHomeView() { + if (!federation_->hasHomeView()) { + setStatusMessage("Camera", "No home view set for this project"); + return; + } + + const auto& home_view = federation_->homeView(); + viewport_widget_->viewport()->setCamera( + home_view.target.x(), home_view.target.y(), home_view.target.z(), + home_view.distance, home_view.yaw, home_view.pitch); + setStatusMessage("Camera", "Home view restored"); +} + void MainWindow::onLoadStarted(uint32_t /*mid*/, QString display_name) { status_mode_label_->setText("Loading"); status_selection_label_->setText(display_name); diff --git a/src/interface/MainWindow.h b/src/interface/MainWindow.h index f42f0bb854..18a7b93a25 100644 --- a/src/interface/MainWindow.h +++ b/src/interface/MainWindow.h @@ -30,7 +30,6 @@ class QDockWidget; class QStackedWidget; class QToolButton; class Federation; -class ViewportWindow; class SceneLoader; namespace ifcinterface { class ElementRegistry; } namespace ifcinterface::components { class TabBar; } @@ -40,6 +39,8 @@ namespace ifcinterface::panels::spatial_hierarchy { class SpatialHierarchyPanelW namespace ifcinterface::panels::spatial_hierarchy { class SpatialHierarchyPanelView; } namespace ifcinterface::panels::properties { class PropertiesPanelWidget; } namespace ifcinterface::panels::properties { class PropertiesPanelView; } +namespace ifcinterface::panels::viewport { class ViewportController; } +namespace ifcinterface::panels::viewport { class ViewportWidget; } namespace ifcinterface::shell { @@ -81,6 +82,8 @@ private slots: void onLoadCancelled(uint32_t mid); void onLoadError(uint32_t mid, QString message); void onAllLoadsFinished(); + void onSetHomeView(); + void onGoHomeView(); void onNewProject(); void onOpenProject(); void onSaveProject(); @@ -93,10 +96,10 @@ private: QLabel* status_perf_label_ = nullptr; ifcinterface::components::TabBar* ribbon_tabs_ = nullptr; QStackedWidget* ribbon_pages_ = nullptr; - ViewportWindow* viewport_ = nullptr; + ifcinterface::panels::viewport::ViewportWidget* viewport_widget_ = nullptr; + ifcinterface::panels::viewport::ViewportController* viewport_controller_ = nullptr; SceneLoader* loader_ = nullptr; ifcinterface::ElementRegistry* element_registry_ = nullptr; - QWidget* viewport_container_ = nullptr; ifcinterface::panels::models::ModelsPanelWidget* models_panel_ = nullptr; ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelWidget* spatial_panel_ = nullptr; QDockWidget* layers_panel_ = nullptr; diff --git a/src/interface/panels/viewport/Controller.cpp b/src/interface/panels/viewport/Controller.cpp new file mode 100644 index 0000000000..14fc3dfea7 --- /dev/null +++ b/src/interface/panels/viewport/Controller.cpp @@ -0,0 +1,138 @@ +// 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 "Controller.h" + +#include "../../../ifcviewer/AppSettings.h" +#include "../../../ifcviewer/Federation.h" +#include "../../../ifcviewer/SceneLoader.h" +#include "../../../ifcviewer/ViewportWindow.h" + +#include + +namespace ifcinterface::panels::viewport { + +ViewportController::ViewportController(Federation* federation, + SceneLoader* loader, + ViewportWindow* viewport, + const QHash* fed_id_to_model_id, + const QHash* model_id_to_fed_id, + QObject* parent) + : QObject(parent) + , federation_(federation) + , loader_(loader) + , viewport_(viewport) + , fed_id_to_model_id_(fed_id_to_model_id) + , model_id_to_fed_id_(model_id_to_fed_id) +{ + connect(federation_, &Federation::federatedFalseOriginChanged, + this, &ViewportController::applyFederatedFalseOrigin); + connect(federation_, &Federation::configChanged, this, [this]() { + applyFederatedFalseOrigin(); + for (auto it = model_id_to_fed_id_->cbegin(); it != model_id_to_fed_id_->cend(); ++it) { + applyModelTransformation(it.key()); + } + }); + connect(federation_, &Federation::modelTransformationChanged, + this, [this](const QString& fed_id) { + auto it = fed_id_to_model_id_->find(fed_id); + if (it != fed_id_to_model_id_->end()) { + applyModelTransformation(it.value()); + } + }); + connect(loader_, &SceneLoader::loadedFromSidecar, this, + [this](uint32_t mid, qint64 /*elapsed_ms*/) { + applyCoordinateOperation(mid); + maybeGuessFederatedFalseOrigin(mid); + }); + connect(loader_, &SceneLoader::dataSourceReady, this, + [this](uint32_t mid) { + applyCoordinateOperation(mid); + }); + connect(loader_, &SceneLoader::loadedFromStream, this, + [this](uint32_t mid, qint64 /*elapsed_ms*/) { + applyCoordinateOperation(mid); + maybeGuessFederatedFalseOrigin(mid); + }); + connect(&AppSettings::instance(), + &AppSettings::applyCoordinateOperationChanged, + this, [this](bool /*enabled*/) { + for (auto it = model_id_to_fed_id_->cbegin(); it != model_id_to_fed_id_->cend(); ++it) { + applyCoordinateOperation(it.key()); + } + }); +} + +void ViewportController::applyCoordinateOperation(uint32_t mid) { + Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity(); + if (AppSettings::instance().applyCoordinateOperation()) { + if (const ModelGeoref* georef = loader_->modelGeoref(mid)) { + if (georef->has_coordinate_operation) { + matrix = georef->coordinate_operation_meters; + } + } + } + viewport_->setModelCoordinateOperation(mid, matrix); + applyModelTransformation(mid); +} + +void ViewportController::applyModelTransformation(uint32_t mid) { + Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity(); + auto fed_it = model_id_to_fed_id_->find(mid); + if (fed_it != model_id_to_fed_id_->end()) { + if (const Federation::Model* model = federation_->findById(fed_it.value())) { + ModelUnits units; + Eigen::Matrix4d coordinate_operation = Eigen::Matrix4d::Identity(); + if (const ModelGeoref* georef = loader_->modelGeoref(mid)) { + units = georef->units; + if (AppSettings::instance().applyCoordinateOperation() && + georef->has_coordinate_operation) { + coordinate_operation = georef->coordinate_operation_meters; + } + } + matrix = composeModelTransformation( + model->model_transformation, federation_->config(), units, coordinate_operation); + } + } + viewport_->setModelTransformation(mid, matrix); +} + +void ViewportController::applyFederatedFalseOrigin() { + viewport_->setFederatedFalseOrigin( + composeFederatedFalseOrigin(federation_->federatedFalseOrigin(), federation_->config())); +} + +void ViewportController::maybeGuessFederatedFalseOrigin(uint32_t mid) { + if (!federation_->filePath().isEmpty()) return; + + const FederatedFalseOrigin& current = federation_->federatedFalseOrigin(); + const FederatedFalseOrigin defaults; + if (current.xyz != defaults.xyz || current.rz_deg != defaults.rz_deg) return; + + const Eigen::Matrix4d* placement = loader_->firstPlacement(mid); + const ModelGeoref* georef = loader_->modelGeoref(mid); + if (placement == nullptr || georef == nullptr) return; + + federation_->setFederatedFalseOrigin(guessFederatedFalseOrigin( + *placement, *georef, federation_->config(), + AppSettings::instance().applyCoordinateOperation())); +} + +} // namespace ifcinterface::panels::viewport diff --git a/src/interface/panels/viewport/Controller.h b/src/interface/panels/viewport/Controller.h new file mode 100644 index 0000000000..25506cc9dd --- /dev/null +++ b/src/interface/panels/viewport/Controller.h @@ -0,0 +1,60 @@ +// 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_PANELS_VIEWPORT_CONTROLLER_H +#define IFCINTERFACE_PANELS_VIEWPORT_CONTROLLER_H + +#include +#include + +class Federation; +class SceneLoader; +class ViewportWindow; + +namespace ifcinterface::panels::viewport { + +class ViewportController : public QObject { + Q_OBJECT + +public: + explicit ViewportController(Federation* federation, + SceneLoader* loader, + ViewportWindow* viewport, + const QHash* fed_id_to_model_id, + const QHash* model_id_to_fed_id, + QObject* parent = nullptr); + + void applyFederatedFalseOrigin(); + +private: + void applyCoordinateOperation(uint32_t mid); + void applyModelTransformation(uint32_t mid); + void maybeGuessFederatedFalseOrigin(uint32_t mid); + + Federation* federation_ = nullptr; + SceneLoader* loader_ = nullptr; + ViewportWindow* viewport_ = nullptr; + const QHash* fed_id_to_model_id_ = nullptr; + const QHash* model_id_to_fed_id_ = nullptr; +}; + +} // namespace ifcinterface::panels::viewport + +#endif diff --git a/src/interface/panels/viewport/Widget.cpp b/src/interface/panels/viewport/Widget.cpp new file mode 100644 index 0000000000..9b6faacd15 --- /dev/null +++ b/src/interface/panels/viewport/Widget.cpp @@ -0,0 +1,60 @@ +// 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 "Widget.h" + +#include "../../../ifcviewer/ViewportWindow.h" + +#include +#include +#include + +namespace ifcinterface::panels::viewport { + +ViewportWidget::ViewportWidget(QWidget* parent) + : QWidget(parent) +{ + auto* root = new QVBoxLayout(this); + root->setContentsMargins(0, 0, 0, 0); + root->setSpacing(0); + + auto* shell = new QFrame(this); + shell->setObjectName("viewportShell"); + auto* shell_layout = new QVBoxLayout(shell); + shell_layout->setContentsMargins(10, 10, 10, 10); + shell_layout->setSpacing(0); + + auto* frame = new QFrame(shell); + frame->setObjectName("viewportFrame"); + auto* frame_layout = new QVBoxLayout(frame); + frame_layout->setContentsMargins(0, 0, 0, 0); + frame_layout->setSpacing(0); + + viewport_ = new ViewportWindow(); + viewport_container_ = QWidget::createWindowContainer(viewport_, frame); + viewport_container_->setMinimumSize(400, 300); + viewport_container_->setFocusPolicy(Qt::StrongFocus); + + frame_layout->addWidget(viewport_container_); + shell_layout->addWidget(frame); + root->addWidget(shell); +} + +} // namespace ifcinterface::panels::viewport diff --git a/src/interface/panels/viewport/Widget.h b/src/interface/panels/viewport/Widget.h new file mode 100644 index 0000000000..08f9676759 --- /dev/null +++ b/src/interface/panels/viewport/Widget.h @@ -0,0 +1,45 @@ +// 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_PANELS_VIEWPORT_WIDGET_H +#define IFCINTERFACE_PANELS_VIEWPORT_WIDGET_H + +#include + +class ViewportWindow; + +namespace ifcinterface::panels::viewport { + +class ViewportWidget : public QWidget { + Q_OBJECT + +public: + explicit ViewportWidget(QWidget* parent = nullptr); + + ViewportWindow* viewport() const { return viewport_; } + +private: + ViewportWindow* viewport_ = nullptr; + QWidget* viewport_container_ = nullptr; +}; + +} // namespace ifcinterface::panels::viewport + +#endif