From bae1eddda9ced86a3bb0aeb5c9955818c252fc02 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 7 May 2026 17:10:04 +1000 Subject: [PATCH] Interface mockup 12 --- src/interface/CMakeLists.txt | 4 + src/interface/ElementRegistry.cpp | 10 ++ src/interface/ElementRegistry.h | 1 + src/interface/MainWindow.cpp | 115 +++++++++--------- src/interface/MainWindow.h | 7 +- src/interface/SessionState.cpp | 107 ++++++++++++++++ src/interface/SessionState.h | 91 ++++++++++++++ src/interface/components/Style.cpp | 27 ++++ src/interface/panels/properties/View.cpp | 23 ++-- src/interface/panels/properties/View.h | 9 +- .../panels/spatial_hierarchy/View.cpp | 10 +- src/interface/panels/spatial_hierarchy/View.h | 9 +- src/interface/panels/viewport/Controller.cpp | 99 +++++++++------ src/interface/panels/viewport/Controller.h | 15 +-- 14 files changed, 399 insertions(+), 128 deletions(-) create mode 100644 src/interface/SessionState.cpp create mode 100644 src/interface/SessionState.h diff --git a/src/interface/CMakeLists.txt b/src/interface/CMakeLists.txt index 5eee8a0824..4ccf79cdb6 100644 --- a/src/interface/CMakeLists.txt +++ b/src/interface/CMakeLists.txt @@ -27,6 +27,8 @@ set(INTERFACE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ElementRegistry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ElementRegistry.h + ${CMAKE_CURRENT_SOURCE_DIR}/SessionState.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/SessionState.h ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.h ${CMAKE_CURRENT_SOURCE_DIR}/components/SvgIcon.cpp @@ -48,6 +50,8 @@ set(INTERFACE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/panels/add_model/Dialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/panels/add_model/Dialog.h ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/Types.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/Controller.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/Controller.h ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/Widget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/Widget.h ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/View.cpp diff --git a/src/interface/ElementRegistry.cpp b/src/interface/ElementRegistry.cpp index 12f84e5d23..724c5ad987 100644 --- a/src/interface/ElementRegistry.cpp +++ b/src/interface/ElementRegistry.cpp @@ -43,6 +43,16 @@ void ElementRegistry::clear() { elements_.clear(); } +void ElementRegistry::removeModel(uint32_t model_id) { + for (auto it = elements_.begin(); it != elements_.end();) { + if (it->second.model_id == model_id) { + it = elements_.erase(it); + } else { + ++it; + } + } +} + std::optional ElementRegistry::findBasicElementInfo(uint32_t object_id) const { auto it = elements_.find(object_id); if (it == elements_.end()) return std::nullopt; diff --git a/src/interface/ElementRegistry.h b/src/interface/ElementRegistry.h index e6ee785184..abad8451da 100644 --- a/src/interface/ElementRegistry.h +++ b/src/interface/ElementRegistry.h @@ -52,6 +52,7 @@ public: void bindLoader(SceneLoader* loader); void clear(); + void removeModel(uint32_t model_id); std::optional findBasicElementInfo(uint32_t object_id) const; std::optional findEntity(uint32_t object_id) const; diff --git a/src/interface/MainWindow.cpp b/src/interface/MainWindow.cpp index 96f311401e..78fac76f51 100644 --- a/src/interface/MainWindow.cpp +++ b/src/interface/MainWindow.cpp @@ -25,11 +25,13 @@ #include "../ifcviewer/SceneLoader.h" #include "../ifcviewer/ViewportWindow.h" #include "ElementRegistry.h" +#include "SessionState.h" #include "components/Buttons.h" #include "components/Panel.h" #include "components/Style.h" #include "components/Tabs.h" #include "panels/add_model/Dialog.h" +#include "panels/models/Controller.h" #include "panels/todo/Widget.h" #include "panels/models/View.h" #include "panels/models/Widget.h" @@ -63,6 +65,9 @@ MainWindow::MainWindow(QWidget* parent) { federation_ = new Federation(this); element_registry_ = new ifcinterface::ElementRegistry(this); + session_state_ = new ifcinterface::SessionState(this); + session_state_->bindFederation(federation_); + session_state_->bindElementRegistry(element_registry_); connect(federation_, &Federation::dirtyChanged, this, [this](bool dirty) { setWindowModified(dirty); updateWindowTitle(); @@ -92,11 +97,6 @@ QWidget* MainWindow::makeRibbonGroup(const QString& title, const QListsetText(mode); - status_selection_label_->setText(detail); -} - QToolButton* MainWindow::makePanelToggle(const QString& text, QDockWidget* dock) { auto* button = makeRibbonAction(text, ":/icons/sidebar-expand.svg"); button->setCheckable(true); @@ -125,11 +125,11 @@ QWidget* MainWindow::buildHomeRibbonPage() { connect(open_project, &QToolButton::clicked, this, &MainWindow::onOpenProject); auto* open_cloud = makeRibbonAction("Open Cloud", ":/icons/cloud-square.svg"); connect(open_cloud, &QToolButton::clicked, this, [this]() { - setStatusMessage("Project", "Open Cloud Project coming soon"); + session_state_->setStatusMessage("Project", "Open Cloud Project coming soon"); }); auto* open_recent = makeRibbonAction("Open Recent", ":/icons/clock-rotate-right.svg"); connect(open_recent, &QToolButton::clicked, this, [this]() { - setStatusMessage("Project", "Open Recent coming soon"); + session_state_->setStatusMessage("Project", "Open Recent coming soon"); }); auto* save_project = makeRibbonAction("Save Project", ":/icons/floppy-disk.svg"); connect(save_project, &QToolButton::clicked, this, &MainWindow::onSaveProject); @@ -140,7 +140,7 @@ QWidget* MainWindow::buildHomeRibbonPage() { connect(add_model, &QToolButton::clicked, this, &MainWindow::onAddFiles); auto* sync_models = makeRibbonAction("Sync Models", ":/icons/refresh-double.svg"); connect(sync_models, &QToolButton::clicked, this, [this]() { - setStatusMessage("Models", "Sync models coming soon"); + session_state_->setStatusMessage("Models", "Sync models coming soon"); }); auto* settings_button = makeRibbonAction("Settings", ":/icons/settings.svg"); @@ -190,7 +190,7 @@ QWidget* MainWindow::buildNavigateRibbonPage() { }); auto* align_object = makeRibbonAction("Align Object", ":/icons/cellar.svg"); connect(align_object, &QToolButton::clicked, this, [this]() { - setStatusMessage("Orientation", "Align to object coming soon"); + session_state_->setStatusMessage("Orientation", "Align to object coming soon"); }); auto* projection_button = makeRibbonAction("Perspective", ":/icons/perspective-view.svg"); connect(projection_button, &QToolButton::clicked, this, [this, projection_button]() { @@ -202,11 +202,11 @@ QWidget* MainWindow::buildNavigateRibbonPage() { auto* orbit_mode = makeRibbonAction("Orbit", ":/icons/rotate-camera-right.svg"); connect(orbit_mode, &QToolButton::clicked, this, [this]() { - setStatusMessage("Mode", "Orbit mode active"); + session_state_->setStatusMessage("Mode", "Orbit mode active"); }); auto* fly_mode = makeRibbonAction("Fly", ":/icons/drone.svg"); connect(fly_mode, &QToolButton::clicked, this, [this]() { - setStatusMessage("Mode", "Fly mode coming soon"); + session_state_->setStatusMessage("Mode", "Fly mode coming soon"); }); row->addWidget(makeRibbonGroup("CAMERA", {set_home, go_home, view_all, view_selected})); @@ -225,32 +225,32 @@ QWidget* MainWindow::buildInspectRibbonPage() { auto* hide_selected = makeRibbonAction("Hide", ":/icons/eye-closed.svg"); connect(hide_selected, &QToolButton::clicked, this, [this]() { - setStatusMessage("Selection", "Hide selected coming soon"); + session_state_->setStatusMessage("Selection", "Hide selected coming soon"); }); auto* isolate_selected = makeRibbonAction("Isolate", ":/icons/eye-solid.svg"); connect(isolate_selected, &QToolButton::clicked, this, [this]() { - setStatusMessage("Selection", "Isolate selected coming soon"); + session_state_->setStatusMessage("Selection", "Isolate selected coming soon"); }); auto* show_all = makeRibbonAction("Show All", ":/icons/eye.svg"); connect(show_all, &QToolButton::clicked, this, [this]() { - setStatusMessage("Selection", "Show all coming soon"); + session_state_->setStatusMessage("Selection", "Show all coming soon"); }); auto* invert_selection = makeRibbonAction("Invert", ":/icons/intersect.svg"); connect(invert_selection, &QToolButton::clicked, this, [this]() { - setStatusMessage("Selection", "Invert selection coming soon"); + session_state_->setStatusMessage("Selection", "Invert selection coming soon"); }); auto* distance = makeRibbonAction("Distance", ":/icons/select-edge3d.svg"); connect(distance, &QToolButton::clicked, this, [this]() { - setStatusMessage("Measure", "Distance coming soon"); + session_state_->setStatusMessage("Measure", "Distance coming soon"); }); auto* area = makeRibbonAction("Area", ":/icons/select-face3d.svg"); connect(area, &QToolButton::clicked, this, [this]() { - setStatusMessage("Measure", "Area coming soon"); + session_state_->setStatusMessage("Measure", "Area coming soon"); }); auto* volume = makeRibbonAction("Volume", ":/icons/select-point3d.svg"); connect(volume, &QToolButton::clicked, this, [this]() { - setStatusMessage("Measure", "Volume coming soon"); + session_state_->setStatusMessage("Measure", "Volume coming soon"); }); row->addWidget(makeRibbonGroup("SELECTION", {hide_selected, isolate_selected, show_all, invert_selection})); @@ -333,15 +333,11 @@ void MainWindow::setupPanels() { spatial_panel_ = new panels::spatial_hierarchy::SpatialHierarchyPanelWidget(this); properties_panel_ = new panels::properties::PropertiesPanelWidget(this); - 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_widget_->viewport(), element_registry_, this); - - connect(models_view_, &panels::models::ModelsPanelView::statusMessageRequested, - this, &MainWindow::setStatusMessage); - connect(spatial_view_, &panels::spatial_hierarchy::SpatialHierarchyPanelView::statusMessageRequested, - this, &MainWindow::setStatusMessage); + models_controller_ = new panels::models::ModelsPanelController( + models_panel_, session_state_, viewport_widget_->viewport(), element_registry_, this); + models_view_ = new panels::models::ModelsPanelView(models_panel_, session_state_, this); + spatial_view_ = new panels::spatial_hierarchy::SpatialHierarchyPanelView(spatial_panel_, session_state_, this); + properties_view_ = new panels::properties::PropertiesPanelView(properties_panel_, session_state_, this); layers_panel_ = new components::Panel("Layers", new panels::todo::TodoPanelWidget("Layers", this), this); stored_views_panel_ = new components::Panel( @@ -403,14 +399,20 @@ void MainWindow::setupStatus() { status_perf_label_->setVisible(show); if (!show) status_perf_label_->clear(); }); + connect(session_state_, &ifcinterface::SessionState::statusMessageChanged, + this, [this](const QString& mode, const QString& detail) { + status_mode_label_->setText(mode); + status_selection_label_->setText(detail); + }); + session_state_->setStatusMessage("Ready", "No selection"); } void MainWindow::setupLoader() { loader_ = new SceneLoader(viewport_widget_->viewport(), this); element_registry_->bindLoader(loader_); + session_state_->bindLoader(loader_); viewport_controller_ = new panels::viewport::ViewportController( - federation_, loader_, viewport_widget_->viewport(), - &fed_id_to_model_id_, &model_id_to_fed_id_, this); + session_state_, viewport_widget_->viewport(), this); connect(loader_, &SceneLoader::loadStarted, this, &MainWindow::onLoadStarted); connect(loader_, &SceneLoader::loadedFromSidecar, this, &MainWindow::onLoadedFromSidecar); connect(loader_, &SceneLoader::loadedFromStream, this, &MainWindow::onLoadedFromStream); @@ -431,7 +433,11 @@ void MainWindow::setupLoader() { .arg(s.total_triangles) .arg(s.gl_draw_calls)); }); - + connect(viewport_widget_->viewport(), &ViewportWindow::objectPicked, + this, [this](uint32_t object_id) { + session_state_->setSelectedObjectId(object_id); + session_state_->notifySelectionChanged(); + }); } void MainWindow::addFiles(const QStringList& paths) { @@ -504,17 +510,18 @@ void MainWindow::onAddFiles() { void MainWindow::clearScene() { if (viewport_widget_) viewport_widget_->viewport()->setSelectedObjectId(0); - if (properties_view_) properties_view_->clearSelection(); + session_state_->setSelectedObjectId(0); + session_state_->notifySelectionChanged(); - const auto model_ids = model_id_to_fed_id_.keys(); + const auto model_ids = session_state_->modelIds(); for (uint32_t mid : model_ids) { viewport_widget_->viewport()->removeModel(mid); loader_->removeModel(mid); } - fed_id_to_model_id_.clear(); - model_id_to_fed_id_.clear(); + session_state_->clearModelMappings(); element_registry_->clear(); + session_state_->notifyModelsChanged(); } bool MainWindow::confirmDiscardIfDirty() { @@ -533,9 +540,9 @@ void MainWindow::loadModelsFromPaths(const QStringList& paths, const QStringList if (paths.isEmpty()) return; const auto ids = loader_->addFiles(paths); for (int i = 0; i < paths.size() && i < static_cast(ids.size()) && i < fed_ids.size(); ++i) { - fed_id_to_model_id_[fed_ids[i]] = ids[i]; - model_id_to_fed_id_[ids[i]] = fed_ids[i]; + session_state_->setModelMapping(fed_ids[i], ids[i]); } + session_state_->notifyModelsChanged(); } bool MainWindow::openProject(const QString& path) { @@ -587,7 +594,8 @@ bool MainWindow::openProject(const QString& path) { hv.target.x(), hv.target.y(), hv.target.z(), hv.distance, hv.yaw, hv.pitch); } updateWindowTitle(); - setStatusMessage("Project", QFileInfo(path).fileName()); + session_state_->setStatusMessage("Project", QFileInfo(path).fileName()); + session_state_->notifyProjectOpened(path); return true; } @@ -601,7 +609,7 @@ bool MainWindow::saveProject() { return false; } updateWindowTitle(); - setStatusMessage("Project", QFileInfo(federation_->filePath()).fileName()); + session_state_->setStatusMessage("Project", QFileInfo(federation_->filePath()).fileName()); return true; } @@ -626,7 +634,7 @@ bool MainWindow::saveProjectAs() { return false; } updateWindowTitle(); - setStatusMessage("Project", QFileInfo(path).fileName()); + session_state_->setStatusMessage("Project", QFileInfo(path).fileName()); return true; } @@ -653,7 +661,8 @@ void MainWindow::onNewProject() { federation_->clear(); viewport_controller_->applyFederatedFalseOrigin(); updateWindowTitle(); - setStatusMessage("Project", "Untitled"); + session_state_->setStatusMessage("Project", "Untitled"); + session_state_->notifyProjectReset(); } void MainWindow::onOpenProject() { @@ -684,12 +693,12 @@ void MainWindow::onSetHomeView() { home_view.pitch = camera.pitch; federation_->setHomeView(home_view); updateWindowTitle(); - setStatusMessage("Camera", "Home view updated"); + session_state_->setStatusMessage("Camera", "Home view updated"); } void MainWindow::onGoHomeView() { if (!federation_->hasHomeView()) { - setStatusMessage("Camera", "No home view set for this project"); + session_state_->setStatusMessage("Camera", "No home view set for this project"); return; } @@ -697,44 +706,40 @@ void MainWindow::onGoHomeView() { 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"); + session_state_->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); + session_state_->setStatusMessage("Loading", display_name); } void MainWindow::onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms) { - status_mode_label_->setText("Loaded"); - status_selection_label_->setText( + session_state_->setStatusMessage( + "Loaded", QString("%1 from cache in %2") .arg(loader_->displayName(mid)) .arg(formatElapsed(elapsed_ms))); } void MainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) { - status_mode_label_->setText("Loaded"); - status_selection_label_->setText( + session_state_->setStatusMessage( + "Loaded", QString("%1 streamed in %2") .arg(loader_->displayName(mid)) .arg(formatElapsed(elapsed_ms))); } void MainWindow::onLoadCancelled(uint32_t mid) { - status_mode_label_->setText("Cancelled"); - status_selection_label_->setText(loader_->displayName(mid)); + session_state_->setStatusMessage("Cancelled", loader_->displayName(mid)); } void MainWindow::onLoadError(uint32_t /*mid*/, QString message) { - status_mode_label_->setText("Error"); - status_selection_label_->setText(message); + session_state_->setStatusMessage("Error", message); QMessageBox::warning(this, "IfcInterfaceMockup", message); } void MainWindow::onAllLoadsFinished() { - status_mode_label_->setText("Loaded"); - status_selection_label_->setText(QString("%1 model(s)").arg(loader_->modelCount())); + session_state_->setStatusMessage("Loaded", QString("%1 model(s)").arg(loader_->modelCount())); } } // namespace ifcinterface::shell diff --git a/src/interface/MainWindow.h b/src/interface/MainWindow.h index 18a7b93a25..c42612576d 100644 --- a/src/interface/MainWindow.h +++ b/src/interface/MainWindow.h @@ -32,7 +32,9 @@ class QToolButton; class Federation; class SceneLoader; namespace ifcinterface { class ElementRegistry; } +namespace ifcinterface { class SessionState; } namespace ifcinterface::components { class TabBar; } +namespace ifcinterface::panels::models { class ModelsPanelController; } namespace ifcinterface::panels::models { class ModelsPanelWidget; } namespace ifcinterface::panels::models { class ModelsPanelView; } namespace ifcinterface::panels::spatial_hierarchy { class SpatialHierarchyPanelWidget; } @@ -70,7 +72,6 @@ private: QToolButton* makeRibbonAction(const QString& text, const QString& icon_path); QWidget* makeRibbonGroup(const QString& title, const QList& buttons); QToolButton* makePanelToggle(const QString& text, QDockWidget* dock); - void setStatusMessage(const QString& mode, const QString& detail); void addFiles(const QStringList& paths); QString formatElapsed(qint64 ms) const; @@ -100,6 +101,7 @@ private: ifcinterface::panels::viewport::ViewportController* viewport_controller_ = nullptr; SceneLoader* loader_ = nullptr; ifcinterface::ElementRegistry* element_registry_ = nullptr; + ifcinterface::SessionState* session_state_ = nullptr; ifcinterface::panels::models::ModelsPanelWidget* models_panel_ = nullptr; ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelWidget* spatial_panel_ = nullptr; QDockWidget* layers_panel_ = nullptr; @@ -110,11 +112,10 @@ private: QDockWidget* audit_panel_ = nullptr; QDockWidget* clash_panel_ = nullptr; QDockWidget* issues_panel_ = nullptr; + ifcinterface::panels::models::ModelsPanelController* models_controller_ = nullptr; ifcinterface::panels::models::ModelsPanelView* models_view_ = nullptr; ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelView* spatial_view_ = nullptr; ifcinterface::panels::properties::PropertiesPanelView* properties_view_ = nullptr; - QHash fed_id_to_model_id_; - QHash model_id_to_fed_id_; }; } // namespace ifcinterface::shell diff --git a/src/interface/SessionState.cpp b/src/interface/SessionState.cpp new file mode 100644 index 0000000000..b8488e65dc --- /dev/null +++ b/src/interface/SessionState.cpp @@ -0,0 +1,107 @@ +// 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 "SessionState.h" + +#include "../ifcviewer/Federation.h" + +namespace ifcinterface { + +SessionState::SessionState(QObject* parent) + : QObject(parent) +{ +} + +void SessionState::bindFederation(Federation* federation) { + federation_ = federation; +} + +void SessionState::bindLoader(SceneLoader* loader) { + loader_ = loader; +} + +void SessionState::bindElementRegistry(ElementRegistry* element_registry) { + element_registry_ = element_registry; +} + +void SessionState::setSelectedObjectId(uint32_t object_id) { + selected_object_id_ = object_id; +} + +void SessionState::setStatusMessage(const QString& mode, const QString& detail) { + status_mode_ = mode; + status_detail_ = detail; + emit statusMessageChanged(status_mode_, status_detail_); +} + +void SessionState::setModelMapping(const QString& fed_id, uint32_t model_id) { + fed_id_to_model_id_[fed_id] = model_id; + model_id_to_fed_id_[model_id] = fed_id; +} + +void SessionState::removeModelMappingByFedId(const QString& fed_id) { + auto it = fed_id_to_model_id_.find(fed_id); + if (it == fed_id_to_model_id_.end()) return; + model_id_to_fed_id_.remove(it.value()); + fed_id_to_model_id_.erase(it); +} + +void SessionState::clearModelMappings() { + fed_id_to_model_id_.clear(); + model_id_to_fed_id_.clear(); +} + +uint32_t SessionState::modelIdForFedId(const QString& fed_id) const { + return fed_id_to_model_id_.value(fed_id, 0); +} + +QString SessionState::fedIdForModelId(uint32_t model_id) const { + return model_id_to_fed_id_.value(model_id); +} + +QList SessionState::modelIds() const { + return model_id_to_fed_id_.keys(); +} + +void SessionState::notifySelectionChanged() { + emit selectionChanged(selected_object_id_); +} + +void SessionState::notifyModelsChanged() { + emit modelsChanged(); +} + +void SessionState::notifyFederationStructureChanged() { + emit federationStructureChanged(); +} + +void SessionState::notifyVisibilityChanged() { + emit visibilityChanged(); +} + +void SessionState::notifyProjectOpened(const QString& path) { + emit projectOpened(path); +} + +void SessionState::notifyProjectReset() { + emit projectReset(); +} + +} // namespace ifcinterface diff --git a/src/interface/SessionState.h b/src/interface/SessionState.h new file mode 100644 index 0000000000..4b543fcb43 --- /dev/null +++ b/src/interface/SessionState.h @@ -0,0 +1,91 @@ +// 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_SESSIONSTATE_H +#define IFCINTERFACE_SESSIONSTATE_H + +#include +#include +#include + +class Federation; +class SceneLoader; + +namespace ifcinterface { + +class ElementRegistry; + +class SessionState : public QObject { + Q_OBJECT + +public: + explicit SessionState(QObject* parent = nullptr); + + void bindFederation(Federation* federation); + void bindLoader(SceneLoader* loader); + void bindElementRegistry(ElementRegistry* element_registry); + + Federation* federation() const { return federation_; } + SceneLoader* loader() const { return loader_; } + ElementRegistry* elementRegistry() const { return element_registry_; } + QString statusMode() const { return status_mode_; } + QString statusDetail() const { return status_detail_; } + + void setSelectedObjectId(uint32_t object_id); + uint32_t selectedObjectId() const { return selected_object_id_; } + void setStatusMessage(const QString& mode, const QString& detail); + + void setModelMapping(const QString& fed_id, uint32_t model_id); + void removeModelMappingByFedId(const QString& fed_id); + void clearModelMappings(); + uint32_t modelIdForFedId(const QString& fed_id) const; + QString fedIdForModelId(uint32_t model_id) const; + QList modelIds() const; + + void notifySelectionChanged(); + void notifyModelsChanged(); + void notifyFederationStructureChanged(); + void notifyVisibilityChanged(); + void notifyProjectOpened(const QString& path); + void notifyProjectReset(); + +signals: + void projectOpened(const QString& path); + void projectReset(); + void modelsChanged(); + void federationStructureChanged(); + void visibilityChanged(); + void selectionChanged(uint32_t object_id); + void statusMessageChanged(const QString& mode, const QString& detail); + +private: + Federation* federation_ = nullptr; + SceneLoader* loader_ = nullptr; + ElementRegistry* element_registry_ = nullptr; + uint32_t selected_object_id_ = 0; + QString status_mode_; + QString status_detail_; + QHash fed_id_to_model_id_; + QHash model_id_to_fed_id_; +}; + +} // namespace ifcinterface + +#endif diff --git a/src/interface/components/Style.cpp b/src/interface/components/Style.cpp index f7b7c5f14b..a115247716 100644 --- a/src/interface/components/Style.cpp +++ b/src/interface/components/Style.cpp @@ -253,6 +253,33 @@ QString buildAppStyleSheet() { QPushButton:pressed { background: ${ribbon_button_pressed}; } + QMenu { + background: ${panel_background}; + color: ${primary_text}; + border: 1px solid ${border}; + padding: 4px; + } + QMenu::item { + background: transparent; + color: ${primary_text}; + padding: 6px 24px 6px 10px; + border-radius: ${panel_radius}px; + } + QMenu::item:selected { + background: ${selection_background}; + color: ${selection_text}; + } + QMenu::item:disabled { + color: ${disabled_text}; + } + QMenu::separator { + height: 1px; + background: ${border}; + margin: 4px 8px; + } + QMenu::icon { + padding-left: 2px; + } QFrame#entityClassBox, QGroupBox#propertySetBox { background: ${box_background}; diff --git a/src/interface/panels/properties/View.cpp b/src/interface/panels/properties/View.cpp index c6535e3d80..aa93b85422 100644 --- a/src/interface/panels/properties/View.cpp +++ b/src/interface/panels/properties/View.cpp @@ -23,28 +23,27 @@ #include "Widget.h" #include "../../ElementRegistry.h" +#include "../../SessionState.h" #include "../../../ifcviewer/AppSettings.h" -#include "../../../ifcviewer/ViewportWindow.h" namespace ifcinterface::panels::properties { PropertiesPanelView::PropertiesPanelView(PropertiesPanelWidget* widget, - ViewportWindow* viewport, - ifcinterface::ElementRegistry* registry, + ifcinterface::SessionState* session_state, QObject* parent) - : QObject(parent), widget_(widget), registry_(registry) + : QObject(parent), widget_(widget), session_state_(session_state) { - connect(viewport, &ViewportWindow::objectPicked, this, [this](uint32_t object_id) { + connect(session_state_, &ifcinterface::SessionState::selectionChanged, this, [this](uint32_t object_id) { refresh(object_id); }); + connect(session_state_, &ifcinterface::SessionState::projectReset, this, [this]() { + refresh(0); + }); refresh(0); } -void PropertiesPanelView::clearSelection() { - refresh(0); -} - void PropertiesPanelView::refresh(uint32_t object_id) { + auto* registry = session_state_->elementRegistry(); PropertiesPanelState state; state.entity = {"IfcWall", "SOLIDWALL"}; state.attributes = { @@ -84,13 +83,13 @@ void PropertiesPanelView::refresh(uint32_t object_id) { {"Paint Coverage", "42.78 m2"}}}, }; - if (!registry_) { + if (!registry) { widget_->render(state); return; } if (!AppSettings::instance().loadDataSource()) { - auto info = registry_->findBasicElementInfo(object_id); + auto info = registry->findBasicElementInfo(object_id); if (info && !info->type.isEmpty()) { state.entity.entity_class = info->type; if (!state.property_sets.isEmpty() && !state.property_sets[1].rows.isEmpty()) { @@ -111,7 +110,7 @@ void PropertiesPanelView::refresh(uint32_t object_id) { return; } - auto entity = registry_->findEntity(object_id); + auto entity = registry->findEntity(object_id); if (entity) { state.entity.entity_class = QString::fromStdString(entity->declaration().name()); if (!state.property_sets.isEmpty() && !state.property_sets[1].rows.isEmpty()) { diff --git a/src/interface/panels/properties/View.h b/src/interface/panels/properties/View.h index f68b1dfc45..8ed49e1bfd 100644 --- a/src/interface/panels/properties/View.h +++ b/src/interface/panels/properties/View.h @@ -25,8 +25,7 @@ #include -namespace ifcinterface { class ElementRegistry; } -class ViewportWindow; +namespace ifcinterface { class SessionState; } namespace ifcinterface::panels::properties { class PropertiesPanelWidget; @@ -35,16 +34,14 @@ class PropertiesPanelView : public QObject { Q_OBJECT public: explicit PropertiesPanelView(PropertiesPanelWidget* widget, - ViewportWindow* viewport, - ifcinterface::ElementRegistry* registry, + ifcinterface::SessionState* session_state, QObject* parent = nullptr); - void clearSelection(); private: void refresh(uint32_t object_id); PropertiesPanelWidget* widget_ = nullptr; - ifcinterface::ElementRegistry* registry_ = nullptr; + ifcinterface::SessionState* session_state_ = nullptr; }; } // namespace ifcinterface::panels::properties diff --git a/src/interface/panels/spatial_hierarchy/View.cpp b/src/interface/panels/spatial_hierarchy/View.cpp index 65077906e8..de78b3db31 100644 --- a/src/interface/panels/spatial_hierarchy/View.cpp +++ b/src/interface/panels/spatial_hierarchy/View.cpp @@ -22,6 +22,8 @@ #include "Widget.h" +#include "../../SessionState.h" + namespace ifcinterface::panels::spatial_hierarchy { namespace { @@ -37,8 +39,10 @@ TreeNode* findNodeRecursive(QList& nodes, const NodePath& path, int de } // namespace -SpatialHierarchyPanelView::SpatialHierarchyPanelView(SpatialHierarchyPanelWidget* widget, QObject* parent) - : QObject(parent), widget_(widget) +SpatialHierarchyPanelView::SpatialHierarchyPanelView(SpatialHierarchyPanelWidget* widget, + ifcinterface::SessionState* session_state, + QObject* parent) + : QObject(parent), widget_(widget), session_state_(session_state) { nodes_ = { {"Site A", ItemKind::Site, true, @@ -52,7 +56,7 @@ SpatialHierarchyPanelView::SpatialHierarchyPanelView(SpatialHierarchyPanelWidget if (auto* node = findNode(path)) { node->visible = !node->visible; reload(); - emit statusMessageRequested("Spatial", node->visible ? "Item shown" : "Item hidden"); + session_state_->setStatusMessage("Spatial", node->visible ? "Item shown" : "Item hidden"); } }); diff --git a/src/interface/panels/spatial_hierarchy/View.h b/src/interface/panels/spatial_hierarchy/View.h index 4df2ddc124..123db712ef 100644 --- a/src/interface/panels/spatial_hierarchy/View.h +++ b/src/interface/panels/spatial_hierarchy/View.h @@ -25,6 +25,7 @@ #include +namespace ifcinterface { class SessionState; } namespace ifcinterface::panels::spatial_hierarchy { class SpatialHierarchyPanelWidget; @@ -32,16 +33,16 @@ class SpatialHierarchyPanelWidget; class SpatialHierarchyPanelView : public QObject { Q_OBJECT public: - explicit SpatialHierarchyPanelView(SpatialHierarchyPanelWidget* widget, QObject* parent = nullptr); - -signals: - void statusMessageRequested(const QString& mode, const QString& detail); + explicit SpatialHierarchyPanelView(SpatialHierarchyPanelWidget* widget, + ifcinterface::SessionState* session_state, + QObject* parent = nullptr); private: void reload(); TreeNode* findNode(const NodePath& path); SpatialHierarchyPanelWidget* widget_ = nullptr; + ifcinterface::SessionState* session_state_ = nullptr; QList nodes_; }; diff --git a/src/interface/panels/viewport/Controller.cpp b/src/interface/panels/viewport/Controller.cpp index 14fc3dfea7..acbe20e896 100644 --- a/src/interface/panels/viewport/Controller.cpp +++ b/src/interface/panels/viewport/Controller.cpp @@ -20,6 +20,7 @@ #include "Controller.h" +#include "../../SessionState.h" #include "../../../ifcviewer/AppSettings.h" #include "../../../ifcviewer/Federation.h" #include "../../../ifcviewer/SceneLoader.h" @@ -29,61 +30,74 @@ namespace ifcinterface::panels::viewport { -ViewportController::ViewportController(Federation* federation, - SceneLoader* loader, +ViewportController::ViewportController(ifcinterface::SessionState* session_state, ViewportWindow* viewport, - const QHash* fed_id_to_model_id, - const QHash* model_id_to_fed_id, QObject* parent) : QObject(parent) - , federation_(federation) - , loader_(loader) + , session_state_(session_state) , 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, + Federation* federation = session_state_->federation(); + SceneLoader* loader = session_state_->loader(); + connect(federation, &Federation::federatedFalseOriginChanged, this, &ViewportController::applyFederatedFalseOrigin); - connect(federation_, &Federation::configChanged, this, [this]() { + 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()); + for (uint32_t mid : session_state_->modelIds()) { + applyModelTransformation(mid); } }); - connect(federation_, &Federation::modelTransformationChanged, + 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()); + const uint32_t mid = session_state_->modelIdForFedId(fed_id); + if (mid != 0) applyModelTransformation(mid); + }); + connect(federation, &Federation::modelVisibilityChanged, + this, [this](const QString& fed_id, bool /*visible*/) { + const uint32_t mid = session_state_->modelIdForFedId(fed_id); + if (mid != 0) applyModelVisibility(mid); + }); + connect(federation, &Federation::modelGroupChanged, + this, [this](const QString& fed_id, const QString& /*group_id*/) { + const uint32_t mid = session_state_->modelIdForFedId(fed_id); + if (mid != 0) applyModelVisibility(mid); + }); + connect(federation, &Federation::groupVisibilityChanged, + this, [this](const QString&, bool /*visible*/) { + for (uint32_t mid : session_state_->modelIds()) { + applyModelVisibility(mid); } }); - connect(loader_, &SceneLoader::loadedFromSidecar, this, + connect(loader, &SceneLoader::loadedFromSidecar, this, [this](uint32_t mid, qint64 /*elapsed_ms*/) { applyCoordinateOperation(mid); + applyModelVisibility(mid); maybeGuessFederatedFalseOrigin(mid); }); - connect(loader_, &SceneLoader::dataSourceReady, this, + connect(loader, &SceneLoader::dataSourceReady, this, [this](uint32_t mid) { applyCoordinateOperation(mid); }); - connect(loader_, &SceneLoader::loadedFromStream, this, + connect(loader, &SceneLoader::loadedFromStream, this, [this](uint32_t mid, qint64 /*elapsed_ms*/) { applyCoordinateOperation(mid); + applyModelVisibility(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()); + for (uint32_t mid : session_state_->modelIds()) { + applyCoordinateOperation(mid); } }); } void ViewportController::applyCoordinateOperation(uint32_t mid) { + SceneLoader* loader = session_state_->loader(); Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity(); if (AppSettings::instance().applyCoordinateOperation()) { - if (const ModelGeoref* georef = loader_->modelGeoref(mid)) { + if (const ModelGeoref* georef = loader->modelGeoref(mid)) { if (georef->has_coordinate_operation) { matrix = georef->coordinate_operation_meters; } @@ -94,13 +108,15 @@ void ViewportController::applyCoordinateOperation(uint32_t mid) { } void ViewportController::applyModelTransformation(uint32_t mid) { + Federation* federation = session_state_->federation(); + SceneLoader* loader = session_state_->loader(); 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())) { + const QString fed_id = session_state_->fedIdForModelId(mid); + if (!fed_id.isEmpty()) { + if (const Federation::Model* model = federation->findById(fed_id)) { ModelUnits units; Eigen::Matrix4d coordinate_operation = Eigen::Matrix4d::Identity(); - if (const ModelGeoref* georef = loader_->modelGeoref(mid)) { + if (const ModelGeoref* georef = loader->modelGeoref(mid)) { units = georef->units; if (AppSettings::instance().applyCoordinateOperation() && georef->has_coordinate_operation) { @@ -108,30 +124,45 @@ void ViewportController::applyModelTransformation(uint32_t mid) { } } matrix = composeModelTransformation( - model->model_transformation, federation_->config(), units, coordinate_operation); + model->model_transformation, federation->config(), units, coordinate_operation); } } viewport_->setModelTransformation(mid, matrix); } +void ViewportController::applyModelVisibility(uint32_t mid) { + Federation* federation = session_state_->federation(); + const QString fed_id = session_state_->fedIdForModelId(mid); + if (fed_id.isEmpty()) return; + + if (federation->isModelEffectivelyVisible(fed_id)) { + viewport_->showModel(mid); + } else { + viewport_->hideModel(mid); + } +} + void ViewportController::applyFederatedFalseOrigin() { + Federation* federation = session_state_->federation(); viewport_->setFederatedFalseOrigin( - composeFederatedFalseOrigin(federation_->federatedFalseOrigin(), federation_->config())); + composeFederatedFalseOrigin(federation->federatedFalseOrigin(), federation->config())); } void ViewportController::maybeGuessFederatedFalseOrigin(uint32_t mid) { - if (!federation_->filePath().isEmpty()) return; + Federation* federation = session_state_->federation(); + SceneLoader* loader = session_state_->loader(); + if (!federation->filePath().isEmpty()) return; - const FederatedFalseOrigin& current = federation_->federatedFalseOrigin(); + 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); + 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(), + federation->setFederatedFalseOrigin(guessFederatedFalseOrigin( + *placement, *georef, federation->config(), AppSettings::instance().applyCoordinateOperation())); } diff --git a/src/interface/panels/viewport/Controller.h b/src/interface/panels/viewport/Controller.h index 25506cc9dd..69aff80b4d 100644 --- a/src/interface/panels/viewport/Controller.h +++ b/src/interface/panels/viewport/Controller.h @@ -21,11 +21,9 @@ #ifndef IFCINTERFACE_PANELS_VIEWPORT_CONTROLLER_H #define IFCINTERFACE_PANELS_VIEWPORT_CONTROLLER_H -#include #include -class Federation; -class SceneLoader; +namespace ifcinterface { class SessionState; } class ViewportWindow; namespace ifcinterface::panels::viewport { @@ -34,11 +32,8 @@ class ViewportController : public QObject { Q_OBJECT public: - explicit ViewportController(Federation* federation, - SceneLoader* loader, + explicit ViewportController(ifcinterface::SessionState* session_state, ViewportWindow* viewport, - const QHash* fed_id_to_model_id, - const QHash* model_id_to_fed_id, QObject* parent = nullptr); void applyFederatedFalseOrigin(); @@ -46,13 +41,11 @@ public: private: void applyCoordinateOperation(uint32_t mid); void applyModelTransformation(uint32_t mid); + void applyModelVisibility(uint32_t mid); void maybeGuessFederatedFalseOrigin(uint32_t mid); - Federation* federation_ = nullptr; - SceneLoader* loader_ = nullptr; + ifcinterface::SessionState* session_state_ = nullptr; ViewportWindow* viewport_ = nullptr; - const QHash* fed_id_to_model_id_ = nullptr; - const QHash* model_id_to_fed_id_ = nullptr; }; } // namespace ifcinterface::panels::viewport