From 4a91e6a87dede08383e9b931d2710659f2aca178 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 15 May 2026 07:18:26 +1000 Subject: [PATCH] Swap interface into IfcViewerFull Replace the old IfcViewerFull application tree with the interface-based viewer while preserving the IfcViewerFull target and build workflow. Generated with the assistance of an AI coding tool. --- src/ifcviewer-full/CMakeLists.txt | 79 +- .../ElementRegistry.cpp | 0 .../ElementRegistry.h | 0 .../FederationSettingsDialog.cpp | 174 -- src/ifcviewer-full/FederationSettingsDialog.h | 61 - .../InterfaceSettings.cpp | 0 .../InterfaceSettings.h | 0 src/ifcviewer-full/MainWindow.cpp | 1840 +++++------------ src/ifcviewer-full/MainWindow.h | 245 +-- src/ifcviewer-full/Measurement.cpp | 1 + src/ifcviewer-full/Measurement.h | 7 +- .../ModelTransformationDialog.cpp | 248 --- .../ModelTransformationDialog.h | 88 - .../SessionState.cpp | 0 .../SessionState.h | 0 src/ifcviewer-full/SettingsWindow.cpp | 171 -- src/ifcviewer-full/SettingsWindow.h | 59 - .../components/Buttons.cpp | 0 .../components/Buttons.h | 0 .../components/Dialog.cpp | 0 .../components/Dialog.h | 0 .../components/KeyValueTable.cpp | 0 .../components/KeyValueTable.h | 0 .../components/Panel.cpp | 0 .../components/Panel.h | 0 .../components/Section.cpp | 0 .../components/Section.h | 0 .../components/Style.cpp | 0 .../components/Style.h | 0 .../components/SvgIcon.cpp | 0 .../components/SvgIcon.h | 0 .../components/Tabs.cpp | 0 .../components/Tabs.h | 0 .../icons/box-3d-center.svg | 0 .../icons/box-3d-three-points.svg | 0 .../icons/building.svg | 0 .../icons/cellar.svg | 0 .../icons/check.svg | 0 .../icons/city.svg | 0 .../icons/clock-rotate-right.svg | 0 .../icons/cloud-square.svg | 0 .../icons/cube-bandage.svg | 0 .../icons/cube-cut-with-curve.svg | 0 .../icons/cube-dots.svg | 0 .../icons/cube-scan-solid.svg | 0 .../icons/cube-scan.svg | 0 .../icons/cube.svg | 0 .../icons/cursor-pointer.svg | 0 .../icons/database-restore.svg | 0 .../icons/database.svg | 0 .../icons/download-square.svg | 0 .../icons/drone.svg | 0 .../icons/eye-closed.svg | 0 .../icons/eye-solid.svg | 0 .../icons/eye.svg | 0 .../icons/face-3d-draft.svg | 0 .../icons/filter.svg | 0 .../icons/floppy-disk-arrow-in.svg | 0 .../icons/floppy-disk.svg | 0 .../icons/folder-minus.svg | 0 .../icons/folder-plus.svg | 0 .../icons/folder.svg | 0 .../icons/frame-alt.svg | 0 .../icons/home-alt.svg | 0 .../icons/home.svg | 0 .../icons/intersect.svg | 0 .../icons/minus-square.svg | 0 .../icons/perspective-view.svg | 0 .../icons/planimetry.svg | 0 .../icons/plus-square.svg | 0 .../icons/refresh-double.svg | 0 .../icons/select-edge3d.svg | 0 .../icons/select-face3d.svg | 0 .../icons/select-point3d.svg | 0 .../icons/settings.svg | 0 .../icons/sidebar-expand.svg | 0 .../icons/square3d-from-center.svg | 0 .../icons/xmark-circle.svg | 0 .../interface_resources.qrc | 0 src/ifcviewer-full/main.cpp | 75 +- .../modules/models/AddModelDialog.cpp | 0 .../modules/models/AddModelDialog.h | 0 .../modules/models/Controller.cpp | 2 +- .../modules/models/Controller.h | 0 .../modules/models/Panel.cpp | 0 .../modules/models/Panel.h | 0 .../modules/models/SettingsDialog.cpp | 0 .../modules/models/SettingsDialog.h | 0 .../modules/models/SettingsView.cpp | 0 .../modules/models/SettingsView.h | 0 .../modules/models/Types.h | 0 .../modules/models/View.cpp | 0 .../modules/models/View.h | 0 .../modules/project/Controller.cpp | 0 .../modules/project/Controller.h | 0 .../modules/properties/Panel.cpp | 0 .../modules/properties/Panel.h | 0 .../modules/properties/Types.h | 0 .../modules/properties/View.cpp | 0 .../modules/properties/View.h | 0 .../modules/settings/Dialog.cpp | 0 .../modules/settings/Dialog.h | 0 .../modules/spatial_hierarchy/Panel.cpp | 0 .../modules/spatial_hierarchy/Panel.h | 0 .../modules/spatial_hierarchy/Types.h | 0 .../modules/spatial_hierarchy/View.cpp | 0 .../modules/spatial_hierarchy/View.h | 0 .../modules/todo/Panel.cpp | 0 .../modules/todo/Panel.h | 0 .../modules/viewport/Controller.cpp | 2 +- .../modules/viewport/Controller.h | 0 .../modules/viewport/Panel.cpp | 0 .../modules/viewport/Panel.h | 0 src/interface/CMakeLists.txt | 107 - src/interface/MainWindow.cpp | 531 ----- src/interface/MainWindow.h | 102 - src/interface/main.cpp | 81 - 117 files changed, 672 insertions(+), 3201 deletions(-) rename src/{interface => ifcviewer-full}/ElementRegistry.cpp (100%) rename src/{interface => ifcviewer-full}/ElementRegistry.h (100%) delete mode 100644 src/ifcviewer-full/FederationSettingsDialog.cpp delete mode 100644 src/ifcviewer-full/FederationSettingsDialog.h rename src/{interface => ifcviewer-full}/InterfaceSettings.cpp (100%) rename src/{interface => ifcviewer-full}/InterfaceSettings.h (100%) delete mode 100644 src/ifcviewer-full/ModelTransformationDialog.cpp delete mode 100644 src/ifcviewer-full/ModelTransformationDialog.h rename src/{interface => ifcviewer-full}/SessionState.cpp (100%) rename src/{interface => ifcviewer-full}/SessionState.h (100%) delete mode 100644 src/ifcviewer-full/SettingsWindow.cpp delete mode 100644 src/ifcviewer-full/SettingsWindow.h rename src/{interface => ifcviewer-full}/components/Buttons.cpp (100%) rename src/{interface => ifcviewer-full}/components/Buttons.h (100%) rename src/{interface => ifcviewer-full}/components/Dialog.cpp (100%) rename src/{interface => ifcviewer-full}/components/Dialog.h (100%) rename src/{interface => ifcviewer-full}/components/KeyValueTable.cpp (100%) rename src/{interface => ifcviewer-full}/components/KeyValueTable.h (100%) rename src/{interface => ifcviewer-full}/components/Panel.cpp (100%) rename src/{interface => ifcviewer-full}/components/Panel.h (100%) rename src/{interface => ifcviewer-full}/components/Section.cpp (100%) rename src/{interface => ifcviewer-full}/components/Section.h (100%) rename src/{interface => ifcviewer-full}/components/Style.cpp (100%) rename src/{interface => ifcviewer-full}/components/Style.h (100%) rename src/{interface => ifcviewer-full}/components/SvgIcon.cpp (100%) rename src/{interface => ifcviewer-full}/components/SvgIcon.h (100%) rename src/{interface => ifcviewer-full}/components/Tabs.cpp (100%) rename src/{interface => ifcviewer-full}/components/Tabs.h (100%) rename src/{interface => ifcviewer-full}/icons/box-3d-center.svg (100%) rename src/{interface => ifcviewer-full}/icons/box-3d-three-points.svg (100%) rename src/{interface => ifcviewer-full}/icons/building.svg (100%) rename src/{interface => ifcviewer-full}/icons/cellar.svg (100%) rename src/{interface => ifcviewer-full}/icons/check.svg (100%) rename src/{interface => ifcviewer-full}/icons/city.svg (100%) rename src/{interface => ifcviewer-full}/icons/clock-rotate-right.svg (100%) rename src/{interface => ifcviewer-full}/icons/cloud-square.svg (100%) rename src/{interface => ifcviewer-full}/icons/cube-bandage.svg (100%) rename src/{interface => ifcviewer-full}/icons/cube-cut-with-curve.svg (100%) rename src/{interface => ifcviewer-full}/icons/cube-dots.svg (100%) rename src/{interface => ifcviewer-full}/icons/cube-scan-solid.svg (100%) rename src/{interface => ifcviewer-full}/icons/cube-scan.svg (100%) rename src/{interface => ifcviewer-full}/icons/cube.svg (100%) rename src/{interface => ifcviewer-full}/icons/cursor-pointer.svg (100%) rename src/{interface => ifcviewer-full}/icons/database-restore.svg (100%) rename src/{interface => ifcviewer-full}/icons/database.svg (100%) rename src/{interface => ifcviewer-full}/icons/download-square.svg (100%) rename src/{interface => ifcviewer-full}/icons/drone.svg (100%) rename src/{interface => ifcviewer-full}/icons/eye-closed.svg (100%) rename src/{interface => ifcviewer-full}/icons/eye-solid.svg (100%) rename src/{interface => ifcviewer-full}/icons/eye.svg (100%) rename src/{interface => ifcviewer-full}/icons/face-3d-draft.svg (100%) rename src/{interface => ifcviewer-full}/icons/filter.svg (100%) rename src/{interface => ifcviewer-full}/icons/floppy-disk-arrow-in.svg (100%) rename src/{interface => ifcviewer-full}/icons/floppy-disk.svg (100%) rename src/{interface => ifcviewer-full}/icons/folder-minus.svg (100%) rename src/{interface => ifcviewer-full}/icons/folder-plus.svg (100%) rename src/{interface => ifcviewer-full}/icons/folder.svg (100%) rename src/{interface => ifcviewer-full}/icons/frame-alt.svg (100%) rename src/{interface => ifcviewer-full}/icons/home-alt.svg (100%) rename src/{interface => ifcviewer-full}/icons/home.svg (100%) rename src/{interface => ifcviewer-full}/icons/intersect.svg (100%) rename src/{interface => ifcviewer-full}/icons/minus-square.svg (100%) rename src/{interface => ifcviewer-full}/icons/perspective-view.svg (100%) rename src/{interface => ifcviewer-full}/icons/planimetry.svg (100%) rename src/{interface => ifcviewer-full}/icons/plus-square.svg (100%) rename src/{interface => ifcviewer-full}/icons/refresh-double.svg (100%) rename src/{interface => ifcviewer-full}/icons/select-edge3d.svg (100%) rename src/{interface => ifcviewer-full}/icons/select-face3d.svg (100%) rename src/{interface => ifcviewer-full}/icons/select-point3d.svg (100%) rename src/{interface => ifcviewer-full}/icons/settings.svg (100%) rename src/{interface => ifcviewer-full}/icons/sidebar-expand.svg (100%) rename src/{interface => ifcviewer-full}/icons/square3d-from-center.svg (100%) rename src/{interface => ifcviewer-full}/icons/xmark-circle.svg (100%) rename src/{interface => ifcviewer-full}/interface_resources.qrc (100%) rename src/{interface => ifcviewer-full}/modules/models/AddModelDialog.cpp (100%) rename src/{interface => ifcviewer-full}/modules/models/AddModelDialog.h (100%) rename src/{interface => ifcviewer-full}/modules/models/Controller.cpp (99%) rename src/{interface => ifcviewer-full}/modules/models/Controller.h (100%) rename src/{interface => ifcviewer-full}/modules/models/Panel.cpp (100%) rename src/{interface => ifcviewer-full}/modules/models/Panel.h (100%) rename src/{interface => ifcviewer-full}/modules/models/SettingsDialog.cpp (100%) rename src/{interface => ifcviewer-full}/modules/models/SettingsDialog.h (100%) rename src/{interface => ifcviewer-full}/modules/models/SettingsView.cpp (100%) rename src/{interface => ifcviewer-full}/modules/models/SettingsView.h (100%) rename src/{interface => ifcviewer-full}/modules/models/Types.h (100%) rename src/{interface => ifcviewer-full}/modules/models/View.cpp (100%) rename src/{interface => ifcviewer-full}/modules/models/View.h (100%) rename src/{interface => ifcviewer-full}/modules/project/Controller.cpp (100%) rename src/{interface => ifcviewer-full}/modules/project/Controller.h (100%) rename src/{interface => ifcviewer-full}/modules/properties/Panel.cpp (100%) rename src/{interface => ifcviewer-full}/modules/properties/Panel.h (100%) rename src/{interface => ifcviewer-full}/modules/properties/Types.h (100%) rename src/{interface => ifcviewer-full}/modules/properties/View.cpp (100%) rename src/{interface => ifcviewer-full}/modules/properties/View.h (100%) rename src/{interface => ifcviewer-full}/modules/settings/Dialog.cpp (100%) rename src/{interface => ifcviewer-full}/modules/settings/Dialog.h (100%) rename src/{interface => ifcviewer-full}/modules/spatial_hierarchy/Panel.cpp (100%) rename src/{interface => ifcviewer-full}/modules/spatial_hierarchy/Panel.h (100%) rename src/{interface => ifcviewer-full}/modules/spatial_hierarchy/Types.h (100%) rename src/{interface => ifcviewer-full}/modules/spatial_hierarchy/View.cpp (100%) rename src/{interface => ifcviewer-full}/modules/spatial_hierarchy/View.h (100%) rename src/{interface => ifcviewer-full}/modules/todo/Panel.cpp (100%) rename src/{interface => ifcviewer-full}/modules/todo/Panel.h (100%) rename src/{interface => ifcviewer-full}/modules/viewport/Controller.cpp (99%) rename src/{interface => ifcviewer-full}/modules/viewport/Controller.h (100%) rename src/{interface => ifcviewer-full}/modules/viewport/Panel.cpp (100%) rename src/{interface => ifcviewer-full}/modules/viewport/Panel.h (100%) delete mode 100644 src/interface/CMakeLists.txt delete mode 100644 src/interface/MainWindow.cpp delete mode 100644 src/interface/MainWindow.h delete mode 100644 src/interface/main.cpp diff --git a/src/ifcviewer-full/CMakeLists.txt b/src/ifcviewer-full/CMakeLists.txt index ebe0a9b772..22ad90c3b6 100644 --- a/src/ifcviewer-full/CMakeLists.txt +++ b/src/ifcviewer-full/CMakeLists.txt @@ -1,3 +1,4 @@ +# This file was generated with the assistance of an AI coding tool. ################################################################################ # # # This file is part of IfcOpenShell. # @@ -19,19 +20,89 @@ message("Running CMakeLists.txt in /src/ifcviewer-full") -file(GLOB IFCVIEWER_FULL_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) -file(GLOB IFCVIEWER_FULL_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h) -set(IFCVIEWER_FULL_FILES ${IFCVIEWER_FULL_CPP_FILES} ${IFCVIEWER_FULL_H_FILES}) +set(QT_VERSION 6 CACHE STRING "Qt version") +find_package(Qt${QT_VERSION} COMPONENTS Core Gui Widgets Svg REQUIRED PATHS ${QT_DIR}) + +set(IFCVIEWER_FULL_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ElementRegistry.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ElementRegistry.h + ${CMAKE_CURRENT_SOURCE_DIR}/InterfaceSettings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/InterfaceSettings.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}/Measurement.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Measurement.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/SvgIcon.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/SvgIcon.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/Style.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/Style.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/Dialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/Dialog.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/Tabs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/Tabs.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/KeyValueTable.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/KeyValueTable.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/Buttons.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/Buttons.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/Section.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/Section.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/AddModelDialog.cpp + ${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 + ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Panel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Panel.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/View.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/View.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/todo/Panel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/todo/Panel.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/Types.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/Panel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/Panel.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/View.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/View.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/project/Controller.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/project/Controller.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/settings/Dialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/settings/Dialog.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/Types.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/Panel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/Panel.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/View.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/View.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Panel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Panel.h + ${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Controller.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Controller.h + ${CMAKE_CURRENT_SOURCE_DIR}/interface_resources.qrc +) add_executable(IfcViewerFull ${IFCVIEWER_FULL_FILES}) set_target_properties(IfcViewerFull PROPERTIES AUTOMOC ON + AUTORCC ON WIN32_EXECUTABLE ON MACOSX_BUNDLE ON ) -target_link_libraries(IfcViewerFull PRIVATE IfcViewer) +target_link_libraries(IfcViewerFull PRIVATE + IfcViewer + Qt${QT_VERSION}::Core + Qt${QT_VERSION}::Gui + Qt${QT_VERSION}::Svg + Qt${QT_VERSION}::Widgets +) install(TARGETS IfcViewerFull EXPORT ${IFCOPENSHELL_EXPORT_TARGETS}) ifcopenshell_deploy_qt_runtime(IfcViewerFull) diff --git a/src/interface/ElementRegistry.cpp b/src/ifcviewer-full/ElementRegistry.cpp similarity index 100% rename from src/interface/ElementRegistry.cpp rename to src/ifcviewer-full/ElementRegistry.cpp diff --git a/src/interface/ElementRegistry.h b/src/ifcviewer-full/ElementRegistry.h similarity index 100% rename from src/interface/ElementRegistry.h rename to src/ifcviewer-full/ElementRegistry.h diff --git a/src/ifcviewer-full/FederationSettingsDialog.cpp b/src/ifcviewer-full/FederationSettingsDialog.cpp deleted file mode 100644 index aa55eddeba..0000000000 --- a/src/ifcviewer-full/FederationSettingsDialog.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/******************************************************************************** - * * - * 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 "FederationSettingsDialog.h" -#include "Federation.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -// Common length units users will pick. itemData() carries -// (prefix, name) — empty prefix for non-prefixed or conversion-based units. -struct UnitChoice { - const char* label; - const char* prefix; - const char* name; -}; -const UnitChoice kUnitChoices[] = { - { "Metres (m)", "", "METRE" }, - { "Millimetres (mm)", "MILLI", "METRE" }, - { "Centimetres (cm)", "CENTI", "METRE" }, - { "Kilometres (km)", "KILO", "METRE" }, - { "Feet (ft)", "", "foot" }, - { "Inches (in)", "", "inch" }, - { "Yards (yd)", "", "yard" }, - { "Miles (mi)", "", "mile" }, -}; - -} // namespace - -FederationSettingsDialog::FederationSettingsDialog(Federation* federation, - QWidget* parent) - : QDialog(parent), federation_(federation) -{ - setWindowTitle("Federation Settings"); - setupUi(); -} - -void FederationSettingsDialog::setupUi() { - auto* root = new QVBoxLayout(this); - - // Unit - { - auto* group = new QGroupBox("Federation Unit", this); - auto* form = new QFormLayout(group); - unit_combo_ = new QComboBox(group); - for (const auto& uc : kUnitChoices) { - QStringList data; - data << QString::fromUtf8(uc.prefix) << QString::fromUtf8(uc.name); - unit_combo_->addItem(uc.label, data); - } - unit_combo_->setToolTip( - "All Federated False Origin and Model Transformation numbers " - "are interpreted in this unit. Changing it after data has been " - "entered re-interprets the numbers — same physical location " - "expressed in the new unit."); - form->addRow("Unit", unit_combo_); - root->addWidget(group); - } - - // Federated False Origin - { - auto* group = new QGroupBox("Federated False Origin", this); - group->setToolTip( - "Nominate a point in the federation that becomes the new (0,0,0). " - "Optional Z-rotation aligns north for the federation."); - auto* form = new QFormLayout(group); - - auto build_xyz = [this](QDoubleSpinBox*& sb) { - sb = new QDoubleSpinBox(); - sb->setRange(-1e12, 1e12); - sb->setDecimals(6); - sb->setSingleStep(1.0); - }; - build_xyz(xyz_x_); - build_xyz(xyz_y_); - build_xyz(xyz_z_); - form->addRow("X", xyz_x_); - form->addRow("Y", xyz_y_); - form->addRow("Z", xyz_z_); - - rz_deg_ = new QDoubleSpinBox(); - rz_deg_->setRange(-360.0, 360.0); - rz_deg_->setDecimals(6); - rz_deg_->setSingleStep(1.0); - form->addRow("Z rotation (°)", rz_deg_); - root->addWidget(group); - } - - auto* button_box = new QDialogButtonBox( - QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - root->addWidget(button_box); - connect(button_box, &QDialogButtonBox::accepted, this, - &FederationSettingsDialog::onAccepted); - connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); -} - -void FederationSettingsDialog::showEvent(QShowEvent* event) { - syncFromFederation(); - QDialog::showEvent(event); -} - -void FederationSettingsDialog::syncFromFederation() { - if (!federation_) return; - - const auto& cfg = federation_->config(); - int idx = -1; - for (int i = 0; i < unit_combo_->count(); ++i) { - const QStringList data = unit_combo_->itemData(i).toStringList(); - if (data.size() == 2 && - data[0].toStdString() == cfg.unit_prefix && - data[1].toStdString() == cfg.unit_name) { - idx = i; - break; - } - } - if (idx >= 0) { - unit_combo_->setCurrentIndex(idx); - } else { - // Federation has a unit that isn't in the common list — fall back to - // the first entry. Saving keeps whatever the user picks; the - // original federation unit is preserved unless they hit Ok. - unit_combo_->setCurrentIndex(0); - } - - const auto& origin = federation_->federatedFalseOrigin(); - xyz_x_->setValue(origin.xyz.x()); - xyz_y_->setValue(origin.xyz.y()); - xyz_z_->setValue(origin.xyz.z()); - rz_deg_->setValue(origin.rz_deg); -} - -void FederationSettingsDialog::onAccepted() { - if (!federation_) { accept(); return; } - - const QStringList data = unit_combo_->currentData().toStringList(); - FederationConfig cfg; - if (data.size() == 2) { - cfg.unit_prefix = data[0].toStdString(); - cfg.unit_name = data[1].toStdString(); - } - federation_->setConfig(cfg); - - FederatedFalseOrigin origin; - origin.xyz = Eigen::Vector3d(xyz_x_->value(), xyz_y_->value(), xyz_z_->value()); - origin.rz_deg = rz_deg_->value(); - federation_->setFederatedFalseOrigin(origin); - - accept(); -} diff --git a/src/ifcviewer-full/FederationSettingsDialog.h b/src/ifcviewer-full/FederationSettingsDialog.h deleted file mode 100644 index 04cf8ebe3b..0000000000 --- a/src/ifcviewer-full/FederationSettingsDialog.h +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************** - * * - * 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 FEDERATIONSETTINGSDIALOG_H -#define FEDERATIONSETTINGSDIALOG_H - -#include - -QT_BEGIN_NAMESPACE -class QComboBox; -class QDoubleSpinBox; -QT_END_NAMESPACE - -class Federation; - -// Edits federation-wide state: the display unit (used to interpret all -// FederatedFalseOrigin / ModelTransformation numeric inputs) and the -// FederatedFalseOrigin (XYZ + Z-axis rotation in that unit). On Ok, -// calls Federation::setConfig + setFederatedFalseOrigin, which fire the -// granular Federation signals and trigger the viewport to recompose. -class FederationSettingsDialog : public QDialog { - Q_OBJECT -public: - explicit FederationSettingsDialog(Federation* federation, QWidget* parent = nullptr); - -protected: - void showEvent(QShowEvent* event) override; - -private: - void setupUi(); - void syncFromFederation(); - void onAccepted(); - - Federation* federation_ = nullptr; - - // The combobox encodes (prefix, name) pairs in user data; common length - // units are listed. itemData(idx) returns a QStringList { prefix, name }. - QComboBox* unit_combo_ = nullptr; - QDoubleSpinBox* xyz_x_ = nullptr; - QDoubleSpinBox* xyz_y_ = nullptr; - QDoubleSpinBox* xyz_z_ = nullptr; - QDoubleSpinBox* rz_deg_ = nullptr; -}; - -#endif diff --git a/src/interface/InterfaceSettings.cpp b/src/ifcviewer-full/InterfaceSettings.cpp similarity index 100% rename from src/interface/InterfaceSettings.cpp rename to src/ifcviewer-full/InterfaceSettings.cpp diff --git a/src/interface/InterfaceSettings.h b/src/ifcviewer-full/InterfaceSettings.h similarity index 100% rename from src/interface/InterfaceSettings.h rename to src/ifcviewer-full/InterfaceSettings.h diff --git a/src/ifcviewer-full/MainWindow.cpp b/src/ifcviewer-full/MainWindow.cpp index 61b379ce79..a85a6197df 100644 --- a/src/ifcviewer-full/MainWindow.cpp +++ b/src/ifcviewer-full/MainWindow.cpp @@ -1,3 +1,4 @@ +// This file was generated with the assistance of an AI coding tool. /******************************************************************************** * * * This file is part of IfcOpenShell. * @@ -18,1390 +19,513 @@ ********************************************************************************/ #include "MainWindow.h" -#include "AppSettings.h" -#include "Federation.h" -#include "FederationSettingsDialog.h" -#include "Measurement.h" -#include "ModelTransformationDialog.h" -#include "SettingsWindow.h" -#include "LodBuilder.h" -#include "SidecarCache.h" -#include -#include +#include "../ifcviewer/AppSettings.h" +#include "../ifcviewer/Federation.h" +#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 "modules/models/Controller.h" +#include "modules/todo/Panel.h" +#include "modules/models/View.h" +#include "modules/models/Panel.h" +#include "modules/project/Controller.h" +#include "modules/properties/View.h" +#include "modules/properties/Panel.h" +#include "modules/settings/Dialog.h" +#include "modules/spatial_hierarchy/View.h" +#include "modules/spatial_hierarchy/Panel.h" +#include "modules/viewport/Controller.h" +#include "modules/viewport/Panel.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ifcinterface::shell { MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { - setupUi(); - setupMenus(); - federation_ = new Federation(this); - - // Federation -> viewport: granular signals so we recompose only what's - // affected. Each handler reads the current federation state, composes - // the matrix, and pushes to the viewport. - connect(federation_, &Federation::federatedFalseOriginChanged, - this, &MainWindow::applyFederatedFalseOriginToViewport); - connect(federation_, &Federation::configChanged, this, [this]() { - // Federation unit changed — both stage 3 (uses fed unit) and every - // model's stage 4 (b/pivot are in fed units) need recomposing. - applyFederatedFalseOriginToViewport(); - for (const auto& kv : fed_id_to_model_id_) { - applyModelTransformationToViewport(kv.second); - } - }); - 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()) { - applyModelTransformationToViewport(it->second); - } - }); - connect(federation_, &Federation::modelVisibilityChanged, - this, [this](const QString& fed_id, bool /*visible*/) { - auto it = fed_id_to_model_id_.find(fed_id); - if (it != fed_id_to_model_id_.end()) { - applyModelVisibilityToViewport(it->second); - } - }); - - connect(federation_, &Federation::modelGroupChanged, - this, [this](const QString& fed_id, const QString& /*group_id*/) { - auto it = fed_id_to_model_id_.find(fed_id); - if (it == fed_id_to_model_id_.end()) return; - reparentModelTreeRoot(it->second); - // Effective visibility may have flipped because the new group's - // chain visibility differs from the old one. - applyModelVisibilityToViewport(it->second); - }); - - connect(federation_, &Federation::groupAdded, - this, [this](const QString& group_id) { - ensureGroupTreeItem(group_id); - reparentGroupTreeItem(group_id); - refreshGroupRowAppearance(group_id); - }); - - connect(federation_, &Federation::groupChanged, - this, [this](const QString& group_id) { - const Federation::Group* g = federation_->findGroupById(group_id); - if (!g) return; - auto it = group_tree_items_.find(group_id); - if (it == group_tree_items_.end()) return; - it->second->setText(0, g->display_name); - reparentGroupTreeItem(group_id); - // Reparenting a group changes the chain visibility for the moved - // group + its descendants — refresh both rows and viewport. - for (const QString& gid : descendantGroupIds(group_id)) { - refreshGroupRowAppearance(gid); - } - applyVisibilityCascadeFromGroup(group_id); - }); - - connect(federation_, &Federation::groupVisibilityChanged, - this, [this](const QString& group_id, bool /*visible*/) { - for (const QString& gid : descendantGroupIds(group_id)) { - refreshGroupRowAppearance(gid); - } - applyVisibilityCascadeFromGroup(group_id); - }); - - connect(federation_, &Federation::groupRemoved, - this, [this](const QString& group_id) { - auto it = group_tree_items_.find(group_id); - if (it == group_tree_items_.end()) return; - // By now, the federation has fired groupChanged / modelGroupChanged - // for every direct child, and our slots have moved them out from - // under this item, so deleting it just removes the (now empty) - // group row. - delete it->second; - group_tree_items_.erase(it); - }); - + 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(); + }); + connect(session_state_, &ifcinterface::SessionState::modelsChanged, + this, &MainWindow::updateWindowTitle); + connect(session_state_, &ifcinterface::SessionState::projectOpened, + this, [this](const QString&) { updateWindowTitle(); }); + connect(session_state_, &ifcinterface::SessionState::projectSaved, + this, [this](const QString&) { updateWindowTitle(); }); + connect(session_state_, &ifcinterface::SessionState::projectReset, + this, &MainWindow::updateWindowTitle); + setupChrome(); + setupViewport(); + setupPanels(); + setupStatus(); + setupLoader(); + setupRibbon(); + resize(1720, 980); +} + +void MainWindow::setupChrome() { + setObjectName("appWindow"); + setDockOptions(QMainWindow::AllowNestedDocks | + QMainWindow::AllowTabbedDocks | + QMainWindow::GroupedDragging); + + auto bind_shortcut = [this](const QKeySequence& sequence, auto fn) { + auto* shortcut = new QShortcut(sequence, this); + shortcut->setContext(Qt::WindowShortcut); + connect(shortcut, &QShortcut::activated, this, fn); + }; + bind_shortcut(QKeySequence("Ctrl+Shift+L"), [this]() { + viewport_controller_->toggleDistanceMode(); + }); + bind_shortcut(QKeySequence("Ctrl+Shift+A"), [this]() { + viewport_controller_->toggleAreaMode(); + }); + bind_shortcut(QKeySequence("Ctrl+Shift+V"), [this]() { + viewport_controller_->toggleVolumeMode(); + }); + bind_shortcut(QKeySequence(Qt::SHIFT | Qt::Key_F), [this]() { + viewport_controller_->setFlyMode(); + }); + bind_shortcut(QKeySequence(Qt::Key_K), [this]() { + viewport_controller_->toggleSectionMode(); + }); + bind_shortcut(QKeySequence(Qt::SHIFT | Qt::Key_K), [this]() { + viewport_controller_->clearSectionPlanes(); + }); + bind_shortcut(QKeySequence(Qt::Key_H), [this]() { + viewport_controller_->hideSelectedElements(); + }); + bind_shortcut(QKeySequence(Qt::SHIFT | Qt::Key_H), [this]() { + viewport_controller_->isolateSelectedElements(); + }); + bind_shortcut(QKeySequence(Qt::ALT | Qt::Key_H), [this]() { + viewport_controller_->showAllElements(); + }); + updateWindowTitle(); +} + +QToolButton* MainWindow::makePanelToggle(const QString& text, QDockWidget* dock) { + auto* button = components::buttons::makeButton(text, ":/icons/sidebar-expand.svg", this); + button->setCheckable(true); + button->setChecked(dock->isVisible()); + connect(button, &QToolButton::toggled, dock, [dock](bool checked) { + dock->setVisible(checked); + if (checked) dock->raise(); + }); + connect(dock, &QDockWidget::visibilityChanged, button, [button](bool visible) { + const QSignalBlocker blocker(button); + button->setChecked(visible); + }); + return button; +} + +QWidget* MainWindow::buildHomeRibbonPage() { + auto* page = new QFrame(this); + page->setObjectName("ribbonPage"); + auto* row = new QHBoxLayout(page); + row->setContentsMargins(6, 4, 6, 4); + row->setSpacing(0); + + auto* new_project = components::buttons::makeButton("New Project", ":/icons/plus-square.svg", this); + connect(new_project, &QToolButton::clicked, this, [this]() { + project_controller_->newProject(); + }); + auto* open_project = components::buttons::makeButton("Open Project", ":/icons/download-square.svg", this); + connect(open_project, &QToolButton::clicked, this, [this]() { + project_controller_->openProject(); + }); + auto* open_cloud = components::buttons::makeButton("Open Cloud", ":/icons/cloud-square.svg", this); + connect(open_cloud, &QToolButton::clicked, this, [this]() { + session_state_->setStatusMessage("Project", "Open Cloud Project coming soon"); + }); + auto* open_recent = components::buttons::makeButton("Open Recent", ":/icons/clock-rotate-right.svg", this); + connect(open_recent, &QToolButton::clicked, this, [this]() { + session_state_->setStatusMessage("Project", "Open Recent coming soon"); + }); + auto* save_project = components::buttons::makeButton("Save Project", ":/icons/floppy-disk.svg", this); + connect(save_project, &QToolButton::clicked, this, [this]() { + project_controller_->saveProject(); + }); + auto* save_project_as = components::buttons::makeButton("Save As", ":/icons/floppy-disk-arrow-in.svg", this); + connect(save_project_as, &QToolButton::clicked, this, [this]() { + project_controller_->saveProjectAs(); }); - loader_ = new SceneLoader(viewport_, this); - connect(loader_, &SceneLoader::loadStarted, - this, &MainWindow::onLoadStarted); - connect(loader_, &SceneLoader::progressChanged, - this, &MainWindow::onLoadProgressChanged); - connect(loader_, &SceneLoader::sidecarElementsReady, - this, &MainWindow::onSidecarElementsReady); - connect(loader_, &SceneLoader::loadedFromSidecar, - this, &MainWindow::onLoadedFromSidecar); - connect(loader_, &SceneLoader::dataSourceReady, - this, &MainWindow::onDataSourceReady); - connect(loader_, &SceneLoader::streamedElementsReady, - this, &MainWindow::onStreamedElementsReady); - connect(loader_, &SceneLoader::loadedFromStream, - this, &MainWindow::onLoadedFromStream); - connect(loader_, &SceneLoader::loadCancelled, - this, &MainWindow::onLoadCancelled); - connect(loader_, &SceneLoader::loadError, - this, &MainWindow::onLoadError); - connect(loader_, &SceneLoader::allLoadsFinished, - this, &MainWindow::onAllLoadsFinished); + auto* add_model = components::buttons::makeButton("Add Model", ":/icons/cube.svg", this); + connect(add_model, &QToolButton::clicked, this, [this]() { + models_controller_->addFiles(); + }); + auto* sync_models = components::buttons::makeButton("Sync Models", ":/icons/refresh-double.svg", this); + connect(sync_models, &QToolButton::clicked, this, [this]() { + session_state_->setStatusMessage("Models", "Sync models coming soon"); + }); - connect(viewport_, &ViewportWindow::frameStatsUpdated, this, + auto* settings_button = components::buttons::makeButton("Settings", ":/icons/settings.svg", this); + connect(settings_button, &QToolButton::clicked, this, [this]() { + modules::settings::SettingsDialog dialog(this); + dialog.exec(); + }); + + row->addWidget(components::buttons::makeButtonGroup("PROJECT", {new_project, open_project, open_cloud, open_recent, save_project, save_project_as}, this)); + row->addWidget(components::buttons::makeButtonGroup("MODELS", {add_model, sync_models}, this)); + row->addWidget(components::buttons::makeButtonGroup("SETTINGS", {settings_button}, this)); + row->addStretch(1); + return page; +} + +QWidget* MainWindow::buildNavigateRibbonPage() { + auto* page = new QFrame(this); + page->setObjectName("ribbonPage"); + auto* row = new QHBoxLayout(page); + row->setContentsMargins(2, 4, 2, 4); + row->setSpacing(0); + + auto* set_home = components::buttons::makeButton("Set Home", ":/icons/home.svg", this); + connect(set_home, &QToolButton::clicked, this, [this]() { + viewport_controller_->setHomeView(); + }); + auto* go_home = components::buttons::makeButton("Go Home", ":/icons/home-alt.svg", this); + connect(go_home, &QToolButton::clicked, this, [this]() { + viewport_controller_->goHomeView(); + }); + auto* view_all = components::buttons::makeButton("View All", ":/icons/cube-scan.svg", this); + connect(view_all, &QToolButton::clicked, this, [this]() { + if (viewport_widget_) viewport_widget_->viewport()->viewAll(); + }); + auto* view_selected = components::buttons::makeButton("View Selected", ":/icons/cube-scan-solid.svg", this); + connect(view_selected, &QToolButton::clicked, this, [this]() { + viewport_controller_->focusSelectedObject(); + }); + + auto* plan_view = components::buttons::makeButton("Plan", ":/icons/planimetry.svg", this); + connect(plan_view, &QToolButton::clicked, this, [this]() { + if (viewport_widget_) viewport_widget_->viewport()->setStandardView(90.0f, 90.0f); + }); + auto* front_view = components::buttons::makeButton("Front", ":/icons/city.svg", this); + connect(front_view, &QToolButton::clicked, this, [this]() { + if (viewport_widget_) viewport_widget_->viewport()->setStandardView(0.0f, 0.0f); + }); + auto* side_view = components::buttons::makeButton("Side", ":/icons/building.svg", this); + connect(side_view, &QToolButton::clicked, this, [this]() { + if (viewport_widget_) viewport_widget_->viewport()->setStandardView(90.0f, 0.0f); + }); + auto* align_object = components::buttons::makeButton("Align Object", ":/icons/cellar.svg", this); + connect(align_object, &QToolButton::clicked, this, [this]() { + session_state_->setStatusMessage("Orientation", "Align to object coming soon"); + }); + auto* projection_button = components::buttons::makeButton("Perspective", ":/icons/perspective-view.svg", this); + connect(projection_button, &QToolButton::clicked, this, [this, projection_button]() { + if (!viewport_widget_) return; + viewport_widget_->viewport()->toggleProjection(); + projection_button->setText( + viewport_widget_->viewport()->projectionOrtho() ? "Ortho" : "Perspective"); + }); + + auto* fly_mode = components::buttons::makeButton("Fly", ":/icons/drone.svg", this); + connect(fly_mode, &QToolButton::clicked, this, [this]() { + viewport_controller_->setFlyMode(); + }); + auto* section_mode = components::buttons::makeButton("Section", ":/icons/cube-cut-with-curve.svg", this); + connect(section_mode, &QToolButton::clicked, this, [this]() { + viewport_controller_->toggleSectionMode(); + }); + + row->addWidget(components::buttons::makeButtonGroup("CAMERA", {set_home, go_home, view_all, view_selected}, this)); + row->addWidget(components::buttons::makeButtonGroup("ORIENTATION", {plan_view, front_view, side_view, align_object, projection_button}, this)); + row->addWidget(components::buttons::makeButtonGroup("MODE", {fly_mode, section_mode}, this)); + row->addStretch(1); + return page; +} + +QWidget* MainWindow::buildInspectRibbonPage() { + auto* page = new QFrame(this); + page->setObjectName("ribbonPage"); + auto* row = new QHBoxLayout(page); + row->setContentsMargins(2, 4, 2, 4); + row->setSpacing(0); + + auto* hide_selected = components::buttons::makeButton("Hide", ":/icons/eye-closed.svg", this); + connect(hide_selected, &QToolButton::clicked, this, [this]() { + viewport_controller_->hideSelectedElements(); + }); + auto* isolate_selected = components::buttons::makeButton("Isolate", ":/icons/eye-solid.svg", this); + connect(isolate_selected, &QToolButton::clicked, this, [this]() { + viewport_controller_->isolateSelectedElements(); + }); + auto* show_all = components::buttons::makeButton("Show All", ":/icons/eye.svg", this); + connect(show_all, &QToolButton::clicked, this, [this]() { + viewport_controller_->showAllElements(); + }); + auto* invert_selection = components::buttons::makeButton("Invert", ":/icons/intersect.svg", this); + connect(invert_selection, &QToolButton::clicked, this, [this]() { + viewport_controller_->invertSelection(); + }); + + auto* distance = components::buttons::makeButton("Distance", ":/icons/select-edge3d.svg", this); + connect(distance, &QToolButton::clicked, this, [this]() { + viewport_controller_->toggleDistanceMode(); + }); + auto* area = components::buttons::makeButton("Area", ":/icons/select-face3d.svg", this); + connect(area, &QToolButton::clicked, this, [this]() { + viewport_controller_->toggleAreaMode(); + }); + auto* volume = components::buttons::makeButton("Volume", ":/icons/select-point3d.svg", this); + connect(volume, &QToolButton::clicked, this, [this]() { + viewport_controller_->toggleVolumeMode(); + }); + + row->addWidget(components::buttons::makeButtonGroup("SELECTION", {hide_selected, isolate_selected, show_all, invert_selection}, this)); + row->addWidget(components::buttons::makeButtonGroup("MEASURE", {distance, area, volume}, this)); + row->addStretch(1); + return page; +} + +QWidget* MainWindow::buildPanelsRibbonPage() { + auto* page = new QFrame(this); + page->setObjectName("ribbonPage"); + auto* row = new QHBoxLayout(page); + row->setContentsMargins(2, 4, 2, 4); + row->setSpacing(0); + + row->addWidget(components::buttons::makeButtonGroup("DATA", { + makePanelToggle("Models", models_panel_), + makePanelToggle("Spatial", spatial_panel_), + makePanelToggle("Layers", layers_panel_), + makePanelToggle("Properties", properties_panel_) + }, this)); + row->addWidget(components::buttons::makeButtonGroup("QUERY", { + makePanelToggle("Views", stored_views_panel_), + makePanelToggle("Search", search_panel_), + makePanelToggle("Sheets", spreadsheet_panel_), + makePanelToggle("Audit", audit_panel_) + }, this)); + row->addWidget(components::buttons::makeButtonGroup("COLLABORATE", { + makePanelToggle("Clash", clash_panel_), + makePanelToggle("Issues", issues_panel_) + }, this)); + row->addStretch(1); + return page; +} + +void MainWindow::setupRibbon() { + auto* shell = new QFrame(this); + + auto* shell_layout = new QVBoxLayout(shell); + shell_layout->setContentsMargins(0, 0, 0, 0); + shell_layout->setSpacing(0); + + ribbon_tabs_ = new components::TabBar(shell); + ribbon_tabs_->addTab("Home"); + ribbon_tabs_->addTab("Navigate"); + ribbon_tabs_->addTab("Inspect"); + ribbon_tabs_->addTab("Panels"); + ribbon_tabs_->setCurrentIndex(0); + + auto* ribbon_band = new QFrame(shell); + ribbon_band->setObjectName("ribbonBand"); + auto* band_layout = new QVBoxLayout(ribbon_band); + band_layout->setContentsMargins(0, 0, 0, 0); + band_layout->setSpacing(0); + + ribbon_pages_ = new QStackedWidget(ribbon_band); + ribbon_pages_->addWidget(buildHomeRibbonPage()); + ribbon_pages_->addWidget(buildNavigateRibbonPage()); + ribbon_pages_->addWidget(buildInspectRibbonPage()); + ribbon_pages_->addWidget(buildPanelsRibbonPage()); + + band_layout->addWidget(ribbon_pages_); + shell_layout->addWidget(ribbon_tabs_); + shell_layout->addWidget(ribbon_band); + + connect(ribbon_tabs_, &components::TabBar::currentChanged, + ribbon_pages_, &QStackedWidget::setCurrentIndex); + + setMenuWidget(shell); +} + +void MainWindow::setupViewport() { + viewport_widget_ = new modules::viewport::ViewportPanel(this); + setCentralWidget(viewport_widget_); +} + +void MainWindow::setupPanels() { + models_panel_ = new modules::models::ModelsPanel(this); + spatial_panel_ = new modules::spatial_hierarchy::SpatialHierarchyPanel(this); + properties_panel_ = new modules::properties::PropertiesPanel(this); + + models_controller_ = new modules::models::ModelsPanelController( + this, models_panel_, session_state_, viewport_widget_->viewport(), this); + models_view_ = new modules::models::ModelsPanelView(models_panel_, session_state_, this); + spatial_view_ = new modules::spatial_hierarchy::SpatialHierarchyPanelView(spatial_panel_, session_state_, this); + properties_view_ = new modules::properties::PropertiesPanelView(properties_panel_, session_state_, this); + + layers_panel_ = new components::Panel("Layers", new modules::todo::TodoPanel("Layers", this), this); + stored_views_panel_ = new components::Panel( + "Stored Views", new modules::todo::TodoPanel("Stored Views", this), this); + search_panel_ = new components::Panel( + "Search and Query", new modules::todo::TodoPanel("Search and Query", this), this); + spreadsheet_panel_ = new components::Panel( + "Spreadsheet", new modules::todo::TodoPanel("Spreadsheet", this), this); + audit_panel_ = new components::Panel("Audit", new modules::todo::TodoPanel("Audit", this), this); + clash_panel_ = new components::Panel("Clash", new modules::todo::TodoPanel("Clash", this), this); + issues_panel_ = new components::Panel("Issues", new modules::todo::TodoPanel("Issues", this), this); + + addDockWidget(Qt::LeftDockWidgetArea, models_panel_); + addDockWidget(Qt::LeftDockWidgetArea, spatial_panel_); + splitDockWidget(models_panel_, spatial_panel_, Qt::Vertical); + + addDockWidget(Qt::RightDockWidgetArea, properties_panel_); + addDockWidget(Qt::RightDockWidgetArea, layers_panel_); + addDockWidget(Qt::RightDockWidgetArea, stored_views_panel_); + addDockWidget(Qt::RightDockWidgetArea, search_panel_); + addDockWidget(Qt::RightDockWidgetArea, spreadsheet_panel_); + addDockWidget(Qt::RightDockWidgetArea, audit_panel_); + addDockWidget(Qt::RightDockWidgetArea, clash_panel_); + addDockWidget(Qt::RightDockWidgetArea, issues_panel_); + + tabifyDockWidget(properties_panel_, layers_panel_); + tabifyDockWidget(layers_panel_, stored_views_panel_); + tabifyDockWidget(stored_views_panel_, search_panel_); + tabifyDockWidget(search_panel_, spreadsheet_panel_); + tabifyDockWidget(spreadsheet_panel_, audit_panel_); + tabifyDockWidget(audit_panel_, clash_panel_); + tabifyDockWidget(clash_panel_, issues_panel_); + properties_panel_->raise(); + + layers_panel_->hide(); + stored_views_panel_->hide(); + search_panel_->hide(); + spreadsheet_panel_->hide(); + audit_panel_->hide(); + clash_panel_->hide(); + issues_panel_->hide(); + + resizeDocks({models_panel_, properties_panel_}, {290, 330}, Qt::Horizontal); + resizeDocks({models_panel_, spatial_panel_}, {280, 240}, Qt::Vertical); +} + +void MainWindow::setupStatus() { + status_mode_label_ = new QLabel("Ready", this); + status_selection_label_ = new QLabel("No selection", this); + status_perf_label_ = new QLabel(this); + status_progress_bar_ = new QProgressBar(this); + status_perf_label_->setVisible(AppSettings::instance().showStats()); + status_progress_bar_->setMaximumWidth(200); + status_progress_bar_->setVisible(false); + + statusBar()->setSizeGripEnabled(false); + statusBar()->addWidget(status_mode_label_); + statusBar()->addWidget(status_selection_label_, 1); + statusBar()->addPermanentWidget(status_perf_label_); + statusBar()->addPermanentWidget(status_progress_bar_); + + connect(&AppSettings::instance(), &AppSettings::showStatsChanged, this, [this](bool show) { + 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_); + models_controller_->bindLoader(loader_); + viewport_controller_ = new modules::viewport::ViewportController( + session_state_, viewport_widget_->viewport(), this); + project_controller_ = new modules::project::ProjectController( + this, federation_, session_state_, element_registry_, + viewport_widget_->viewport(), models_controller_, viewport_controller_, this); + + connect(loader_, &SceneLoader::loadStarted, this, + [this](uint32_t /*mid*/, const QString& /*display_name*/) { + status_progress_bar_->setValue(0); + status_progress_bar_->setVisible(true); + }); + connect(loader_, &SceneLoader::progressChanged, this, + [this](int percent) { + status_progress_bar_->setValue(percent); + }); + + auto hide_progress = [this]() { + status_progress_bar_->setVisible(false); + }; + connect(loader_, &SceneLoader::loadedFromSidecar, this, + [hide_progress](uint32_t /*mid*/, qint64 /*elapsed_ms*/) { + hide_progress(); + }); + connect(loader_, &SceneLoader::loadedFromStream, this, + [hide_progress](uint32_t /*mid*/, qint64 /*elapsed_ms*/) { + hide_progress(); + }); + connect(loader_, &SceneLoader::loadCancelled, this, + [hide_progress](uint32_t /*mid*/) { + hide_progress(); + }); + connect(loader_, &SceneLoader::loadError, this, + [hide_progress](uint32_t /*mid*/, const QString& /*message*/) { + hide_progress(); + }); + + connect(viewport_widget_->viewport(), &ViewportWindow::frameStatsUpdated, this, [this](const ViewportWindow::FrameStats& s) { - if (!stats_label_->isVisible()) return; - stats_label_->setText( - QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 gl_draws (%8 sub)") + if (!status_perf_label_->isVisible()) return; + status_perf_label_->setText( + QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 draws") .arg(s.fps, 0, 'f', 1) .arg(s.frame_time_ms, 0, 'f', 1) .arg(s.visible_objects) .arg(s.total_objects) .arg(s.visible_triangles) .arg(s.total_triangles) - .arg(s.gl_draw_calls) - .arg(s.indirect_sub_draws)); + .arg(s.gl_draw_calls)); }); - - connect(&AppSettings::instance(), &AppSettings::showStatsChanged, this, [this](bool show) { - stats_label_->setVisible(show); - if (!show) stats_label_->clear(); + connect(viewport_widget_->viewport(), &ViewportWindow::objectPicked, + this, [this](uint32_t object_id) { + session_state_->setSelectedObjectId(object_id); + session_state_->notifySelectionChanged(); }); - - updateWindowTitle(); - resize(1400, 900); -} - -MainWindow::~MainWindow() = default; - -void MainWindow::setupUi() { - viewport_ = new ViewportWindow(); - viewport_container_ = QWidget::createWindowContainer(viewport_, this); - viewport_container_->setMinimumSize(400, 300); - viewport_container_->setFocusPolicy(Qt::StrongFocus); - setCentralWidget(viewport_container_); - - connect(viewport_, &ViewportWindow::objectPicked, this, &MainWindow::onObjectPicked); - connect(viewport_, &ViewportWindow::surfacePickedInTool, this, - [this](int x, int y, int modifiers) { - const bool alt = (modifiers & Qt::AltModifier) != 0; - switch (viewport_->toolMode()) { - case ViewportWindow::ToolMode::Area: - area_measurement_.onPick(*viewport_, x, y, alt); - viewport_->setHudText(QString("Area: %1 m² (%2 tris)") - .arg(area_measurement_.totalArea(), 0, 'f', 4) - .arg(area_measurement_.triangleCount())); - break; - case ViewportWindow::ToolMode::Length: - length_measurement_.onPick(*viewport_, x, y, alt); - break; - case ViewportWindow::ToolMode::None: - break; - } - }); - connect(viewport_, &ViewportWindow::toolModeChanged, this, - [this](ViewportWindow::ToolMode mode) { - // Always clear both — the previous tool's accumulator/overlay - // shouldn't bleed into the next one. - area_measurement_.clear(*viewport_); - length_measurement_.clear(*viewport_); - switch (mode) { - case ViewportWindow::ToolMode::Area: - viewport_->setHudText("Area: 0.0000 m² (0 tris)"); - status_label_->setText("Area tool: LMB add, Alt+LMB single tri, click again to remove, Esc exits"); - break; - case ViewportWindow::ToolMode::Length: - viewport_->setHudText("Length tool: click first point"); - status_label_->setText("Length tool: LMB add point, Backspace remove last, Esc exits"); - break; - case ViewportWindow::ToolMode::Volume: - // Volume tool is passive — selection works as in None. The - // readout helper writes both HUD and per-object labels for - // whatever's currently selected, then keeps them in sync as - // the selection changes. - status_label_->setText("Volume tool: click / box-select objects, Esc exits"); - updateVolumeReadout(); - break; - case ViewportWindow::ToolMode::None: - viewport_->setHudText(QString()); - viewport_->setOverlayLabels({}); - status_label_->setText("Ready"); - break; - } - }); - connect(viewport_, &ViewportWindow::toolBackspacePressed, this, [this]() { - if (viewport_->toolMode() == ViewportWindow::ToolMode::Length) { - length_measurement_.removeLastPoint(*viewport_); - } - }); - - auto* tree_dock = new QDockWidget("Elements", this); - tree_dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); - element_tree_ = new QTreeWidget(); - element_tree_->setHeaderLabels({"Name", "Type", "GUID"}); - element_tree_->setColumnWidth(0, 200); - element_tree_->setColumnWidth(1, 120); - element_tree_->setSelectionMode(QAbstractItemView::SingleSelection); - element_tree_->setContextMenuPolicy(Qt::CustomContextMenu); - connect(element_tree_, &QTreeWidget::itemSelectionChanged, this, &MainWindow::onTreeSelectionChanged); - connect(element_tree_, &QTreeWidget::customContextMenuRequested, - this, &MainWindow::onTreeContextMenu); - tree_dock->setWidget(element_tree_); - addDockWidget(Qt::LeftDockWidgetArea, tree_dock); - - auto* prop_dock = new QDockWidget("Properties", this); - prop_dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); - property_table_ = new QTableWidget(); - property_table_->setColumnCount(2); - property_table_->setHorizontalHeaderLabels({"Property", "Value"}); - property_table_->horizontalHeader()->setStretchLastSection(true); - property_table_->setEditTriggers(QAbstractItemView::NoEditTriggers); - property_table_->setSelectionBehavior(QAbstractItemView::SelectRows); - prop_dock->setWidget(property_table_); - addDockWidget(Qt::RightDockWidgetArea, prop_dock); - - progress_bar_ = new QProgressBar(); - progress_bar_->setMaximumWidth(200); - progress_bar_->setVisible(false); - status_label_ = new QLabel("Ready"); - stats_label_ = new QLabel(); - stats_label_->setVisible(AppSettings::instance().showStats()); - statusBar()->addWidget(status_label_, 1); - statusBar()->addPermanentWidget(stats_label_); - statusBar()->addPermanentWidget(progress_bar_); -} - -void MainWindow::setupMenus() { - auto* file_menu = menuBar()->addMenu("&File"); - file_menu->addAction("&New Federation", - this, &MainWindow::onFederationNew, - QKeySequence::New); - file_menu->addAction("&Open Federation...", - this, &MainWindow::onFederationOpen, - QKeySequence::Open); - file_menu->addSeparator(); - file_menu->addAction("&Add Files...", - this, &MainWindow::onFileOpen, - QKeySequence("Ctrl+Shift+O")); - file_menu->addAction("Add &Database...", this, &MainWindow::onDatabaseOpen); - file_menu->addSeparator(); - file_menu->addAction("&Save Federation", - this, &MainWindow::onFederationSave, - QKeySequence::Save); - file_menu->addAction("Save Federation &As...", - this, &MainWindow::onFederationSaveAs, - QKeySequence::SaveAs); - file_menu->addAction("Federation Se&ttings...", - this, &MainWindow::onFederationSettings); - file_menu->addAction("&Model Transformations...", - this, &MainWindow::onModelTransformations); - file_menu->addSeparator(); - file_menu->addAction("&Settings...", this, &MainWindow::onFileSettings); - file_menu->addSeparator(); - file_menu->addAction("&Quit", QKeySequence::Quit, qApp, &QApplication::quit); - - auto* view_menu = menuBar()->addMenu("&View"); - // F frames the current selection. The viewport already binds F in its - // own keyPressEvent for the case where it has focus; this duplicate at - // window level is so the shortcut still fires when the tree, property - // table, or any other child widget has keyboard focus. - view_menu->addAction("&Frame Selected", this, [this]() { - viewport_->focusOnSelectedObject(); - }, QKeySequence(Qt::Key_F)); - view_menu->addAction("Print Selected &Coords", this, [this]() { - viewport_->printSelectedObjectCoords(); - }, QKeySequence("Ctrl+Shift+P")); - view_menu->addAction("&Measure Area", this, [this]() { - viewport_->toggleAreaTool(); - }, QKeySequence("Ctrl+Shift+A")); - view_menu->addAction("Measure &Length", this, [this]() { - viewport_->toggleLengthTool(); - }, QKeySequence("Ctrl+Shift+L")); - view_menu->addAction("Measure &Volume", this, [this]() { - viewport_->toggleVolumeTool(); - }, QKeySequence("Ctrl+Shift+V")); - view_menu->addSeparator(); - // Element visibility — H hides the current selection, Shift+H - // isolates it (hides everything else within visible models), Alt+H - // restores every element. Model-level hiding is independent and - // stays put. - view_menu->addAction("&Hide Selected", this, [this]() { - viewport_->hideSelectedElements(); - }, QKeySequence(Qt::Key_H)); - view_menu->addAction("&Isolate Selected", this, [this]() { - viewport_->isolateSelectedElements(); - }, QKeySequence(Qt::SHIFT | Qt::Key_H)); - view_menu->addAction("&Show All Elements", this, [this]() { - viewport_->showAllElements(); - }, QKeySequence(Qt::ALT | Qt::Key_H)); - view_menu->addSeparator(); - view_menu->addAction("Set &Home View", this, &MainWindow::onSetHomeView); - view_menu->addAction("&Go to Home View", this, &MainWindow::onGoHomeView); -} - -void MainWindow::onFileOpen() { - QStringList paths = QFileDialog::getOpenFileNames( - this, "Add IFC Files", QString(), - "IFC Files (*.ifc *.ifcxml *.ifczip);;" - "IFC Viewer Cache (*.ifcview);;" - "All Files (*)"); - if (!paths.isEmpty()) { - addFiles(paths); - } -} - -void MainWindow::onDatabaseOpen() { - QFileDialog dialog(this, "Add IFC Databases"); - dialog.setFileMode(QFileDialog::Directory); - dialog.setOption(QFileDialog::ShowDirsOnly, true); - dialog.setOption(QFileDialog::DontResolveSymlinks, true); - // Native dialogs only support single-directory selection — use Qt's - // dialog so we can flip the inner views into ExtendedSelection. - dialog.setOption(QFileDialog::DontUseNativeDialog, true); - if (auto* list = dialog.findChild("listView")) { - list->setSelectionMode(QAbstractItemView::ExtendedSelection); - } - if (auto* tree = dialog.findChild()) { - tree->setSelectionMode(QAbstractItemView::ExtendedSelection); - } - if (dialog.exec() != QDialog::Accepted) return; - QStringList paths = dialog.selectedFiles(); - if (!paths.isEmpty()) { - addFiles(paths); - } -} - -void MainWindow::onFileSettings() { - if (settings_ == nullptr) { - settings_ = new SettingsWindow(this); - } - settings_->open(); - settings_->activateWindow(); - settings_->raise(); -} - -void MainWindow::onFederationSettings() { - if (federation_settings_ == nullptr) { - federation_settings_ = new FederationSettingsDialog(federation_, this); - } - federation_settings_->open(); - federation_settings_->activateWindow(); - federation_settings_->raise(); -} - -void MainWindow::onModelTransformations() { - if (model_transformations_ == nullptr) { - model_transformations_ = new ModelTransformationDialog(federation_, this); - } - model_transformations_->open(); - model_transformations_->activateWindow(); - model_transformations_->raise(); -} - -void MainWindow::addFiles(const QStringList& paths) { - QStringList accepted_paths; - QStringList accepted_fed_ids; - for (const auto& p : paths) { - QString fed_id = federation_->addModel(p); - if (fed_id.isEmpty()) continue; // .ifcfed or empty path — silently skipped - accepted_paths << p; - accepted_fed_ids << fed_id; - } - loadModelsFromPaths(accepted_paths, accepted_fed_ids); - updateWindowTitle(); -} - -void MainWindow::loadModelsFromPaths(const QStringList& paths, - const QStringList& fed_ids) { - if (paths.isEmpty()) return; - auto ids = loader_->addFiles(paths); - for (int i = 0; i < paths.size() && i < static_cast(ids.size()); ++i) { - uint32_t mid = ids[i]; - const QString& fed_id = fed_ids[i]; - fed_id_to_model_id_[fed_id] = mid; - model_id_to_fed_id_[mid] = fed_id; - - QString display = QFileInfo(paths[i]).fileName(); - auto* root = new QTreeWidgetItem(); - root->setText(0, display); - root->setText(1, "IFC Model"); - root->setData(0, Qt::UserRole, static_cast(0)); - tree_roots_[mid] = root; - reparentModelTreeRoot(mid); - } -} - -bool MainWindow::openFederation(const QString& path) { - if (!confirmDiscardIfDirty()) return false; - - QStringList warnings; - QString err; - if (!federation_->load(path, &warnings, &err)) { - QMessageBox::warning(this, "Open Federation", - QString("Could not open federation:\n%1").arg(err)); - return false; - } - - clearScene(); - - // Materialise group tree items. allGroups() walks parents-before- - // children, so reparenting in the same pass always finds the parent's - // tree item ready. - for (const Federation::Group* g : federation_->allGroups()) { - ensureGroupTreeItem(g->id); - reparentGroupTreeItem(g->id); - refreshGroupRowAppearance(g->id); - } - - QStringList paths; - QStringList fed_ids; - QStringList missing; - for (const auto& m : federation_->models()) { - if (m.source_kind != "local") continue; // already warned by load() - if (!QFileInfo::exists(m.source_path)) { - missing << m.source_path; - continue; - } - paths << m.source_path; - fed_ids << m.id; - } - loadModelsFromPaths(paths, fed_ids); - - for (const auto& msg : missing) { - warnings << QString("Source not found, kept in federation: %1").arg(msg); - } - if (!warnings.isEmpty()) { - QMessageBox::warning(this, "Open Federation", - "Federation opened with warnings:\n\n" + warnings.join("\n")); - } - - federation_->markClean(); - updateWindowTitle(); - - // Push the federation's loaded FederatedFalseOrigin to the viewport. - // Per-model ModelTransformations get pushed as each model finishes - // loading, via applyCoordinateOperationToViewport. - applyFederatedFalseOriginToViewport(); - - 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); - } - return true; -} - -void MainWindow::onFederationNew() { - if (!confirmDiscardIfDirty()) return; - clearScene(); - federation_->clear(); - updateWindowTitle(); -} - -void MainWindow::onFederationOpen() { - QString path = QFileDialog::getOpenFileName( - this, "Open Federation", QString(), - "IFC Federation (*.ifcfed);;All Files (*)"); - if (path.isEmpty()) return; - openFederation(path); -} - -bool MainWindow::onFederationSave() { - if (federation_->filePath().isEmpty()) return onFederationSaveAs(); - QString err; - if (!federation_->save(federation_->filePath(), &err)) { - QMessageBox::warning(this, "Save Federation", - QString("Could not save federation:\n%1").arg(err)); - return false; - } - updateWindowTitle(); - return true; -} - -bool MainWindow::onFederationSaveAs() { - QString suggested = federation_->filePath(); - if (suggested.isEmpty()) suggested = "federation.ifcfed"; - QString path = QFileDialog::getSaveFileName( - this, "Save Federation As", suggested, - "IFC Federation (*.ifcfed);;All Files (*)"); - if (path.isEmpty()) return false; - if (!path.endsWith(".ifcfed", Qt::CaseInsensitive)) path += ".ifcfed"; - - QString err; - if (!federation_->save(path, &err)) { - QMessageBox::warning(this, "Save Federation", - QString("Could not save federation:\n%1").arg(err)); - return false; - } - updateWindowTitle(); - return true; -} - -void MainWindow::onSetHomeView() { - auto cs = viewport_->cameraState(); - Federation::HomeView hv; - hv.target = cs.target; - hv.distance = cs.distance; - hv.yaw = cs.yaw; - hv.pitch = cs.pitch; - federation_->setHomeView(hv); - updateWindowTitle(); -} - -void MainWindow::onGoHomeView() { - if (!federation_->hasHomeView()) { - status_label_->setText("No home view set for this federation."); - return; - } - const auto& hv = federation_->homeView(); - viewport_->setCamera(hv.target.x(), hv.target.y(), hv.target.z(), - hv.distance, hv.yaw, hv.pitch); -} - -void MainWindow::clearScene() { - while (!tree_roots_.empty()) { - uint32_t mid = tree_roots_.begin()->first; - viewport_->removeModel(mid); - loader_->removeModel(mid); - removeModelUi(mid); - } - fed_id_to_model_id_.clear(); - model_id_to_fed_id_.clear(); - for (auto& kv : group_tree_items_) delete kv.second; - group_tree_items_.clear(); -} - -bool MainWindow::confirmDiscardIfDirty() { - if (!federation_->isDirty()) return true; - auto ret = QMessageBox::question( - this, "Unsaved Federation", - "The current federation has unsaved changes. Save before continuing?", - QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, - QMessageBox::Save); - if (ret == QMessageBox::Cancel) return false; - if (ret == QMessageBox::Save) return onFederationSave(); - return true; // Discard } void MainWindow::updateWindowTitle() { - QString fed_path = federation_->filePath(); - if (fed_path.isEmpty() && federation_->models().empty()) { - setWindowTitle("IfcViewer"); - } else if (fed_path.isEmpty()) { - setWindowTitle("untitled[*] — IfcViewer"); + const QString project_path = federation_ ? federation_->filePath() : QString(); + if (project_path.isEmpty() && (!federation_ || federation_->models().empty())) { + setWindowTitle("IfcOpenShell Interface"); + } else if (project_path.isEmpty()) { + setWindowTitle("untitled[*] - IfcOpenShell Interface"); } else { - setWindowTitle(QFileInfo(fed_path).fileName() + "[*] — IfcViewer"); - } - setWindowModified(federation_->isDirty()); -} - -void MainWindow::closeEvent(QCloseEvent* event) { - if (confirmDiscardIfDirty()) event->accept(); - else event->ignore(); -} - -void MainWindow::onLoadStarted(uint32_t /*mid*/, QString display_name) { - progress_bar_->setValue(0); - progress_bar_->setVisible(true); - status_label_->setText("Loading: " + display_name); -} - -void MainWindow::onLoadProgressChanged(int percent) { - progress_bar_->setValue(percent); -} - -void MainWindow::appendElementToTree(uint32_t model_id, - uint32_t object_id, - int ifc_id, - int parent_ifc_id, - const std::string& guid, - const std::string& name, - const std::string& type) { - auto root_it = tree_roots_.find(model_id); - QTreeWidgetItem* parent_item = (root_it != tree_roots_.end()) ? root_it->second : nullptr; - - auto parent_obj_it = scoped_ifc_id_to_object_id_.find( - scopedKey(model_id, parent_ifc_id)); - if (parent_obj_it != scoped_ifc_id_to_object_id_.end()) { - auto tree_it = tree_items_.find(parent_obj_it->second); - if (tree_it != tree_items_.end()) { - parent_item = tree_it->second; - } - } - - QString display_name = QString::fromStdString(name); - if (display_name.isEmpty()) { - display_name = QString::fromStdString(type) + " #" + QString::number(ifc_id); - } - - auto* item = new QTreeWidgetItem(parent_item); - item->setText(0, display_name); - item->setText(1, QString::fromStdString(type)); - item->setText(2, QString::fromStdString(guid)); - item->setData(0, Qt::UserRole, object_id); - - tree_items_[object_id] = item; -} - -void MainWindow::onSidecarElementsReady(uint32_t mid, - std::vector elements, - std::string string_table) { - auto str = [&](uint32_t offset, uint32_t length) -> std::string { - if (length == 0 || offset + length > string_table.size()) return {}; - return string_table.substr(offset, length); - }; - - QElapsedTimer t; - t.start(); - element_tree_->setUpdatesEnabled(false); - - for (const auto& pe : elements) { - ElementInfo info; - info.object_id = pe.object_id; - info.model_id = pe.model_id; - info.ifc_id = pe.ifc_id; - info.parent_id = pe.parent_id; - info.guid = str(pe.guid_offset, pe.guid_length); - info.name = str(pe.name_offset, pe.name_length); - info.type = str(pe.type_offset, pe.type_length); - - element_map_[info.object_id] = info; - scoped_ifc_id_to_object_id_[scopedKey(info.model_id, info.ifc_id)] = info.object_id; - - appendElementToTree(info.model_id, info.object_id, info.ifc_id, - info.parent_id, info.guid, info.name, info.type); - } - - element_tree_->setUpdatesEnabled(true); - qDebug(" Tree build: %lld ms (%zu elements)", t.elapsed(), elements.size()); -} - -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(); - if (items.isEmpty()) return; - uint32_t object_id = items.first()->data(0, Qt::UserRole).toUInt(); - auto it = element_map_.find(object_id); - if (it != element_map_.end() && it->second.model_id == mid) { - populateProperties(object_id); + setWindowTitle(QFileInfo(project_path).fileName() + "[*] - IfcOpenShell Interface"); } } -void MainWindow::applyCoordinateOperationToViewport(uint32_t mid) { - Eigen::Matrix4d M = Eigen::Matrix4d::Identity(); - if (const ModelGeoref* gr = loader_->modelGeoref(mid)) { - if (gr->has_coordinate_operation) { - M = gr->coordinate_operation_meters; - } - } - viewport_->setModelCoordinateOperation(mid, M); - // ModelTransformation's compose can depend on the active - // CoordinateOperation (when ModelTransformation.a_frame == ModelLocal, - // a is lifted through stage 2), so re-push it whenever stage 2 changes. - applyModelTransformationToViewport(mid); -} - -void MainWindow::applyModelTransformationToViewport(uint32_t mid) { - Eigen::Matrix4d M = 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* m = federation_->findById(fed_it->second)) { - // ModelUnits + the active CoordinateOperation come from the - // ModelGeoref cache; defaults are safe (1.0, 1.0, identity) - // when the IFC file isn't yet available. - ModelUnits units; - Eigen::Matrix4d coord_op = Eigen::Matrix4d::Identity(); - if (const ModelGeoref* gr = loader_->modelGeoref(mid)) { - units = gr->units; - if (gr->has_coordinate_operation) { - coord_op = gr->coordinate_operation_meters; - } - } - M = composeModelTransformation( - m->model_transformation, federation_->config(), - units, coord_op); - } - } - viewport_->setModelTransformation(mid, M); -} - -void MainWindow::applyFederatedFalseOriginToViewport() { - const Eigen::Matrix4d M = composeFederatedFalseOrigin( - federation_->federatedFalseOrigin(), federation_->config()); - viewport_->setFederatedFalseOrigin(M); -} - -void MainWindow::maybeGuessFederatedFalseOrigin(uint32_t mid) { - // Only auto-guess for untitled federations. A saved .ifcfed carries its - // authoritative origin (even if that happens to be the default), so we - // never silently overwrite it when the user re-adds a model. - if (!federation_->filePath().isEmpty()) return; - - // Skip once the origin is non-default — either the user edited it, a - // previous batch already guessed, or a load-from-file populated it. - // For a multi-file batch this means whichever model finishes first - // anchors the federation; the rest see a non-default origin and skip. - const FederatedFalseOrigin& cur = federation_->federatedFalseOrigin(); - const FederatedFalseOrigin def; - if (cur.xyz != def.xyz || cur.rz_deg != def.rz_deg) return; - - const Eigen::Matrix4d* placement = loader_->firstPlacement(mid); - const ModelGeoref* gr = loader_->modelGeoref(mid); - if (placement == nullptr || gr == nullptr) return; - - const FederatedFalseOrigin guess = guessFederatedFalseOrigin( - *placement, *gr, federation_->config()); - federation_->setFederatedFalseOrigin(guess); -} - -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") - .arg(element_map_.size()) - .arg(loader_->modelCount()) - .arg(formatElapsed(elapsed_ms))); - - // Sidecar v11+ caches the CoordinateOperation, so SceneLoader has - // already populated modelGeoref by now — push CoordinateOperation + - // ModelTransformation immediately rather than waiting for the - // (possibly never-arriving) data-source load. - applyCoordinateOperationToViewport(mid); - applyModelVisibilityToViewport(mid); - maybeGuessFederatedFalseOrigin(mid); -} - -void MainWindow::onStreamedElementsReady(uint32_t /*mid*/, std::vector elements) { - for (const auto& info : elements) { - element_map_[info.object_id] = info; - scoped_ifc_id_to_object_id_[scopedKey(info.model_id, info.ifc_id)] = info.object_id; - appendElementToTree(info.model_id, info.object_id, info.ifc_id, - info.parent_id, info.guid, info.name, info.type); - } -} - -void MainWindow::writeSidecarForModel(uint32_t mid) { - SidecarData sd; - if (!viewport_->snapshotModel(mid, sd)) return; - - // Cache the model's CoordinateOperation alongside the geometry so a - // sidecar load doesn't need the IFC source just to apply georef. - if (const ModelGeoref* gr = loader_->modelGeoref(mid)) { - sd.has_coordinate_operation = gr->has_coordinate_operation ? 1 : 0; - Eigen::Map>( - sd.coordinate_operation_meters) = gr->coordinate_operation_meters; - sd.project_length_to_meters = gr->units.project_length_to_meters; - sd.map_unit_to_meters = gr->units.map_unit_to_meters; - } - - for (const auto& [oid, info] : element_map_) { - if (info.model_id != mid) continue; - PackedElementInfo pe; - pe.object_id = info.object_id; - pe.model_id = info.model_id; - pe.ifc_id = info.ifc_id; - pe.parent_id = info.parent_id; - pe.guid_offset = static_cast(sd.string_table.size()); - pe.guid_length = static_cast(info.guid.size()); - sd.string_table += info.guid; - pe.name_offset = static_cast(sd.string_table.size()); - pe.name_length = static_cast(info.name.size()); - sd.string_table += info.name; - pe.type_offset = static_cast(sd.string_table.size()); - pe.type_length = static_cast(info.type.size()); - sd.string_table += info.type; - sd.elements.push_back(pe); - } - - QElapsedTimer t_lod; t_lod.start(); - buildLods(sd); - LodStats ls = summariseLods(sd); - qDebug(" LOD build: %lld ms — %u/%u meshes got LOD1 " - "(%u tris -> %u tris for those meshes)", - t_lod.elapsed(), - ls.meshes_with_lod1, ls.meshes_total, - ls.tris_lod0_for_lod1, ls.tris_lod1); - viewport_->applyLodExtension(mid, sd); - - QElapsedTimer t; t.start(); - bool ok = writeSidecar(loader_->filePath(mid).toStdString(), sd); - qDebug(" Sidecar write: %lld ms (%s)", t.elapsed(), ok ? "ok" : "FAILED"); -} - -void MainWindow::removeModelUi(uint32_t mid) { - auto fed_it = model_id_to_fed_id_.find(mid); - if (fed_it != model_id_to_fed_id_.end()) { - fed_id_to_model_id_.erase(fed_it->second); - model_id_to_fed_id_.erase(fed_it); - } - - auto root_it = tree_roots_.find(mid); - if (root_it != tree_roots_.end()) { - delete root_it->second; - tree_roots_.erase(root_it); - } - - for (auto it = tree_items_.begin(); it != tree_items_.end();) { - auto info_it = element_map_.find(it->first); - if (info_it != element_map_.end() && info_it->second.model_id == mid) { - it = tree_items_.erase(it); - } else { - ++it; - } - } - - for (auto it = element_map_.begin(); it != element_map_.end();) { - if (it->second.model_id == mid) { - it = element_map_.erase(it); - } else { - ++it; - } - } - - for (auto it = scoped_ifc_id_to_object_id_.begin(); it != scoped_ifc_id_to_object_id_.end();) { - if (static_cast(it->first >> 32) == mid) { - it = scoped_ifc_id_to_object_id_.erase(it); - } else { - ++it; - } - } - - viewport_->setSelectedObjectId(0); - property_table_->setRowCount(0); -} - -void MainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) { - progress_bar_->setVisible(false); - status_label_->setText(QString("%1 elements across %2 model(s) — last loaded in %3") - .arg(element_map_.size()) - .arg(loader_->modelCount()) - .arg(formatElapsed(elapsed_ms))); - - applyCoordinateOperationToViewport(mid); - applyModelVisibilityToViewport(mid); - maybeGuessFederatedFalseOrigin(mid); - - writeSidecarForModel(mid); -} - -void MainWindow::onLoadCancelled(uint32_t mid) { - progress_bar_->setVisible(false); - removeModelUi(mid); - status_label_->setText(QString("%1 load cancelled").arg(loader_->displayName(mid))); -} - -void MainWindow::onLoadError(uint32_t mid, QString message) { - progress_bar_->setVisible(false); - removeModelUi(mid); - status_label_->setText("Error: " + message); - QMessageBox::warning(this, "Error", message); -} - -void MainWindow::onAllLoadsFinished() { - applyPendingBenchmark(); -} - -void MainWindow::onObjectPicked(uint32_t object_id) { - // Signal originates from the viewport's SelectionState — viewport - // selection is already authoritative. Pushing setSelectedObjectId - // back here would clobber a multi-select set with {object_id}. - auto it = tree_items_.find(object_id); - if (it != tree_items_.end()) { - element_tree_->blockSignals(true); - element_tree_->setCurrentItem(it->second); - element_tree_->blockSignals(false); - } else { - // No active (e.g. selection cleared, or active id is in a model - // whose tree node hasn't materialised) — drop the tree highlight - // so the panel doesn't lie about what's active. - element_tree_->blockSignals(true); - element_tree_->setCurrentItem(nullptr); - element_tree_->blockSignals(false); - } - - populateProperties(object_id); - updateVolumeReadout(); -} - -void MainWindow::updateVolumeReadout() { - // Volume readout only renders while the volume tool is active — - // matches Area/Length, which are also gated behind their own tool - // mode. Other modes own the HUD + overlay labels for their - // lifetime, so we stay quiet here. - if (viewport_->toolMode() != ViewportWindow::ToolMode::Volume) return; - - const auto& sel = viewport_->selection().selectionIds(); - if (sel.empty()) { - viewport_->setHudText(QString()); - viewport_->setOverlayLabels({}); - return; - } - - std::vector ids(sel.begin(), sel.end()); - const auto per_obj = volumesPerObject(*viewport_, ids); - - double total = 0.0; - std::vector labels; - labels.reserve(per_obj.size()); - for (const auto& [oid, v] : per_obj) { - total += v; - QVector3D mn, mx; - if (!viewport_->computeObjectAabb(oid, mn, mx)) continue; - OverlayRenderer::Label lbl; - const QVector3D c = (mn + mx) * 0.5f; - lbl.world_pos[0] = c.x(); - lbl.world_pos[1] = c.y(); - lbl.world_pos[2] = c.z(); - lbl.text = QString::number(v, 'f', 4) + " m³"; - labels.push_back(std::move(lbl)); - } - - viewport_->setHudText(QString("Volume: %1 m³ (%2 object%3)") - .arg(total, 0, 'f', 4) - .arg(per_obj.size()) - .arg(per_obj.size() == 1 ? "" : "s")); - viewport_->setOverlayLabels(labels); -} - -void MainWindow::onTreeSelectionChanged() { - auto items = element_tree_->selectedItems(); - if (items.isEmpty()) return; - - uint32_t object_id = items.first()->data(0, Qt::UserRole).toUInt(); - // Tree stays single-select per UX choice — clicking a tree item - // replaces the viewport's multi-set with just that item. - viewport_->setSelectedObjectId(object_id); - populateProperties(object_id); -} - -void MainWindow::populateProperties(uint32_t object_id) { - property_table_->setRowCount(0); - if (object_id == 0) return; - - auto it = element_map_.find(object_id); - if (it == element_map_.end()) return; - - const auto& info = it->second; - - auto addRow = [this](const QString& key, const QString& value) { - int row = property_table_->rowCount(); - property_table_->insertRow(row); - property_table_->setItem(row, 0, new QTableWidgetItem(key)); - property_table_->setItem(row, 1, new QTableWidgetItem(value)); - }; - - addRow("IFC ID", QString::number(info.ifc_id)); - addRow("GUID", QString::fromStdString(info.guid)); - addRow("Name", QString::fromStdString(info.name)); - addRow("Type", QString::fromStdString(info.type)); - - auto* file = loader_->ifcFile(info.model_id); - if (!file) return; - - auto product = file->instance_by_id(info.ifc_id); - if (!product) return; - - auto& decl = product.declaration(); - if (auto* entity = decl.as_entity()) { - for (size_t i = 0; i < entity->attribute_count(); ++i) { - auto* attr = entity->attribute_by_index(i); - try { - auto val = product.get_attribute_value(i); - if (!val.isNull()) { - std::string str_val; - try { - str_val = static_cast(val); - } catch (...) { - str_val = "<" + std::string(ifcopenshell::argument_type_to_string(val.type())) + ">"; - } - addRow(QString::fromStdString(attr->name()), QString::fromStdString(str_val)); - } - } catch (...) {} - } - } -} - -void MainWindow::setPendingCamera(const QString& params) { - pending_camera_ = params; -} - -void MainWindow::setPendingBenchmark(int frames) { - pending_benchmark_ = frames; -} - -void MainWindow::applyPendingBenchmark() { - if (pending_camera_.isEmpty() && pending_benchmark_ <= 0) return; - - if (!pending_camera_.isEmpty()) { - QStringList parts = pending_camera_.split(','); - if (parts.size() == 6) { - viewport_->setCamera( - parts[0].toFloat(), parts[1].toFloat(), parts[2].toFloat(), - parts[3].toFloat(), parts[4].toFloat(), parts[5].toFloat()); - qDebug("Camera set: %s", qPrintable(pending_camera_)); - } else { - qWarning("--camera expects 6 comma-separated values: tx,ty,tz,dist,yaw,pitch"); - } - pending_camera_.clear(); - } - - if (pending_benchmark_ > 0) { - qDebug("Starting benchmark: %d frames", pending_benchmark_); - viewport_->setBenchmarkFrames(pending_benchmark_); - pending_benchmark_ = 0; - } -} - -QString MainWindow::formatElapsed(qint64 ms) const { - return (ms >= 1000) - ? QString::number(ms / 1000.0, 'f', 2) + " s" - : QString::number(ms) + " ms"; -} - -uint32_t MainWindow::modelIdForRoot(QTreeWidgetItem* item) const { - if (!item) return 0; - for (const auto& kv : tree_roots_) { - if (kv.second == item) return kv.first; - } - return 0; -} - -void MainWindow::applyModelVisibilityToViewport(uint32_t mid) { - auto fed_it = model_id_to_fed_id_.find(mid); - if (fed_it == model_id_to_fed_id_.end()) return; - const QString& fed_id = fed_it->second; - const bool effective = federation_->isModelEffectivelyVisible(fed_id); - if (effective) viewport_->showModel(mid); - else viewport_->hideModel(mid); - - // Tree-side cue: italicise + grey out the model root when not - // effectively visible (own toggle off, or any ancestor group hidden). - auto root_it = tree_roots_.find(mid); - if (root_it != tree_roots_.end()) { - QFont f = root_it->second->font(0); - f.setItalic(!effective); - for (int col = 0; col < element_tree_->columnCount(); ++col) { - root_it->second->setFont(col, f); - root_it->second->setForeground( - col, - effective ? element_tree_->palette().color(QPalette::Text) - : element_tree_->palette().color(QPalette::Disabled, - QPalette::Text)); - } - } -} - -void MainWindow::onTreeContextMenu(const QPoint& pos) { - QTreeWidgetItem* item = element_tree_->itemAt(pos); - const uint32_t mid = modelIdForRoot(item); - const QString group_id = groupIdForItem(item); - // Element rows (children of a model root) are excluded — only model - // roots, group rows, and the empty area get a menu. - const bool is_element_row = - item != nullptr && mid == 0 && group_id.isEmpty(); - if (is_element_row) return; - - QMenu menu(this); - - if (mid != 0) { - // === Model row === - auto fed_it = model_id_to_fed_id_.find(mid); - if (fed_it == model_id_to_fed_id_.end()) return; - const Federation::Model* m = federation_->findById(fed_it->second); - if (!m) return; - - const bool currently_loading = loader_->isLoadingModel(mid); - - QAction* hide_show = menu.addAction(m->visible ? "Hide" : "Show"); - - QMenu* move_menu = menu.addMenu("Move to Group"); - QAction* move_to_root = move_menu->addAction("(Root)"); - move_to_root->setEnabled(!m->group_id.isEmpty()); - move_menu->addSeparator(); - std::vector> move_targets; - for (const Federation::Group* g : federation_->allGroups()) { - QAction* a = move_menu->addAction(g->display_name); - a->setEnabled(g->id != m->group_id); - move_targets.emplace_back(a, g->id); - } - if (federation_->allGroups().empty()) { - QAction* none = move_menu->addAction("(no groups)"); - none->setEnabled(false); - } - - QAction* remove = menu.addAction("Remove"); - remove->setEnabled(!currently_loading); - - QAction* chosen = menu.exec(element_tree_->viewport()->mapToGlobal(pos)); - if (!chosen) return; - if (chosen == hide_show) { - federation_->setModelVisible(fed_it->second, !m->visible); - } else if (chosen == move_to_root) { - federation_->setModelGroup(fed_it->second, QString()); - } else if (chosen == remove) { - removeModel(mid); - } else { - for (const auto& [a, gid] : move_targets) { - if (chosen == a) { - federation_->setModelGroup(fed_it->second, gid); - break; - } - } - } - return; - } - - if (!group_id.isEmpty()) { - // === Group row === - const Federation::Group* g = federation_->findGroupById(group_id); - if (!g) return; - - QAction* hide_show = menu.addAction(g->visible ? "Hide" : "Show"); - QAction* rename = menu.addAction("Rename..."); - QAction* new_sub = menu.addAction("New Subgroup"); - - QMenu* move_menu = menu.addMenu("Move to Parent"); - QAction* move_to_root = move_menu->addAction("(Root)"); - move_to_root->setEnabled(g->parent != nullptr); - move_menu->addSeparator(); - std::vector> move_targets; - for (const Federation::Group* og : federation_->allGroups()) { - QAction* a = move_menu->addAction(og->display_name); - // Disable self, current parent, and any descendant of g (the - // latter would create a cycle). Walk og's ancestor chain to - // detect descendants. - bool would_cycle = false; - for (const Federation::Group* cur = og; cur; cur = cur->parent) { - if (cur == g) { would_cycle = true; break; } - } - const bool is_current_parent = - g->parent != nullptr && og == g->parent; - a->setEnabled(!would_cycle && !is_current_parent); - move_targets.emplace_back(a, og->id); - } - - QAction* remove = menu.addAction("Remove Group"); - - QAction* chosen = menu.exec(element_tree_->viewport()->mapToGlobal(pos)); - if (!chosen) return; - if (chosen == hide_show) { - federation_->setGroupVisible(group_id, !g->visible); - } else if (chosen == rename) { - bool ok = false; - QString name = QInputDialog::getText( - this, "Rename Group", "Group name:", QLineEdit::Normal, - g->display_name, &ok); - if (ok && !name.isEmpty()) federation_->setGroupName(group_id, name); - } else if (chosen == new_sub) { - bool ok = false; - QString name = QInputDialog::getText( - this, "New Subgroup", "Group name:", QLineEdit::Normal, - "Group", &ok); - if (ok && !name.isEmpty()) federation_->addGroup(name, group_id); - } else if (chosen == move_to_root) { - federation_->setGroupParent(group_id, QString()); - } else if (chosen == remove) { - federation_->removeGroup(group_id); - } else { - for (const auto& [a, gid] : move_targets) { - if (chosen == a) { - federation_->setGroupParent(group_id, gid); - break; - } - } - } - return; - } - - // === Empty area === - QAction* new_group = menu.addAction("New Group"); - QAction* chosen = menu.exec(element_tree_->viewport()->mapToGlobal(pos)); - if (!chosen) return; - if (chosen == new_group) { - bool ok = false; - QString name = QInputDialog::getText( - this, "New Group", "Group name:", QLineEdit::Normal, - "Group", &ok); - if (ok && !name.isEmpty()) federation_->addGroup(name, QString()); - } -} - -void MainWindow::removeModel(uint32_t mid) { - if (loader_->isLoadingModel(mid)) return; - - QString fed_id; - auto fed_it = model_id_to_fed_id_.find(mid); - if (fed_it != model_id_to_fed_id_.end()) fed_id = fed_it->second; - - viewport_->removeModel(mid); - loader_->removeModel(mid); - removeModelUi(mid); - if (!fed_id.isEmpty()) federation_->removeModel(fed_id); - updateWindowTitle(); -} - -QString MainWindow::groupIdForItem(QTreeWidgetItem* item) const { - if (!item) return {}; - for (const auto& kv : group_tree_items_) { - if (kv.second == item) return kv.first; - } - return {}; -} - -QTreeWidgetItem* MainWindow::ensureGroupTreeItem(const QString& group_id) { - if (group_id.isEmpty()) return nullptr; - auto it = group_tree_items_.find(group_id); - if (it != group_tree_items_.end()) return it->second; - const Federation::Group* g = federation_->findGroupById(group_id); - if (!g) return nullptr; - - auto* item = new QTreeWidgetItem(); - item->setText(0, g->display_name); - item->setText(1, "Group"); - group_tree_items_[group_id] = item; - return item; -} - -void MainWindow::reparentGroupTreeItem(const QString& group_id) { - auto it = group_tree_items_.find(group_id); - if (it == group_tree_items_.end()) return; - QTreeWidgetItem* item = it->second; - const Federation::Group* g = federation_->findGroupById(group_id); - if (!g) return; - - QTreeWidgetItem* desired_parent = nullptr; - if (g->parent != nullptr) { - auto pit = group_tree_items_.find(g->parent->id); - if (pit != group_tree_items_.end()) desired_parent = pit->second; - } - - QTreeWidgetItem* current_parent = item->parent(); - if (current_parent == desired_parent && - (current_parent != nullptr || - element_tree_->indexOfTopLevelItem(item) >= 0)) { - return; - } - - // Detach from current location. - if (current_parent) { - current_parent->removeChild(item); - } else { - int idx = element_tree_->indexOfTopLevelItem(item); - if (idx >= 0) element_tree_->takeTopLevelItem(idx); - } - // Attach to desired location. - if (desired_parent) desired_parent->addChild(item); - else element_tree_->addTopLevelItem(item); -} - -void MainWindow::reparentModelTreeRoot(uint32_t mid) { - auto root_it = tree_roots_.find(mid); - if (root_it == tree_roots_.end()) return; - QTreeWidgetItem* item = root_it->second; - - auto fed_it = model_id_to_fed_id_.find(mid); - QString group_id; - if (fed_it != model_id_to_fed_id_.end()) { - if (const Federation::Model* m = federation_->findById(fed_it->second)) { - group_id = m->group_id; - } - } - - QTreeWidgetItem* desired_parent = nullptr; - if (!group_id.isEmpty()) { - auto pit = group_tree_items_.find(group_id); - if (pit != group_tree_items_.end()) desired_parent = pit->second; - } - - QTreeWidgetItem* current_parent = item->parent(); - if (current_parent == desired_parent && - (current_parent != nullptr || - element_tree_->indexOfTopLevelItem(item) >= 0)) { - return; - } - - if (current_parent) { - current_parent->removeChild(item); - } else { - int idx = element_tree_->indexOfTopLevelItem(item); - if (idx >= 0) element_tree_->takeTopLevelItem(idx); - } - if (desired_parent) desired_parent->addChild(item); - else element_tree_->addTopLevelItem(item); -} - -void MainWindow::refreshGroupRowAppearance(const QString& group_id) { - auto it = group_tree_items_.find(group_id); - if (it == group_tree_items_.end()) return; - QTreeWidgetItem* item = it->second; - const bool effective = federation_->isGroupChainVisible(group_id); - - QFont f = item->font(0); - f.setItalic(!effective); - f.setBold(true); - for (int col = 0; col < element_tree_->columnCount(); ++col) { - item->setFont(col, f); - item->setForeground( - col, - effective ? element_tree_->palette().color(QPalette::Text) - : element_tree_->palette().color(QPalette::Disabled, - QPalette::Text)); - } -} - -std::vector MainWindow::descendantGroupIds(const QString& group_id) const { - std::vector out; - auto walk = [&](auto&& self, - const std::vector>& src) -> void { - for (const auto& g : src) { - out.push_back(g->id); - self(self, g->children); - } - }; - if (group_id.isEmpty()) { - walk(walk, federation_->rootGroups()); - } else { - const Federation::Group* g = federation_->findGroupById(group_id); - if (!g) return out; - out.push_back(g->id); - walk(walk, g->children); - } - return out; -} - -void MainWindow::applyVisibilityCascadeFromGroup(const QString& group_id) { - const std::vector gids = descendantGroupIds(group_id); - // Models directly assigned to one of these groups need their viewport - // visibility re-pushed because chain visibility may have flipped. - for (const auto& m : federation_->models()) { - const bool affected = - (group_id.isEmpty()) || - std::find(gids.begin(), gids.end(), m.group_id) != gids.end(); - if (!affected) continue; - auto it = fed_id_to_model_id_.find(m.id); - if (it != fed_id_to_model_id_.end()) { - applyModelVisibilityToViewport(it->second); - } - } -} +} // namespace ifcinterface::shell diff --git a/src/ifcviewer-full/MainWindow.h b/src/ifcviewer-full/MainWindow.h index 2d26cda6fd..1439d8bf91 100644 --- a/src/ifcviewer-full/MainWindow.h +++ b/src/ifcviewer-full/MainWindow.h @@ -1,3 +1,4 @@ +// This file was generated with the assistance of an AI coding tool. /******************************************************************************** * * * This file is part of IfcOpenShell. * @@ -17,195 +18,85 @@ * * ********************************************************************************/ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H +#ifndef IFCINTERFACE_SHELL_MAINWINDOW_H +#define IFCINTERFACE_SHELL_MAINWINDOW_H +#include #include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "Measurement.h" -#include "ViewportWindow.h" -#include "SceneLoader.h" +#include +class QLabel; +class QDockWidget; +class QProgressBar; +class QStackedWidget; +class QToolButton; class Federation; -class SettingsWindow; -class FederationSettingsDialog; -class ModelTransformationDialog; +class SceneLoader; +namespace ifcinterface { class ElementRegistry; } +namespace ifcinterface { class SessionState; } +namespace ifcinterface::components { class TabBar; } +namespace ifcinterface::modules::models { class ModelsPanelController; } +namespace ifcinterface::modules::models { class ModelsPanel; } +namespace ifcinterface::modules::models { class ModelsPanelView; } +namespace ifcinterface::modules::project { class ProjectController; } +namespace ifcinterface::modules::spatial_hierarchy { class SpatialHierarchyPanel; } +namespace ifcinterface::modules::spatial_hierarchy { class SpatialHierarchyPanelView; } +namespace ifcinterface::modules::properties { class PropertiesPanel; } +namespace ifcinterface::modules::properties { class PropertiesPanelView; } +namespace ifcinterface::modules::viewport { class ViewportController; } +namespace ifcinterface::modules::viewport { class ViewportPanel; } + +namespace ifcinterface::shell { class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget* parent = nullptr); - ~MainWindow(); - - void addFiles(const QStringList& paths); - bool openFederation(const QString& path); - void setPendingCamera(const QString& params); - void setPendingBenchmark(int frames); - -protected: - void closeEvent(QCloseEvent* event) override; - -private slots: - void onFileOpen(); - void onDatabaseOpen(); - void onFederationNew(); - void onFederationOpen(); - bool onFederationSave(); - bool onFederationSaveAs(); - void onSetHomeView(); - void onGoHomeView(); - void onFileSettings(); - void onFederationSettings(); - void onModelTransformations(); - void onObjectPicked(uint32_t object_id); - void onTreeSelectionChanged(); - void onTreeContextMenu(const QPoint& pos); - - void onLoadStarted(uint32_t mid, QString display_name); - void onLoadProgressChanged(int percent); - void onSidecarElementsReady(uint32_t mid, - std::vector elements, - std::string string_table); - void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms); - void onDataSourceReady(uint32_t mid); - void onStreamedElementsReady(uint32_t mid, std::vector elements); - void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms); - void onLoadCancelled(uint32_t mid); - void onLoadError(uint32_t mid, QString message); - void onAllLoadsFinished(); private: - void setupUi(); - void setupMenus(); - void loadModelsFromPaths(const QStringList& paths, - const QStringList& fed_ids); - void clearScene(); - bool confirmDiscardIfDirty(); + void setupChrome(); + void setupRibbon(); + void setupViewport(); + void setupPanels(); + void setupStatus(); + void setupLoader(); + QWidget* buildHomeRibbonPage(); + QWidget* buildNavigateRibbonPage(); + QWidget* buildInspectRibbonPage(); + QWidget* buildPanelsRibbonPage(); void updateWindowTitle(); - void populateProperties(uint32_t object_id); - // Push the volume HUD + per-object volume labels for the current - // selection. No-op when a measurement tool is active — that tool - // owns the overlay state until it's exited. - void updateVolumeReadout(); - void appendElementToTree(uint32_t model_id, - uint32_t object_id, - int ifc_id, - int parent_ifc_id, - const std::string& guid, - const std::string& name, - const std::string& type); - void writeSidecarForModel(uint32_t mid); - void removeModelUi(uint32_t mid); - void removeModel(uint32_t mid); - // Returns the model_id whose tree root is `item`, or 0 if `item` is not - // a model root (i.e. an element row, group, or null). - uint32_t modelIdForRoot(QTreeWidgetItem* item) const; - // Returns the group_id whose tree item is `item`, or empty string when - // `item` is null or is not a group item. - QString groupIdForItem(QTreeWidgetItem* item) const; - // Place / reparent a model root under its group (or at top level when - // group_id is empty / unknown). Idempotent. No-op if there's no tree - // root yet for `mid`. - void reparentModelTreeRoot(uint32_t mid); - // Place / reparent a group item under its parent group (or at top - // level). Idempotent. No-op if there's no tree item for `group_id`. - void reparentGroupTreeItem(const QString& group_id); - // Lazily create the QTreeWidgetItem for `group_id` if not already in - // group_tree_items_. Returns the item. Sets text + flags but does - // not place it under a parent — call reparentGroupTreeItem afterwards. - QTreeWidgetItem* ensureGroupTreeItem(const QString& group_id); - // Recompute italic/grey on a group row from current effective visibility. - void refreshGroupRowAppearance(const QString& group_id); - // Walk descendants of `group_id` and re-push effective visibility to - // the viewport for every model under it. When `group_id` is empty, - // re-pushes every model in the federation. - void applyVisibilityCascadeFromGroup(const QString& group_id); - // Walk every group_id whose ancestor chain currently includes - // `group_id` (inclusive). - std::vector descendantGroupIds(const QString& group_id) const; - // Push the federation's `visible` flag for `mid` onto the viewport. - // No-op if `mid` is not in the federation map. Idempotent — safe to - // call before the model is finalised on the viewport (hideModel is a - // lookup-and-set on models_gpu_; missing entries are skipped). - void applyModelVisibilityToViewport(uint32_t mid); - void applyPendingBenchmark(); + QToolButton* makePanelToggle(const QString& text, QDockWidget* dock); - // 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); - - // Push a model's ModelTransformation (stage 4) matrix to the viewport, - // composed from the federation's authoring intent + the model's units - // + the active CoordinateOperation matrix. Identity when the model is - // not in the federation. - void applyModelTransformationToViewport(uint32_t mid); - - // Push the federation-wide FederatedFalseOrigin (stage 3) matrix to - // the viewport. Affects every loaded model. - void applyFederatedFalseOriginToViewport(); - - // For an untitled federation whose FederatedFalseOrigin is still at - // its default, derive a sensible origin from `mid`'s first instance - // placement + georef and push it via Federation. Idempotent: a - // non-default origin (user-edited, already guessed by a sibling load - // in the same batch, or loaded from a saved .ifcfed) is left - // untouched, so multi-file batches naturally anchor on whichever - // model finishes first. - void maybeGuessFederatedFalseOrigin(uint32_t mid); - QString formatElapsed(qint64 ms) const; - - ViewportWindow* viewport_ = nullptr; - SceneLoader* loader_ = nullptr; - Federation* federation_ = nullptr; - SettingsWindow* settings_ = nullptr; - FederationSettingsDialog* federation_settings_ = nullptr; - ModelTransformationDialog* model_transformations_ = nullptr; - QWidget* viewport_container_ = nullptr; - QTreeWidget* element_tree_ = nullptr; - QTableWidget* property_table_ = nullptr; - QProgressBar* progress_bar_ = nullptr; - QLabel* status_label_ = nullptr; - QLabel* stats_label_ = nullptr; - - // Per-model tree roots, keyed by model_id. May live at the top level - // of element_tree_ or as a child of a group tree item. - std::map tree_roots_; - // Per-group tree items, keyed by Federation group id. - std::unordered_map group_tree_items_; - - // Bidirectional federation_id <-> model_id map. Federation owns the - // persistent ids; SceneLoader owns the runtime model_ids. - std::unordered_map fed_id_to_model_id_; - std::unordered_map model_id_to_fed_id_; - - // Display-side element registry for tree + property lookup. - std::unordered_map element_map_; - std::unordered_map tree_items_; - // Scoped (model_id, ifc_id) -> object_id - std::unordered_map scoped_ifc_id_to_object_id_; - - static uint64_t scopedKey(uint32_t model_id, int ifc_id) { - return (static_cast(model_id) << 32) | static_cast(ifc_id); - } - - QString pending_camera_; - int pending_benchmark_ = 0; - - AreaMeasurement area_measurement_; - LengthMeasurement length_measurement_; +private: + Federation* federation_ = nullptr; + QLabel* status_mode_label_ = nullptr; + QLabel* status_selection_label_ = nullptr; + QLabel* status_perf_label_ = nullptr; + QProgressBar* status_progress_bar_ = nullptr; + ifcinterface::components::TabBar* ribbon_tabs_ = nullptr; + QStackedWidget* ribbon_pages_ = nullptr; + ifcinterface::modules::viewport::ViewportPanel* viewport_widget_ = nullptr; + ifcinterface::modules::viewport::ViewportController* viewport_controller_ = nullptr; + SceneLoader* loader_ = nullptr; + ifcinterface::ElementRegistry* element_registry_ = nullptr; + ifcinterface::SessionState* session_state_ = nullptr; + ifcinterface::modules::models::ModelsPanel* models_panel_ = nullptr; + ifcinterface::modules::spatial_hierarchy::SpatialHierarchyPanel* spatial_panel_ = nullptr; + QDockWidget* layers_panel_ = nullptr; + ifcinterface::modules::properties::PropertiesPanel* properties_panel_ = nullptr; + QDockWidget* stored_views_panel_ = nullptr; + QDockWidget* search_panel_ = nullptr; + QDockWidget* spreadsheet_panel_ = nullptr; + QDockWidget* audit_panel_ = nullptr; + QDockWidget* clash_panel_ = nullptr; + QDockWidget* issues_panel_ = nullptr; + ifcinterface::modules::models::ModelsPanelController* models_controller_ = nullptr; + ifcinterface::modules::models::ModelsPanelView* models_view_ = nullptr; + ifcinterface::modules::project::ProjectController* project_controller_ = nullptr; + ifcinterface::modules::spatial_hierarchy::SpatialHierarchyPanelView* spatial_view_ = nullptr; + ifcinterface::modules::properties::PropertiesPanelView* properties_view_ = nullptr; }; -#endif // MAINWINDOW_H +} // namespace ifcinterface::shell + +#endif diff --git a/src/ifcviewer-full/Measurement.cpp b/src/ifcviewer-full/Measurement.cpp index 548a39756f..77e62f798e 100644 --- a/src/ifcviewer-full/Measurement.cpp +++ b/src/ifcviewer-full/Measurement.cpp @@ -1,3 +1,4 @@ +// This file was generated with the assistance of an AI coding tool. /******************************************************************************** * * * This file is part of IfcOpenShell. * diff --git a/src/ifcviewer-full/Measurement.h b/src/ifcviewer-full/Measurement.h index 9982448f17..83f2dbfba5 100644 --- a/src/ifcviewer-full/Measurement.h +++ b/src/ifcviewer-full/Measurement.h @@ -1,3 +1,4 @@ +// This file was generated with the assistance of an AI coding tool. /******************************************************************************** * * * This file is part of IfcOpenShell. * @@ -17,8 +18,8 @@ * * ********************************************************************************/ -#ifndef IFCVIEWER_FULL_MEASUREMENT_H -#define IFCVIEWER_FULL_MEASUREMENT_H +#ifndef IFCINTERFACE_MEASUREMENT_H +#define IFCINTERFACE_MEASUREMENT_H #include #include "ViewportWindow.h" @@ -158,4 +159,4 @@ private: ViewportWindow::MeshLocalPick first_pick_{}; }; -#endif // IFCVIEWER_FULL_MEASUREMENT_H +#endif // IFCINTERFACE_MEASUREMENT_H diff --git a/src/ifcviewer-full/ModelTransformationDialog.cpp b/src/ifcviewer-full/ModelTransformationDialog.cpp deleted file mode 100644 index f42c45ca42..0000000000 --- a/src/ifcviewer-full/ModelTransformationDialog.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/******************************************************************************** - * * - * 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 "ModelTransformationDialog.h" -#include "Federation.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -QString unitDisplay(const FederationConfig& cfg) { - QString s; - if (!cfg.unit_prefix.empty()) s += QString::fromStdString(cfg.unit_prefix) + " "; - s += QString::fromStdString(cfg.unit_name); - return s; -} - -} // namespace - -ModelTransformationDialog::ModelTransformationDialog(Federation* federation, - QWidget* parent) - : QDialog(parent), federation_(federation) -{ - setWindowTitle("Model Transformation"); - setupUi(); -} - -void ModelTransformationDialog::setupUi() { - auto* root = new QVBoxLayout(this); - - // Model picker. - { - auto* row = new QHBoxLayout(); - row->addWidget(new QLabel("Model:", this)); - model_combo_ = new QComboBox(this); - row->addWidget(model_combo_, 1); - root->addLayout(row); - connect(model_combo_, QOverload::of(&QComboBox::currentIndexChanged), - this, &ModelTransformationDialog::onModelChanged); - } - - auto build_xyz = [](QDoubleSpinBox*& sb) { - sb = new QDoubleSpinBox(); - sb->setRange(-1e12, 1e12); - sb->setDecimals(6); - sb->setSingleStep(1.0); - }; - - // A-frame + A. - { - auto* group = new QGroupBox("Point A", this); - group->setToolTip( - "The model-side anchor point. ModelLocal expresses A in the " - "model's project length unit, before its CoordinateOperation. " - "ModelGlobal expresses A in the model's map unit, after its " - "CoordinateOperation."); - auto* form = new QFormLayout(group); - - radio_local_ = new QRadioButton("ModelLocal", group); - radio_global_ = new QRadioButton("ModelGlobal", group); - radio_global_->setChecked(true); - auto* af_group = new QButtonGroup(this); - af_group->addButton(radio_local_); - af_group->addButton(radio_global_); - connect(radio_local_, &QRadioButton::toggled, - this, &ModelTransformationDialog::onAFrameToggled); - connect(radio_global_, &QRadioButton::toggled, - this, &ModelTransformationDialog::onAFrameToggled); - - auto* af_row = new QHBoxLayout(); - af_row->addWidget(radio_local_); - af_row->addWidget(radio_global_); - af_row->addStretch(); - form->addRow("Frame", af_row); - - build_xyz(a_x_); build_xyz(a_y_); build_xyz(a_z_); - form->addRow("X", a_x_); - form->addRow("Y", a_y_); - form->addRow("Z", a_z_); - a_unit_label_ = new QLabel("(model unit)", group); - form->addRow("Unit", a_unit_label_); - - root->addWidget(group); - } - - // B. - { - auto* group = new QGroupBox("Point B (federation target)", this); - group->setToolTip( - "Where in the federation point A should land. In federation units."); - auto* form = new QFormLayout(group); - build_xyz(b_x_); build_xyz(b_y_); build_xyz(b_z_); - form->addRow("X", b_x_); - form->addRow("Y", b_y_); - form->addRow("Z", b_z_); - b_unit_label_ = new QLabel("(federation unit)", group); - form->addRow("Unit", b_unit_label_); - root->addWidget(group); - } - - // Rotation (intrinsic XYZ). - { - auto* group = new QGroupBox("Rotation (intrinsic XYZ, degrees)", this); - group->setToolTip( - "Composed as R = R_z(rz) · R_y(ry) · R_x(rx). Y-up models " - "rotated to Z-up: rx = 90, ry = 0, rz = 0."); - auto* form = new QFormLayout(group); - rx_ = new QDoubleSpinBox(); rx_->setRange(-360, 360); rx_->setDecimals(6); - ry_ = new QDoubleSpinBox(); ry_->setRange(-360, 360); ry_->setDecimals(6); - rz_ = new QDoubleSpinBox(); rz_->setRange(-360, 360); rz_->setDecimals(6); - form->addRow("rx (°)", rx_); - form->addRow("ry (°)", ry_); - form->addRow("rz (°)", rz_); - root->addWidget(group); - } - - // Pivot. - { - auto* group = new QGroupBox("Rotation Pivot", this); - group->setToolTip( - "The point the rotation rotates around, in federation units. " - "Set pivot = B to keep A landing on B regardless of rotation."); - auto* form = new QFormLayout(group); - build_xyz(pivot_x_); build_xyz(pivot_y_); build_xyz(pivot_z_); - form->addRow("X", pivot_x_); - form->addRow("Y", pivot_y_); - form->addRow("Z", pivot_z_); - pivot_unit_label_ = new QLabel("(federation unit)", group); - form->addRow("Unit", pivot_unit_label_); - root->addWidget(group); - } - - auto* button_box = new QDialogButtonBox( - QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - root->addWidget(button_box); - connect(button_box, &QDialogButtonBox::accepted, this, - &ModelTransformationDialog::onAccepted); - connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); -} - -void ModelTransformationDialog::showEvent(QShowEvent* event) { - populateModelCombo(); - refreshUnitLabels(); - if (model_combo_->count() > 0) { - syncFromModel(model_combo_->currentData().toString()); - } - QDialog::showEvent(event); -} - -void ModelTransformationDialog::populateModelCombo() { - model_combo_->blockSignals(true); - QString prev_id = model_combo_->currentData().toString(); - model_combo_->clear(); - if (federation_) { - for (const auto& m : federation_->models()) { - QString label = m.display_name.isEmpty() ? m.id : m.display_name; - model_combo_->addItem(label, m.id); - } - } - int restore = -1; - for (int i = 0; i < model_combo_->count(); ++i) { - if (model_combo_->itemData(i).toString() == prev_id) { restore = i; break; } - } - if (restore >= 0) model_combo_->setCurrentIndex(restore); - model_combo_->blockSignals(false); -} - -void ModelTransformationDialog::syncFromModel(const QString& fed_id) { - if (!federation_) return; - const Federation::Model* m = federation_->findById(fed_id); - if (!m) return; - const ModelTransformation& xf = m->model_transformation; - - radio_local_->setChecked(xf.a_frame == AFrame::ModelLocal); - radio_global_->setChecked(xf.a_frame == AFrame::ModelGlobal); - - a_x_->setValue(xf.a.x()); a_y_->setValue(xf.a.y()); a_z_->setValue(xf.a.z()); - b_x_->setValue(xf.b.x()); b_y_->setValue(xf.b.y()); b_z_->setValue(xf.b.z()); - rx_->setValue(xf.rxyz_deg.x()); - ry_->setValue(xf.rxyz_deg.y()); - rz_->setValue(xf.rxyz_deg.z()); - pivot_x_->setValue(xf.pivot.x()); - pivot_y_->setValue(xf.pivot.y()); - pivot_z_->setValue(xf.pivot.z()); -} - -void ModelTransformationDialog::refreshUnitLabels() { - if (!federation_) return; - const QString fed_unit = unitDisplay(federation_->config()); - b_unit_label_->setText("(federation: " + fed_unit + ")"); - pivot_unit_label_->setText("(federation: " + fed_unit + ")"); - onAFrameToggled(); -} - -void ModelTransformationDialog::onModelChanged(int /*idx*/) { - if (model_combo_->count() == 0) return; - syncFromModel(model_combo_->currentData().toString()); -} - -void ModelTransformationDialog::onAFrameToggled() { - if (!a_unit_label_) return; - if (radio_local_->isChecked()) { - a_unit_label_->setText("(model project length unit, e.g. millimetre)"); - } else { - a_unit_label_->setText("(model map unit, e.g. metre)"); - } -} - -void ModelTransformationDialog::onAccepted() { - if (!federation_ || model_combo_->count() == 0) { accept(); return; } - const QString fed_id = model_combo_->currentData().toString(); - - ModelTransformation xf; - xf.a_frame = radio_local_->isChecked() ? AFrame::ModelLocal : AFrame::ModelGlobal; - xf.a = Eigen::Vector3d(a_x_->value(), a_y_->value(), a_z_->value()); - xf.b = Eigen::Vector3d(b_x_->value(), b_y_->value(), b_z_->value()); - xf.rxyz_deg = Eigen::Vector3d(rx_->value(), ry_->value(), rz_->value()); - xf.pivot = Eigen::Vector3d(pivot_x_->value(), pivot_y_->value(), pivot_z_->value()); - - federation_->setModelTransformation(fed_id, xf); - accept(); -} diff --git a/src/ifcviewer-full/ModelTransformationDialog.h b/src/ifcviewer-full/ModelTransformationDialog.h deleted file mode 100644 index 1d4d910095..0000000000 --- a/src/ifcviewer-full/ModelTransformationDialog.h +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************** - * * - * 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 MODELTRANSFORMATIONDIALOG_H -#define MODELTRANSFORMATIONDIALOG_H - -#include -#include - -QT_BEGIN_NAMESPACE -class QComboBox; -class QDoubleSpinBox; -class QLabel; -class QRadioButton; -QT_END_NAMESPACE - -class Federation; - -// Edits one model's ModelTransformation (the per-model placement within -// the federation: a_frame, a, b, rxyz Euler degrees, pivot). A combobox -// at the top picks which model to edit. Switching models discards any -// unsaved form edits — Ok saves the currently-visible model. On Ok, -// calls Federation::setModelTransformation, which fires -// modelTransformationChanged → MainWindow recomposes the viewport for -// that model. -class ModelTransformationDialog : public QDialog { - Q_OBJECT -public: - explicit ModelTransformationDialog(Federation* federation, QWidget* parent = nullptr); - -protected: - void showEvent(QShowEvent* event) override; - -private slots: - void onModelChanged(int idx); - void onAFrameToggled(); - void onAccepted(); - -private: - void setupUi(); - void populateModelCombo(); - void syncFromModel(const QString& fed_id); - void refreshUnitLabels(); - - Federation* federation_ = nullptr; - - QComboBox* model_combo_ = nullptr; - - QRadioButton* radio_local_ = nullptr; - QRadioButton* radio_global_ = nullptr; - - QDoubleSpinBox* a_x_ = nullptr; - QDoubleSpinBox* a_y_ = nullptr; - QDoubleSpinBox* a_z_ = nullptr; - QLabel* a_unit_label_ = nullptr; - - QDoubleSpinBox* b_x_ = nullptr; - QDoubleSpinBox* b_y_ = nullptr; - QDoubleSpinBox* b_z_ = nullptr; - QLabel* b_unit_label_ = nullptr; - - QDoubleSpinBox* rx_ = nullptr; - QDoubleSpinBox* ry_ = nullptr; - QDoubleSpinBox* rz_ = nullptr; - - QDoubleSpinBox* pivot_x_ = nullptr; - QDoubleSpinBox* pivot_y_ = nullptr; - QDoubleSpinBox* pivot_z_ = nullptr; - QLabel* pivot_unit_label_ = nullptr; -}; - -#endif diff --git a/src/interface/SessionState.cpp b/src/ifcviewer-full/SessionState.cpp similarity index 100% rename from src/interface/SessionState.cpp rename to src/ifcviewer-full/SessionState.cpp diff --git a/src/interface/SessionState.h b/src/ifcviewer-full/SessionState.h similarity index 100% rename from src/interface/SessionState.h rename to src/ifcviewer-full/SessionState.h diff --git a/src/ifcviewer-full/SettingsWindow.cpp b/src/ifcviewer-full/SettingsWindow.cpp deleted file mode 100644 index 987e4a6871..0000000000 --- a/src/ifcviewer-full/SettingsWindow.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/******************************************************************************** - * * - * 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 "SettingsWindow.h" -#include "AppSettings.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -SettingsWindow::SettingsWindow(QWidget *parent) - : QDialog(parent) -{ - setWindowTitle("Settings"); - setupUi(); -} - -void SettingsWindow::setupUi() { - auto* form = new QFormLayout(); - - geometry_library_edit_ = new QLineEdit(this); - geometry_library_edit_->setMinimumWidth(280); - form->addRow("Geometry Library", geometry_library_edit_); - - show_stats_check_ = new QCheckBox(this); - form->addRow("Show Performance Stats", show_stats_check_); - - backface_culling_check_ = new QCheckBox(this); - backface_culling_check_->setToolTip( - "Skip triangles facing away from the camera. Big FPS win on " - "closed solids; disable if you see holes in open geometry."); - form->addRow("Backface Culling", backface_culling_check_); - - void_limit_spin_ = new QSpinBox(this); - void_limit_spin_->setRange(0, 100000); - void_limit_spin_->setToolTip( - "Skip elements with more openings (HasOpenings) than this. " - "A handful of pathological elements can dominate boolean-subtraction " - "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_); - - min_pixel_radius_spin_ = new QDoubleSpinBox(this); - min_pixel_radius_spin_->setRange(0.0, 100.0); - min_pixel_radius_spin_->setDecimals(2); - min_pixel_radius_spin_->setSingleStep(0.5); - min_pixel_radius_spin_->setToolTip( - "Minimum projected sphere radius (in pixels) for an instance to " - "be drawn. Bigger = faster but more pop-in on small detail."); - form->addRow("Min Pixel Radius", min_pixel_radius_spin_); - - motion_min_pixel_radius_spin_ = new QDoubleSpinBox(this); - motion_min_pixel_radius_spin_->setRange(0.0, 100.0); - motion_min_pixel_radius_spin_->setDecimals(2); - motion_min_pixel_radius_spin_->setSingleStep(1.0); - motion_min_pixel_radius_spin_->setToolTip( - "Aggressive cull threshold while the camera is moving. 0 = no " - "motion boost (motion uses the same threshold as still frames). " - "Big perceived FPS win on heavy scenes."); - form->addRow("Motion Min Pixel Radius", motion_min_pixel_radius_spin_); - - lod1_pixel_threshold_spin_ = new QDoubleSpinBox(this); - lod1_pixel_threshold_spin_->setRange(0.0, 1000.0); - lod1_pixel_threshold_spin_->setDecimals(1); - lod1_pixel_threshold_spin_->setSingleStep(1.0); - lod1_pixel_threshold_spin_->setToolTip( - "Pixel radius below which an instance switches to its LOD1 " - "representation. 0 disables LOD1 entirely (always draw LOD0)."); - form->addRow("LOD1 Pixel Threshold", lod1_pixel_threshold_spin_); - - hiz_enabled_check_ = new QCheckBox(this); - hiz_enabled_check_->setToolTip( - "Enable HiZ (hierarchical Z) occlusion culling. Hides geometry " - "behind opaque blockers based on a downsampled depth pyramid " - "from the previous frame. Big perf win on dense interiors."); - form->addRow("HiZ Occlusion", hiz_enabled_check_); - - hiz_resolution_spin_ = new QSpinBox(this); - hiz_resolution_spin_->setRange(64, 4096); - hiz_resolution_spin_->setSingleStep(64); - hiz_resolution_spin_->setToolTip( - "Base HiZ pyramid width in texels (height tracks aspect). " - "Bigger = tighter occlusion but more readback bandwidth. " - "Changes take effect on next viewport reinitialization."); - form->addRow("HiZ Resolution", hiz_resolution_spin_); - - auto* button_box = new QDialogButtonBox( - QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - - auto* root = new QVBoxLayout(this); - root->addLayout(form); - root->addWidget(button_box); - - connect(button_box, &QDialogButtonBox::accepted, this, &SettingsWindow::onAccepted); - connect(button_box, &QDialogButtonBox::rejected, this, &SettingsWindow::reject); -} - -void SettingsWindow::showEvent(QShowEvent* event) { - // Re-sync widgets from the persisted settings every time the dialog is - // shown, so a previous Cancel doesn't leave stale text in the field. - syncFromSettings(); - QDialog::showEvent(event); -} - -void SettingsWindow::syncFromSettings() { - geometry_library_edit_->setText(AppSettings::instance().geometryLibrary()); - show_stats_check_->setChecked(AppSettings::instance().showStats()); - backface_culling_check_->setChecked(AppSettings::instance().backfaceCulling()); - void_limit_spin_->setValue(AppSettings::instance().voidLimit()); - deflection_tolerance_spin_->setValue(AppSettings::instance().deflectionTolerance()); - angular_tolerance_spin_->setValue(AppSettings::instance().angularTolerance()); - min_pixel_radius_spin_->setValue(AppSettings::instance().minPixelRadius()); - motion_min_pixel_radius_spin_->setValue(AppSettings::instance().motionMinPixelRadius()); - lod1_pixel_threshold_spin_->setValue(AppSettings::instance().lod1PixelThreshold()); - hiz_enabled_check_->setChecked(AppSettings::instance().hizEnabled()); - hiz_resolution_spin_->setValue(AppSettings::instance().hizResolution()); -} - -void SettingsWindow::onAccepted() { - AppSettings::instance().setGeometryLibrary(geometry_library_edit_->text()); - AppSettings::instance().setShowStats(show_stats_check_->isChecked()); - AppSettings::instance().setBackfaceCulling(backface_culling_check_->isChecked()); - AppSettings::instance().setVoidLimit(void_limit_spin_->value()); - AppSettings::instance().setDeflectionTolerance(deflection_tolerance_spin_->value()); - AppSettings::instance().setAngularTolerance(angular_tolerance_spin_->value()); - AppSettings::instance().setMinPixelRadius(min_pixel_radius_spin_->value()); - AppSettings::instance().setMotionMinPixelRadius(motion_min_pixel_radius_spin_->value()); - AppSettings::instance().setLod1PixelThreshold(lod1_pixel_threshold_spin_->value()); - AppSettings::instance().setHizEnabled(hiz_enabled_check_->isChecked()); - AppSettings::instance().setHizResolution(hiz_resolution_spin_->value()); - accept(); -} diff --git a/src/ifcviewer-full/SettingsWindow.h b/src/ifcviewer-full/SettingsWindow.h deleted file mode 100644 index 4ecf8d6b45..0000000000 --- a/src/ifcviewer-full/SettingsWindow.h +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************** - * * - * 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 SETTINGSWINDOW_H -#define SETTINGSWINDOW_H - -#include - -class QCheckBox; -class QDoubleSpinBox; -class QLineEdit; -class QShowEvent; -class QSpinBox; - -class SettingsWindow : public QDialog { - Q_OBJECT -public: - explicit SettingsWindow(QWidget *parent = nullptr); - -protected: - void showEvent(QShowEvent* event) override; - -private slots: - void onAccepted(); - -private: - void setupUi(); - void syncFromSettings(); - - QLineEdit* geometry_library_edit_ = nullptr; - QCheckBox* show_stats_check_ = nullptr; - QCheckBox* backface_culling_check_ = nullptr; - QSpinBox* void_limit_spin_ = nullptr; - QDoubleSpinBox* deflection_tolerance_spin_ = nullptr; - QDoubleSpinBox* angular_tolerance_spin_ = nullptr; - QDoubleSpinBox* min_pixel_radius_spin_ = nullptr; - QDoubleSpinBox* motion_min_pixel_radius_spin_ = nullptr; - QDoubleSpinBox* lod1_pixel_threshold_spin_ = nullptr; - QSpinBox* hiz_resolution_spin_ = nullptr; - QCheckBox* hiz_enabled_check_ = nullptr; -}; - -#endif diff --git a/src/interface/components/Buttons.cpp b/src/ifcviewer-full/components/Buttons.cpp similarity index 100% rename from src/interface/components/Buttons.cpp rename to src/ifcviewer-full/components/Buttons.cpp diff --git a/src/interface/components/Buttons.h b/src/ifcviewer-full/components/Buttons.h similarity index 100% rename from src/interface/components/Buttons.h rename to src/ifcviewer-full/components/Buttons.h diff --git a/src/interface/components/Dialog.cpp b/src/ifcviewer-full/components/Dialog.cpp similarity index 100% rename from src/interface/components/Dialog.cpp rename to src/ifcviewer-full/components/Dialog.cpp diff --git a/src/interface/components/Dialog.h b/src/ifcviewer-full/components/Dialog.h similarity index 100% rename from src/interface/components/Dialog.h rename to src/ifcviewer-full/components/Dialog.h diff --git a/src/interface/components/KeyValueTable.cpp b/src/ifcviewer-full/components/KeyValueTable.cpp similarity index 100% rename from src/interface/components/KeyValueTable.cpp rename to src/ifcviewer-full/components/KeyValueTable.cpp diff --git a/src/interface/components/KeyValueTable.h b/src/ifcviewer-full/components/KeyValueTable.h similarity index 100% rename from src/interface/components/KeyValueTable.h rename to src/ifcviewer-full/components/KeyValueTable.h diff --git a/src/interface/components/Panel.cpp b/src/ifcviewer-full/components/Panel.cpp similarity index 100% rename from src/interface/components/Panel.cpp rename to src/ifcviewer-full/components/Panel.cpp diff --git a/src/interface/components/Panel.h b/src/ifcviewer-full/components/Panel.h similarity index 100% rename from src/interface/components/Panel.h rename to src/ifcviewer-full/components/Panel.h diff --git a/src/interface/components/Section.cpp b/src/ifcviewer-full/components/Section.cpp similarity index 100% rename from src/interface/components/Section.cpp rename to src/ifcviewer-full/components/Section.cpp diff --git a/src/interface/components/Section.h b/src/ifcviewer-full/components/Section.h similarity index 100% rename from src/interface/components/Section.h rename to src/ifcviewer-full/components/Section.h diff --git a/src/interface/components/Style.cpp b/src/ifcviewer-full/components/Style.cpp similarity index 100% rename from src/interface/components/Style.cpp rename to src/ifcviewer-full/components/Style.cpp diff --git a/src/interface/components/Style.h b/src/ifcviewer-full/components/Style.h similarity index 100% rename from src/interface/components/Style.h rename to src/ifcviewer-full/components/Style.h diff --git a/src/interface/components/SvgIcon.cpp b/src/ifcviewer-full/components/SvgIcon.cpp similarity index 100% rename from src/interface/components/SvgIcon.cpp rename to src/ifcviewer-full/components/SvgIcon.cpp diff --git a/src/interface/components/SvgIcon.h b/src/ifcviewer-full/components/SvgIcon.h similarity index 100% rename from src/interface/components/SvgIcon.h rename to src/ifcviewer-full/components/SvgIcon.h diff --git a/src/interface/components/Tabs.cpp b/src/ifcviewer-full/components/Tabs.cpp similarity index 100% rename from src/interface/components/Tabs.cpp rename to src/ifcviewer-full/components/Tabs.cpp diff --git a/src/interface/components/Tabs.h b/src/ifcviewer-full/components/Tabs.h similarity index 100% rename from src/interface/components/Tabs.h rename to src/ifcviewer-full/components/Tabs.h diff --git a/src/interface/icons/box-3d-center.svg b/src/ifcviewer-full/icons/box-3d-center.svg similarity index 100% rename from src/interface/icons/box-3d-center.svg rename to src/ifcviewer-full/icons/box-3d-center.svg diff --git a/src/interface/icons/box-3d-three-points.svg b/src/ifcviewer-full/icons/box-3d-three-points.svg similarity index 100% rename from src/interface/icons/box-3d-three-points.svg rename to src/ifcviewer-full/icons/box-3d-three-points.svg diff --git a/src/interface/icons/building.svg b/src/ifcviewer-full/icons/building.svg similarity index 100% rename from src/interface/icons/building.svg rename to src/ifcviewer-full/icons/building.svg diff --git a/src/interface/icons/cellar.svg b/src/ifcviewer-full/icons/cellar.svg similarity index 100% rename from src/interface/icons/cellar.svg rename to src/ifcviewer-full/icons/cellar.svg diff --git a/src/interface/icons/check.svg b/src/ifcviewer-full/icons/check.svg similarity index 100% rename from src/interface/icons/check.svg rename to src/ifcviewer-full/icons/check.svg diff --git a/src/interface/icons/city.svg b/src/ifcviewer-full/icons/city.svg similarity index 100% rename from src/interface/icons/city.svg rename to src/ifcviewer-full/icons/city.svg diff --git a/src/interface/icons/clock-rotate-right.svg b/src/ifcviewer-full/icons/clock-rotate-right.svg similarity index 100% rename from src/interface/icons/clock-rotate-right.svg rename to src/ifcviewer-full/icons/clock-rotate-right.svg diff --git a/src/interface/icons/cloud-square.svg b/src/ifcviewer-full/icons/cloud-square.svg similarity index 100% rename from src/interface/icons/cloud-square.svg rename to src/ifcviewer-full/icons/cloud-square.svg diff --git a/src/interface/icons/cube-bandage.svg b/src/ifcviewer-full/icons/cube-bandage.svg similarity index 100% rename from src/interface/icons/cube-bandage.svg rename to src/ifcviewer-full/icons/cube-bandage.svg diff --git a/src/interface/icons/cube-cut-with-curve.svg b/src/ifcviewer-full/icons/cube-cut-with-curve.svg similarity index 100% rename from src/interface/icons/cube-cut-with-curve.svg rename to src/ifcviewer-full/icons/cube-cut-with-curve.svg diff --git a/src/interface/icons/cube-dots.svg b/src/ifcviewer-full/icons/cube-dots.svg similarity index 100% rename from src/interface/icons/cube-dots.svg rename to src/ifcviewer-full/icons/cube-dots.svg diff --git a/src/interface/icons/cube-scan-solid.svg b/src/ifcviewer-full/icons/cube-scan-solid.svg similarity index 100% rename from src/interface/icons/cube-scan-solid.svg rename to src/ifcviewer-full/icons/cube-scan-solid.svg diff --git a/src/interface/icons/cube-scan.svg b/src/ifcviewer-full/icons/cube-scan.svg similarity index 100% rename from src/interface/icons/cube-scan.svg rename to src/ifcviewer-full/icons/cube-scan.svg diff --git a/src/interface/icons/cube.svg b/src/ifcviewer-full/icons/cube.svg similarity index 100% rename from src/interface/icons/cube.svg rename to src/ifcviewer-full/icons/cube.svg diff --git a/src/interface/icons/cursor-pointer.svg b/src/ifcviewer-full/icons/cursor-pointer.svg similarity index 100% rename from src/interface/icons/cursor-pointer.svg rename to src/ifcviewer-full/icons/cursor-pointer.svg diff --git a/src/interface/icons/database-restore.svg b/src/ifcviewer-full/icons/database-restore.svg similarity index 100% rename from src/interface/icons/database-restore.svg rename to src/ifcviewer-full/icons/database-restore.svg diff --git a/src/interface/icons/database.svg b/src/ifcviewer-full/icons/database.svg similarity index 100% rename from src/interface/icons/database.svg rename to src/ifcviewer-full/icons/database.svg diff --git a/src/interface/icons/download-square.svg b/src/ifcviewer-full/icons/download-square.svg similarity index 100% rename from src/interface/icons/download-square.svg rename to src/ifcviewer-full/icons/download-square.svg diff --git a/src/interface/icons/drone.svg b/src/ifcviewer-full/icons/drone.svg similarity index 100% rename from src/interface/icons/drone.svg rename to src/ifcviewer-full/icons/drone.svg diff --git a/src/interface/icons/eye-closed.svg b/src/ifcviewer-full/icons/eye-closed.svg similarity index 100% rename from src/interface/icons/eye-closed.svg rename to src/ifcviewer-full/icons/eye-closed.svg diff --git a/src/interface/icons/eye-solid.svg b/src/ifcviewer-full/icons/eye-solid.svg similarity index 100% rename from src/interface/icons/eye-solid.svg rename to src/ifcviewer-full/icons/eye-solid.svg diff --git a/src/interface/icons/eye.svg b/src/ifcviewer-full/icons/eye.svg similarity index 100% rename from src/interface/icons/eye.svg rename to src/ifcviewer-full/icons/eye.svg diff --git a/src/interface/icons/face-3d-draft.svg b/src/ifcviewer-full/icons/face-3d-draft.svg similarity index 100% rename from src/interface/icons/face-3d-draft.svg rename to src/ifcviewer-full/icons/face-3d-draft.svg diff --git a/src/interface/icons/filter.svg b/src/ifcviewer-full/icons/filter.svg similarity index 100% rename from src/interface/icons/filter.svg rename to src/ifcviewer-full/icons/filter.svg diff --git a/src/interface/icons/floppy-disk-arrow-in.svg b/src/ifcviewer-full/icons/floppy-disk-arrow-in.svg similarity index 100% rename from src/interface/icons/floppy-disk-arrow-in.svg rename to src/ifcviewer-full/icons/floppy-disk-arrow-in.svg diff --git a/src/interface/icons/floppy-disk.svg b/src/ifcviewer-full/icons/floppy-disk.svg similarity index 100% rename from src/interface/icons/floppy-disk.svg rename to src/ifcviewer-full/icons/floppy-disk.svg diff --git a/src/interface/icons/folder-minus.svg b/src/ifcviewer-full/icons/folder-minus.svg similarity index 100% rename from src/interface/icons/folder-minus.svg rename to src/ifcviewer-full/icons/folder-minus.svg diff --git a/src/interface/icons/folder-plus.svg b/src/ifcviewer-full/icons/folder-plus.svg similarity index 100% rename from src/interface/icons/folder-plus.svg rename to src/ifcviewer-full/icons/folder-plus.svg diff --git a/src/interface/icons/folder.svg b/src/ifcviewer-full/icons/folder.svg similarity index 100% rename from src/interface/icons/folder.svg rename to src/ifcviewer-full/icons/folder.svg diff --git a/src/interface/icons/frame-alt.svg b/src/ifcviewer-full/icons/frame-alt.svg similarity index 100% rename from src/interface/icons/frame-alt.svg rename to src/ifcviewer-full/icons/frame-alt.svg diff --git a/src/interface/icons/home-alt.svg b/src/ifcviewer-full/icons/home-alt.svg similarity index 100% rename from src/interface/icons/home-alt.svg rename to src/ifcviewer-full/icons/home-alt.svg diff --git a/src/interface/icons/home.svg b/src/ifcviewer-full/icons/home.svg similarity index 100% rename from src/interface/icons/home.svg rename to src/ifcviewer-full/icons/home.svg diff --git a/src/interface/icons/intersect.svg b/src/ifcviewer-full/icons/intersect.svg similarity index 100% rename from src/interface/icons/intersect.svg rename to src/ifcviewer-full/icons/intersect.svg diff --git a/src/interface/icons/minus-square.svg b/src/ifcviewer-full/icons/minus-square.svg similarity index 100% rename from src/interface/icons/minus-square.svg rename to src/ifcviewer-full/icons/minus-square.svg diff --git a/src/interface/icons/perspective-view.svg b/src/ifcviewer-full/icons/perspective-view.svg similarity index 100% rename from src/interface/icons/perspective-view.svg rename to src/ifcviewer-full/icons/perspective-view.svg diff --git a/src/interface/icons/planimetry.svg b/src/ifcviewer-full/icons/planimetry.svg similarity index 100% rename from src/interface/icons/planimetry.svg rename to src/ifcviewer-full/icons/planimetry.svg diff --git a/src/interface/icons/plus-square.svg b/src/ifcviewer-full/icons/plus-square.svg similarity index 100% rename from src/interface/icons/plus-square.svg rename to src/ifcviewer-full/icons/plus-square.svg diff --git a/src/interface/icons/refresh-double.svg b/src/ifcviewer-full/icons/refresh-double.svg similarity index 100% rename from src/interface/icons/refresh-double.svg rename to src/ifcviewer-full/icons/refresh-double.svg diff --git a/src/interface/icons/select-edge3d.svg b/src/ifcviewer-full/icons/select-edge3d.svg similarity index 100% rename from src/interface/icons/select-edge3d.svg rename to src/ifcviewer-full/icons/select-edge3d.svg diff --git a/src/interface/icons/select-face3d.svg b/src/ifcviewer-full/icons/select-face3d.svg similarity index 100% rename from src/interface/icons/select-face3d.svg rename to src/ifcviewer-full/icons/select-face3d.svg diff --git a/src/interface/icons/select-point3d.svg b/src/ifcviewer-full/icons/select-point3d.svg similarity index 100% rename from src/interface/icons/select-point3d.svg rename to src/ifcviewer-full/icons/select-point3d.svg diff --git a/src/interface/icons/settings.svg b/src/ifcviewer-full/icons/settings.svg similarity index 100% rename from src/interface/icons/settings.svg rename to src/ifcviewer-full/icons/settings.svg diff --git a/src/interface/icons/sidebar-expand.svg b/src/ifcviewer-full/icons/sidebar-expand.svg similarity index 100% rename from src/interface/icons/sidebar-expand.svg rename to src/ifcviewer-full/icons/sidebar-expand.svg diff --git a/src/interface/icons/square3d-from-center.svg b/src/ifcviewer-full/icons/square3d-from-center.svg similarity index 100% rename from src/interface/icons/square3d-from-center.svg rename to src/ifcviewer-full/icons/square3d-from-center.svg diff --git a/src/interface/icons/xmark-circle.svg b/src/ifcviewer-full/icons/xmark-circle.svg similarity index 100% rename from src/interface/icons/xmark-circle.svg rename to src/ifcviewer-full/icons/xmark-circle.svg diff --git a/src/interface/interface_resources.qrc b/src/ifcviewer-full/interface_resources.qrc similarity index 100% rename from src/interface/interface_resources.qrc rename to src/ifcviewer-full/interface_resources.qrc diff --git a/src/ifcviewer-full/main.cpp b/src/ifcviewer-full/main.cpp index 8f861e3f31..c2e47ee685 100644 --- a/src/ifcviewer-full/main.cpp +++ b/src/ifcviewer-full/main.cpp @@ -1,3 +1,4 @@ +// This file was generated with the assistance of an AI coding tool. /******************************************************************************** * * * This file is part of IfcOpenShell. * @@ -17,18 +18,40 @@ * * ********************************************************************************/ -#include -#include -#include - #include "MainWindow.h" +#include "InterfaceSettings.h" +#include "components/Style.h" + +#include +#include +#include +#include +#include + +namespace { + +void installUiFont() { + const int font_id = QFontDatabase::addApplicationFont( + ":/fonts/DMSans-VariableFont_opsz,wght.ttf"); + QString family; + if (font_id >= 0) { + const QStringList families = QFontDatabase::applicationFontFamilies(font_id); + if (!families.isEmpty()) { + family = families.front(); + } + } + if (!family.isEmpty()) { + QApplication::setFont(QFont(family, 10)); + } +} + +} // namespace int main(int argc, char* argv[]) { QApplication app(argc, argv); app.setApplicationName("IfcViewer"); app.setOrganizationName("IfcOpenShell"); - // Request OpenGL 4.5 Core globally QSurfaceFormat fmt; fmt.setVersion(4, 5); fmt.setProfile(QSurfaceFormat::CoreProfile); @@ -38,39 +61,21 @@ int main(int argc, char* argv[]) { QSurfaceFormat::setDefaultFormat(fmt); QCommandLineParser parser; - parser.setApplicationDescription("IfcOpenShell IFC Viewer"); + parser.setApplicationDescription("IfcOpenShell IFC viewer"); parser.addHelpOption(); - parser.addPositionalArgument("files", - "IFC file(s) and/or one .ifcfed federation to open", - "[files...]"); - parser.addOption({{"c", "camera"}, - "Set camera: tx,ty,tz,dist,yaw,pitch", "params"}); - parser.addOption({{"b", "benchmark"}, - "Run N frames then print stats and exit", "frames"}); parser.process(app); - MainWindow window; + installUiFont(); + const auto applyStyle = [&app]() { + app.setStyleSheet(ifcinterface::components::style::buildAppStyleSheet()); + }; + applyStyle(); + QObject::connect(&ifcinterface::InterfaceSettings::instance(), + &ifcinterface::InterfaceSettings::themeChanged, + &app, + applyStyle); + + ifcinterface::shell::MainWindow window; window.show(); - - auto args = parser.positionalArguments(); - QStringList file_args; - QString fed_arg; - for (const auto& a : args) { - if (fed_arg.isEmpty() && a.endsWith(".ifcfed", Qt::CaseInsensitive)) { - fed_arg = a; - } else { - file_args << a; - } - } - if (!fed_arg.isEmpty()) window.openFederation(fed_arg); - if (!file_args.isEmpty()) window.addFiles(file_args); - - if (parser.isSet("camera")) { - window.setPendingCamera(parser.value("camera")); - } - if (parser.isSet("benchmark")) { - window.setPendingBenchmark(parser.value("benchmark").toInt()); - } - return app.exec(); } diff --git a/src/interface/modules/models/AddModelDialog.cpp b/src/ifcviewer-full/modules/models/AddModelDialog.cpp similarity index 100% rename from src/interface/modules/models/AddModelDialog.cpp rename to src/ifcviewer-full/modules/models/AddModelDialog.cpp diff --git a/src/interface/modules/models/AddModelDialog.h b/src/ifcviewer-full/modules/models/AddModelDialog.h similarity index 100% rename from src/interface/modules/models/AddModelDialog.h rename to src/ifcviewer-full/modules/models/AddModelDialog.h diff --git a/src/interface/modules/models/Controller.cpp b/src/ifcviewer-full/modules/models/Controller.cpp similarity index 99% rename from src/interface/modules/models/Controller.cpp rename to src/ifcviewer-full/modules/models/Controller.cpp index 17401f0c1a..00e8a9e63d 100644 --- a/src/interface/modules/models/Controller.cpp +++ b/src/ifcviewer-full/modules/models/Controller.cpp @@ -144,7 +144,7 @@ void ModelsPanelController::bindLoader(SceneLoader* loader) { connect(loader, &SceneLoader::loadError, this, [this, host = host_](uint32_t /*mid*/, const QString& message) { session_state_->setStatusMessage("Error", message); - QMessageBox::warning(host, "IfcInterfaceMockup", message); + QMessageBox::warning(host, "IfcViewer", message); }); connect(loader, &SceneLoader::allLoadsFinished, this, [this, loader]() { diff --git a/src/interface/modules/models/Controller.h b/src/ifcviewer-full/modules/models/Controller.h similarity index 100% rename from src/interface/modules/models/Controller.h rename to src/ifcviewer-full/modules/models/Controller.h diff --git a/src/interface/modules/models/Panel.cpp b/src/ifcviewer-full/modules/models/Panel.cpp similarity index 100% rename from src/interface/modules/models/Panel.cpp rename to src/ifcviewer-full/modules/models/Panel.cpp diff --git a/src/interface/modules/models/Panel.h b/src/ifcviewer-full/modules/models/Panel.h similarity index 100% rename from src/interface/modules/models/Panel.h rename to src/ifcviewer-full/modules/models/Panel.h diff --git a/src/interface/modules/models/SettingsDialog.cpp b/src/ifcviewer-full/modules/models/SettingsDialog.cpp similarity index 100% rename from src/interface/modules/models/SettingsDialog.cpp rename to src/ifcviewer-full/modules/models/SettingsDialog.cpp diff --git a/src/interface/modules/models/SettingsDialog.h b/src/ifcviewer-full/modules/models/SettingsDialog.h similarity index 100% rename from src/interface/modules/models/SettingsDialog.h rename to src/ifcviewer-full/modules/models/SettingsDialog.h diff --git a/src/interface/modules/models/SettingsView.cpp b/src/ifcviewer-full/modules/models/SettingsView.cpp similarity index 100% rename from src/interface/modules/models/SettingsView.cpp rename to src/ifcviewer-full/modules/models/SettingsView.cpp diff --git a/src/interface/modules/models/SettingsView.h b/src/ifcviewer-full/modules/models/SettingsView.h similarity index 100% rename from src/interface/modules/models/SettingsView.h rename to src/ifcviewer-full/modules/models/SettingsView.h diff --git a/src/interface/modules/models/Types.h b/src/ifcviewer-full/modules/models/Types.h similarity index 100% rename from src/interface/modules/models/Types.h rename to src/ifcviewer-full/modules/models/Types.h diff --git a/src/interface/modules/models/View.cpp b/src/ifcviewer-full/modules/models/View.cpp similarity index 100% rename from src/interface/modules/models/View.cpp rename to src/ifcviewer-full/modules/models/View.cpp diff --git a/src/interface/modules/models/View.h b/src/ifcviewer-full/modules/models/View.h similarity index 100% rename from src/interface/modules/models/View.h rename to src/ifcviewer-full/modules/models/View.h diff --git a/src/interface/modules/project/Controller.cpp b/src/ifcviewer-full/modules/project/Controller.cpp similarity index 100% rename from src/interface/modules/project/Controller.cpp rename to src/ifcviewer-full/modules/project/Controller.cpp diff --git a/src/interface/modules/project/Controller.h b/src/ifcviewer-full/modules/project/Controller.h similarity index 100% rename from src/interface/modules/project/Controller.h rename to src/ifcviewer-full/modules/project/Controller.h diff --git a/src/interface/modules/properties/Panel.cpp b/src/ifcviewer-full/modules/properties/Panel.cpp similarity index 100% rename from src/interface/modules/properties/Panel.cpp rename to src/ifcviewer-full/modules/properties/Panel.cpp diff --git a/src/interface/modules/properties/Panel.h b/src/ifcviewer-full/modules/properties/Panel.h similarity index 100% rename from src/interface/modules/properties/Panel.h rename to src/ifcviewer-full/modules/properties/Panel.h diff --git a/src/interface/modules/properties/Types.h b/src/ifcviewer-full/modules/properties/Types.h similarity index 100% rename from src/interface/modules/properties/Types.h rename to src/ifcviewer-full/modules/properties/Types.h diff --git a/src/interface/modules/properties/View.cpp b/src/ifcviewer-full/modules/properties/View.cpp similarity index 100% rename from src/interface/modules/properties/View.cpp rename to src/ifcviewer-full/modules/properties/View.cpp diff --git a/src/interface/modules/properties/View.h b/src/ifcviewer-full/modules/properties/View.h similarity index 100% rename from src/interface/modules/properties/View.h rename to src/ifcviewer-full/modules/properties/View.h diff --git a/src/interface/modules/settings/Dialog.cpp b/src/ifcviewer-full/modules/settings/Dialog.cpp similarity index 100% rename from src/interface/modules/settings/Dialog.cpp rename to src/ifcviewer-full/modules/settings/Dialog.cpp diff --git a/src/interface/modules/settings/Dialog.h b/src/ifcviewer-full/modules/settings/Dialog.h similarity index 100% rename from src/interface/modules/settings/Dialog.h rename to src/ifcviewer-full/modules/settings/Dialog.h diff --git a/src/interface/modules/spatial_hierarchy/Panel.cpp b/src/ifcviewer-full/modules/spatial_hierarchy/Panel.cpp similarity index 100% rename from src/interface/modules/spatial_hierarchy/Panel.cpp rename to src/ifcviewer-full/modules/spatial_hierarchy/Panel.cpp diff --git a/src/interface/modules/spatial_hierarchy/Panel.h b/src/ifcviewer-full/modules/spatial_hierarchy/Panel.h similarity index 100% rename from src/interface/modules/spatial_hierarchy/Panel.h rename to src/ifcviewer-full/modules/spatial_hierarchy/Panel.h diff --git a/src/interface/modules/spatial_hierarchy/Types.h b/src/ifcviewer-full/modules/spatial_hierarchy/Types.h similarity index 100% rename from src/interface/modules/spatial_hierarchy/Types.h rename to src/ifcviewer-full/modules/spatial_hierarchy/Types.h diff --git a/src/interface/modules/spatial_hierarchy/View.cpp b/src/ifcviewer-full/modules/spatial_hierarchy/View.cpp similarity index 100% rename from src/interface/modules/spatial_hierarchy/View.cpp rename to src/ifcviewer-full/modules/spatial_hierarchy/View.cpp diff --git a/src/interface/modules/spatial_hierarchy/View.h b/src/ifcviewer-full/modules/spatial_hierarchy/View.h similarity index 100% rename from src/interface/modules/spatial_hierarchy/View.h rename to src/ifcviewer-full/modules/spatial_hierarchy/View.h diff --git a/src/interface/modules/todo/Panel.cpp b/src/ifcviewer-full/modules/todo/Panel.cpp similarity index 100% rename from src/interface/modules/todo/Panel.cpp rename to src/ifcviewer-full/modules/todo/Panel.cpp diff --git a/src/interface/modules/todo/Panel.h b/src/ifcviewer-full/modules/todo/Panel.h similarity index 100% rename from src/interface/modules/todo/Panel.h rename to src/ifcviewer-full/modules/todo/Panel.h diff --git a/src/interface/modules/viewport/Controller.cpp b/src/ifcviewer-full/modules/viewport/Controller.cpp similarity index 99% rename from src/interface/modules/viewport/Controller.cpp rename to src/ifcviewer-full/modules/viewport/Controller.cpp index 8fd0b67dfa..454a266961 100644 --- a/src/interface/modules/viewport/Controller.cpp +++ b/src/ifcviewer-full/modules/viewport/Controller.cpp @@ -27,7 +27,7 @@ #include "../../../ifcviewer/SceneLoader.h" #include "../../../ifcviewer/ViewportWindow.h" #include "../../../ifcviewer/OverlayRenderer.h" -#include "../../../ifcviewer-full/Measurement.h" +#include "../../Measurement.h" #include #include diff --git a/src/interface/modules/viewport/Controller.h b/src/ifcviewer-full/modules/viewport/Controller.h similarity index 100% rename from src/interface/modules/viewport/Controller.h rename to src/ifcviewer-full/modules/viewport/Controller.h diff --git a/src/interface/modules/viewport/Panel.cpp b/src/ifcviewer-full/modules/viewport/Panel.cpp similarity index 100% rename from src/interface/modules/viewport/Panel.cpp rename to src/ifcviewer-full/modules/viewport/Panel.cpp diff --git a/src/interface/modules/viewport/Panel.h b/src/ifcviewer-full/modules/viewport/Panel.h similarity index 100% rename from src/interface/modules/viewport/Panel.h rename to src/ifcviewer-full/modules/viewport/Panel.h diff --git a/src/interface/CMakeLists.txt b/src/interface/CMakeLists.txt deleted file mode 100644 index 0a6593dcfb..0000000000 --- a/src/interface/CMakeLists.txt +++ /dev/null @@ -1,107 +0,0 @@ -# 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 . # -# # -################################################################################ - -message("Running CMakeLists.txt in /src/interface") - -set(QT_VERSION 6 CACHE STRING "Qt version") -find_package(Qt${QT_VERSION} COMPONENTS Core Gui Widgets Svg REQUIRED PATHS ${QT_DIR}) - -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}/InterfaceSettings.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/InterfaceSettings.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 - ${CMAKE_CURRENT_SOURCE_DIR}/components/SvgIcon.h - ${CMAKE_CURRENT_SOURCE_DIR}/components/Style.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/components/Style.h - ${CMAKE_CURRENT_SOURCE_DIR}/components/Dialog.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/components/Dialog.h - ${CMAKE_CURRENT_SOURCE_DIR}/components/Tabs.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/components/Tabs.h - ${CMAKE_CURRENT_SOURCE_DIR}/components/KeyValueTable.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/components/KeyValueTable.h - ${CMAKE_CURRENT_SOURCE_DIR}/components/Buttons.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/components/Buttons.h - ${CMAKE_CURRENT_SOURCE_DIR}/components/Section.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/components/Section.h - ${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/AddModelDialog.cpp - ${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 - ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Panel.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Panel.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/View.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modules/models/View.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/todo/Panel.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modules/todo/Panel.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/Types.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/Panel.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/Panel.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/View.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/View.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/project/Controller.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modules/project/Controller.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/settings/Dialog.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modules/settings/Dialog.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/Types.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/Panel.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/Panel.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/View.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/View.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Panel.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Panel.h - ${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Controller.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Controller.h - ${CMAKE_CURRENT_SOURCE_DIR}/../ifcviewer-full/Measurement.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../ifcviewer-full/Measurement.h - ${CMAKE_CURRENT_SOURCE_DIR}/interface_resources.qrc -) - -add_executable(IfcInterfaceMockup ${INTERFACE_FILES}) - -set_target_properties(IfcInterfaceMockup PROPERTIES - AUTOMOC ON - AUTORCC ON - WIN32_EXECUTABLE ON - MACOSX_BUNDLE ON -) - -target_link_libraries(IfcInterfaceMockup PRIVATE - IfcViewer - Qt${QT_VERSION}::Core - Qt${QT_VERSION}::Gui - Qt${QT_VERSION}::Svg - Qt${QT_VERSION}::Widgets -) - -install(TARGETS IfcInterfaceMockup EXPORT ${IFCOPENSHELL_EXPORT_TARGETS}) diff --git a/src/interface/MainWindow.cpp b/src/interface/MainWindow.cpp deleted file mode 100644 index a85a6197df..0000000000 --- a/src/interface/MainWindow.cpp +++ /dev/null @@ -1,531 +0,0 @@ -// 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 "MainWindow.h" - -#include "../ifcviewer/AppSettings.h" -#include "../ifcviewer/Federation.h" -#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 "modules/models/Controller.h" -#include "modules/todo/Panel.h" -#include "modules/models/View.h" -#include "modules/models/Panel.h" -#include "modules/project/Controller.h" -#include "modules/properties/View.h" -#include "modules/properties/Panel.h" -#include "modules/settings/Dialog.h" -#include "modules/spatial_hierarchy/View.h" -#include "modules/spatial_hierarchy/Panel.h" -#include "modules/viewport/Controller.h" -#include "modules/viewport/Panel.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ifcinterface::shell { - -MainWindow::MainWindow(QWidget* parent) - : QMainWindow(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(); - }); - connect(session_state_, &ifcinterface::SessionState::modelsChanged, - this, &MainWindow::updateWindowTitle); - connect(session_state_, &ifcinterface::SessionState::projectOpened, - this, [this](const QString&) { updateWindowTitle(); }); - connect(session_state_, &ifcinterface::SessionState::projectSaved, - this, [this](const QString&) { updateWindowTitle(); }); - connect(session_state_, &ifcinterface::SessionState::projectReset, - this, &MainWindow::updateWindowTitle); - setupChrome(); - setupViewport(); - setupPanels(); - setupStatus(); - setupLoader(); - setupRibbon(); - resize(1720, 980); -} - -void MainWindow::setupChrome() { - setObjectName("appWindow"); - setDockOptions(QMainWindow::AllowNestedDocks | - QMainWindow::AllowTabbedDocks | - QMainWindow::GroupedDragging); - - auto bind_shortcut = [this](const QKeySequence& sequence, auto fn) { - auto* shortcut = new QShortcut(sequence, this); - shortcut->setContext(Qt::WindowShortcut); - connect(shortcut, &QShortcut::activated, this, fn); - }; - bind_shortcut(QKeySequence("Ctrl+Shift+L"), [this]() { - viewport_controller_->toggleDistanceMode(); - }); - bind_shortcut(QKeySequence("Ctrl+Shift+A"), [this]() { - viewport_controller_->toggleAreaMode(); - }); - bind_shortcut(QKeySequence("Ctrl+Shift+V"), [this]() { - viewport_controller_->toggleVolumeMode(); - }); - bind_shortcut(QKeySequence(Qt::SHIFT | Qt::Key_F), [this]() { - viewport_controller_->setFlyMode(); - }); - bind_shortcut(QKeySequence(Qt::Key_K), [this]() { - viewport_controller_->toggleSectionMode(); - }); - bind_shortcut(QKeySequence(Qt::SHIFT | Qt::Key_K), [this]() { - viewport_controller_->clearSectionPlanes(); - }); - bind_shortcut(QKeySequence(Qt::Key_H), [this]() { - viewport_controller_->hideSelectedElements(); - }); - bind_shortcut(QKeySequence(Qt::SHIFT | Qt::Key_H), [this]() { - viewport_controller_->isolateSelectedElements(); - }); - bind_shortcut(QKeySequence(Qt::ALT | Qt::Key_H), [this]() { - viewport_controller_->showAllElements(); - }); - updateWindowTitle(); -} - -QToolButton* MainWindow::makePanelToggle(const QString& text, QDockWidget* dock) { - auto* button = components::buttons::makeButton(text, ":/icons/sidebar-expand.svg", this); - button->setCheckable(true); - button->setChecked(dock->isVisible()); - connect(button, &QToolButton::toggled, dock, [dock](bool checked) { - dock->setVisible(checked); - if (checked) dock->raise(); - }); - connect(dock, &QDockWidget::visibilityChanged, button, [button](bool visible) { - const QSignalBlocker blocker(button); - button->setChecked(visible); - }); - return button; -} - -QWidget* MainWindow::buildHomeRibbonPage() { - auto* page = new QFrame(this); - page->setObjectName("ribbonPage"); - auto* row = new QHBoxLayout(page); - row->setContentsMargins(6, 4, 6, 4); - row->setSpacing(0); - - auto* new_project = components::buttons::makeButton("New Project", ":/icons/plus-square.svg", this); - connect(new_project, &QToolButton::clicked, this, [this]() { - project_controller_->newProject(); - }); - auto* open_project = components::buttons::makeButton("Open Project", ":/icons/download-square.svg", this); - connect(open_project, &QToolButton::clicked, this, [this]() { - project_controller_->openProject(); - }); - auto* open_cloud = components::buttons::makeButton("Open Cloud", ":/icons/cloud-square.svg", this); - connect(open_cloud, &QToolButton::clicked, this, [this]() { - session_state_->setStatusMessage("Project", "Open Cloud Project coming soon"); - }); - auto* open_recent = components::buttons::makeButton("Open Recent", ":/icons/clock-rotate-right.svg", this); - connect(open_recent, &QToolButton::clicked, this, [this]() { - session_state_->setStatusMessage("Project", "Open Recent coming soon"); - }); - auto* save_project = components::buttons::makeButton("Save Project", ":/icons/floppy-disk.svg", this); - connect(save_project, &QToolButton::clicked, this, [this]() { - project_controller_->saveProject(); - }); - auto* save_project_as = components::buttons::makeButton("Save As", ":/icons/floppy-disk-arrow-in.svg", this); - connect(save_project_as, &QToolButton::clicked, this, [this]() { - project_controller_->saveProjectAs(); - }); - - auto* add_model = components::buttons::makeButton("Add Model", ":/icons/cube.svg", this); - connect(add_model, &QToolButton::clicked, this, [this]() { - models_controller_->addFiles(); - }); - auto* sync_models = components::buttons::makeButton("Sync Models", ":/icons/refresh-double.svg", this); - connect(sync_models, &QToolButton::clicked, this, [this]() { - session_state_->setStatusMessage("Models", "Sync models coming soon"); - }); - - auto* settings_button = components::buttons::makeButton("Settings", ":/icons/settings.svg", this); - connect(settings_button, &QToolButton::clicked, this, [this]() { - modules::settings::SettingsDialog dialog(this); - dialog.exec(); - }); - - row->addWidget(components::buttons::makeButtonGroup("PROJECT", {new_project, open_project, open_cloud, open_recent, save_project, save_project_as}, this)); - row->addWidget(components::buttons::makeButtonGroup("MODELS", {add_model, sync_models}, this)); - row->addWidget(components::buttons::makeButtonGroup("SETTINGS", {settings_button}, this)); - row->addStretch(1); - return page; -} - -QWidget* MainWindow::buildNavigateRibbonPage() { - auto* page = new QFrame(this); - page->setObjectName("ribbonPage"); - auto* row = new QHBoxLayout(page); - row->setContentsMargins(2, 4, 2, 4); - row->setSpacing(0); - - auto* set_home = components::buttons::makeButton("Set Home", ":/icons/home.svg", this); - connect(set_home, &QToolButton::clicked, this, [this]() { - viewport_controller_->setHomeView(); - }); - auto* go_home = components::buttons::makeButton("Go Home", ":/icons/home-alt.svg", this); - connect(go_home, &QToolButton::clicked, this, [this]() { - viewport_controller_->goHomeView(); - }); - auto* view_all = components::buttons::makeButton("View All", ":/icons/cube-scan.svg", this); - connect(view_all, &QToolButton::clicked, this, [this]() { - if (viewport_widget_) viewport_widget_->viewport()->viewAll(); - }); - auto* view_selected = components::buttons::makeButton("View Selected", ":/icons/cube-scan-solid.svg", this); - connect(view_selected, &QToolButton::clicked, this, [this]() { - viewport_controller_->focusSelectedObject(); - }); - - auto* plan_view = components::buttons::makeButton("Plan", ":/icons/planimetry.svg", this); - connect(plan_view, &QToolButton::clicked, this, [this]() { - if (viewport_widget_) viewport_widget_->viewport()->setStandardView(90.0f, 90.0f); - }); - auto* front_view = components::buttons::makeButton("Front", ":/icons/city.svg", this); - connect(front_view, &QToolButton::clicked, this, [this]() { - if (viewport_widget_) viewport_widget_->viewport()->setStandardView(0.0f, 0.0f); - }); - auto* side_view = components::buttons::makeButton("Side", ":/icons/building.svg", this); - connect(side_view, &QToolButton::clicked, this, [this]() { - if (viewport_widget_) viewport_widget_->viewport()->setStandardView(90.0f, 0.0f); - }); - auto* align_object = components::buttons::makeButton("Align Object", ":/icons/cellar.svg", this); - connect(align_object, &QToolButton::clicked, this, [this]() { - session_state_->setStatusMessage("Orientation", "Align to object coming soon"); - }); - auto* projection_button = components::buttons::makeButton("Perspective", ":/icons/perspective-view.svg", this); - connect(projection_button, &QToolButton::clicked, this, [this, projection_button]() { - if (!viewport_widget_) return; - viewport_widget_->viewport()->toggleProjection(); - projection_button->setText( - viewport_widget_->viewport()->projectionOrtho() ? "Ortho" : "Perspective"); - }); - - auto* fly_mode = components::buttons::makeButton("Fly", ":/icons/drone.svg", this); - connect(fly_mode, &QToolButton::clicked, this, [this]() { - viewport_controller_->setFlyMode(); - }); - auto* section_mode = components::buttons::makeButton("Section", ":/icons/cube-cut-with-curve.svg", this); - connect(section_mode, &QToolButton::clicked, this, [this]() { - viewport_controller_->toggleSectionMode(); - }); - - row->addWidget(components::buttons::makeButtonGroup("CAMERA", {set_home, go_home, view_all, view_selected}, this)); - row->addWidget(components::buttons::makeButtonGroup("ORIENTATION", {plan_view, front_view, side_view, align_object, projection_button}, this)); - row->addWidget(components::buttons::makeButtonGroup("MODE", {fly_mode, section_mode}, this)); - row->addStretch(1); - return page; -} - -QWidget* MainWindow::buildInspectRibbonPage() { - auto* page = new QFrame(this); - page->setObjectName("ribbonPage"); - auto* row = new QHBoxLayout(page); - row->setContentsMargins(2, 4, 2, 4); - row->setSpacing(0); - - auto* hide_selected = components::buttons::makeButton("Hide", ":/icons/eye-closed.svg", this); - connect(hide_selected, &QToolButton::clicked, this, [this]() { - viewport_controller_->hideSelectedElements(); - }); - auto* isolate_selected = components::buttons::makeButton("Isolate", ":/icons/eye-solid.svg", this); - connect(isolate_selected, &QToolButton::clicked, this, [this]() { - viewport_controller_->isolateSelectedElements(); - }); - auto* show_all = components::buttons::makeButton("Show All", ":/icons/eye.svg", this); - connect(show_all, &QToolButton::clicked, this, [this]() { - viewport_controller_->showAllElements(); - }); - auto* invert_selection = components::buttons::makeButton("Invert", ":/icons/intersect.svg", this); - connect(invert_selection, &QToolButton::clicked, this, [this]() { - viewport_controller_->invertSelection(); - }); - - auto* distance = components::buttons::makeButton("Distance", ":/icons/select-edge3d.svg", this); - connect(distance, &QToolButton::clicked, this, [this]() { - viewport_controller_->toggleDistanceMode(); - }); - auto* area = components::buttons::makeButton("Area", ":/icons/select-face3d.svg", this); - connect(area, &QToolButton::clicked, this, [this]() { - viewport_controller_->toggleAreaMode(); - }); - auto* volume = components::buttons::makeButton("Volume", ":/icons/select-point3d.svg", this); - connect(volume, &QToolButton::clicked, this, [this]() { - viewport_controller_->toggleVolumeMode(); - }); - - row->addWidget(components::buttons::makeButtonGroup("SELECTION", {hide_selected, isolate_selected, show_all, invert_selection}, this)); - row->addWidget(components::buttons::makeButtonGroup("MEASURE", {distance, area, volume}, this)); - row->addStretch(1); - return page; -} - -QWidget* MainWindow::buildPanelsRibbonPage() { - auto* page = new QFrame(this); - page->setObjectName("ribbonPage"); - auto* row = new QHBoxLayout(page); - row->setContentsMargins(2, 4, 2, 4); - row->setSpacing(0); - - row->addWidget(components::buttons::makeButtonGroup("DATA", { - makePanelToggle("Models", models_panel_), - makePanelToggle("Spatial", spatial_panel_), - makePanelToggle("Layers", layers_panel_), - makePanelToggle("Properties", properties_panel_) - }, this)); - row->addWidget(components::buttons::makeButtonGroup("QUERY", { - makePanelToggle("Views", stored_views_panel_), - makePanelToggle("Search", search_panel_), - makePanelToggle("Sheets", spreadsheet_panel_), - makePanelToggle("Audit", audit_panel_) - }, this)); - row->addWidget(components::buttons::makeButtonGroup("COLLABORATE", { - makePanelToggle("Clash", clash_panel_), - makePanelToggle("Issues", issues_panel_) - }, this)); - row->addStretch(1); - return page; -} - -void MainWindow::setupRibbon() { - auto* shell = new QFrame(this); - - auto* shell_layout = new QVBoxLayout(shell); - shell_layout->setContentsMargins(0, 0, 0, 0); - shell_layout->setSpacing(0); - - ribbon_tabs_ = new components::TabBar(shell); - ribbon_tabs_->addTab("Home"); - ribbon_tabs_->addTab("Navigate"); - ribbon_tabs_->addTab("Inspect"); - ribbon_tabs_->addTab("Panels"); - ribbon_tabs_->setCurrentIndex(0); - - auto* ribbon_band = new QFrame(shell); - ribbon_band->setObjectName("ribbonBand"); - auto* band_layout = new QVBoxLayout(ribbon_band); - band_layout->setContentsMargins(0, 0, 0, 0); - band_layout->setSpacing(0); - - ribbon_pages_ = new QStackedWidget(ribbon_band); - ribbon_pages_->addWidget(buildHomeRibbonPage()); - ribbon_pages_->addWidget(buildNavigateRibbonPage()); - ribbon_pages_->addWidget(buildInspectRibbonPage()); - ribbon_pages_->addWidget(buildPanelsRibbonPage()); - - band_layout->addWidget(ribbon_pages_); - shell_layout->addWidget(ribbon_tabs_); - shell_layout->addWidget(ribbon_band); - - connect(ribbon_tabs_, &components::TabBar::currentChanged, - ribbon_pages_, &QStackedWidget::setCurrentIndex); - - setMenuWidget(shell); -} - -void MainWindow::setupViewport() { - viewport_widget_ = new modules::viewport::ViewportPanel(this); - setCentralWidget(viewport_widget_); -} - -void MainWindow::setupPanels() { - models_panel_ = new modules::models::ModelsPanel(this); - spatial_panel_ = new modules::spatial_hierarchy::SpatialHierarchyPanel(this); - properties_panel_ = new modules::properties::PropertiesPanel(this); - - models_controller_ = new modules::models::ModelsPanelController( - this, models_panel_, session_state_, viewport_widget_->viewport(), this); - models_view_ = new modules::models::ModelsPanelView(models_panel_, session_state_, this); - spatial_view_ = new modules::spatial_hierarchy::SpatialHierarchyPanelView(spatial_panel_, session_state_, this); - properties_view_ = new modules::properties::PropertiesPanelView(properties_panel_, session_state_, this); - - layers_panel_ = new components::Panel("Layers", new modules::todo::TodoPanel("Layers", this), this); - stored_views_panel_ = new components::Panel( - "Stored Views", new modules::todo::TodoPanel("Stored Views", this), this); - search_panel_ = new components::Panel( - "Search and Query", new modules::todo::TodoPanel("Search and Query", this), this); - spreadsheet_panel_ = new components::Panel( - "Spreadsheet", new modules::todo::TodoPanel("Spreadsheet", this), this); - audit_panel_ = new components::Panel("Audit", new modules::todo::TodoPanel("Audit", this), this); - clash_panel_ = new components::Panel("Clash", new modules::todo::TodoPanel("Clash", this), this); - issues_panel_ = new components::Panel("Issues", new modules::todo::TodoPanel("Issues", this), this); - - addDockWidget(Qt::LeftDockWidgetArea, models_panel_); - addDockWidget(Qt::LeftDockWidgetArea, spatial_panel_); - splitDockWidget(models_panel_, spatial_panel_, Qt::Vertical); - - addDockWidget(Qt::RightDockWidgetArea, properties_panel_); - addDockWidget(Qt::RightDockWidgetArea, layers_panel_); - addDockWidget(Qt::RightDockWidgetArea, stored_views_panel_); - addDockWidget(Qt::RightDockWidgetArea, search_panel_); - addDockWidget(Qt::RightDockWidgetArea, spreadsheet_panel_); - addDockWidget(Qt::RightDockWidgetArea, audit_panel_); - addDockWidget(Qt::RightDockWidgetArea, clash_panel_); - addDockWidget(Qt::RightDockWidgetArea, issues_panel_); - - tabifyDockWidget(properties_panel_, layers_panel_); - tabifyDockWidget(layers_panel_, stored_views_panel_); - tabifyDockWidget(stored_views_panel_, search_panel_); - tabifyDockWidget(search_panel_, spreadsheet_panel_); - tabifyDockWidget(spreadsheet_panel_, audit_panel_); - tabifyDockWidget(audit_panel_, clash_panel_); - tabifyDockWidget(clash_panel_, issues_panel_); - properties_panel_->raise(); - - layers_panel_->hide(); - stored_views_panel_->hide(); - search_panel_->hide(); - spreadsheet_panel_->hide(); - audit_panel_->hide(); - clash_panel_->hide(); - issues_panel_->hide(); - - resizeDocks({models_panel_, properties_panel_}, {290, 330}, Qt::Horizontal); - resizeDocks({models_panel_, spatial_panel_}, {280, 240}, Qt::Vertical); -} - -void MainWindow::setupStatus() { - status_mode_label_ = new QLabel("Ready", this); - status_selection_label_ = new QLabel("No selection", this); - status_perf_label_ = new QLabel(this); - status_progress_bar_ = new QProgressBar(this); - status_perf_label_->setVisible(AppSettings::instance().showStats()); - status_progress_bar_->setMaximumWidth(200); - status_progress_bar_->setVisible(false); - - statusBar()->setSizeGripEnabled(false); - statusBar()->addWidget(status_mode_label_); - statusBar()->addWidget(status_selection_label_, 1); - statusBar()->addPermanentWidget(status_perf_label_); - statusBar()->addPermanentWidget(status_progress_bar_); - - connect(&AppSettings::instance(), &AppSettings::showStatsChanged, this, [this](bool show) { - 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_); - models_controller_->bindLoader(loader_); - viewport_controller_ = new modules::viewport::ViewportController( - session_state_, viewport_widget_->viewport(), this); - project_controller_ = new modules::project::ProjectController( - this, federation_, session_state_, element_registry_, - viewport_widget_->viewport(), models_controller_, viewport_controller_, this); - - connect(loader_, &SceneLoader::loadStarted, this, - [this](uint32_t /*mid*/, const QString& /*display_name*/) { - status_progress_bar_->setValue(0); - status_progress_bar_->setVisible(true); - }); - connect(loader_, &SceneLoader::progressChanged, this, - [this](int percent) { - status_progress_bar_->setValue(percent); - }); - - auto hide_progress = [this]() { - status_progress_bar_->setVisible(false); - }; - connect(loader_, &SceneLoader::loadedFromSidecar, this, - [hide_progress](uint32_t /*mid*/, qint64 /*elapsed_ms*/) { - hide_progress(); - }); - connect(loader_, &SceneLoader::loadedFromStream, this, - [hide_progress](uint32_t /*mid*/, qint64 /*elapsed_ms*/) { - hide_progress(); - }); - connect(loader_, &SceneLoader::loadCancelled, this, - [hide_progress](uint32_t /*mid*/) { - hide_progress(); - }); - connect(loader_, &SceneLoader::loadError, this, - [hide_progress](uint32_t /*mid*/, const QString& /*message*/) { - hide_progress(); - }); - - connect(viewport_widget_->viewport(), &ViewportWindow::frameStatsUpdated, this, - [this](const ViewportWindow::FrameStats& s) { - if (!status_perf_label_->isVisible()) return; - status_perf_label_->setText( - QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 draws") - .arg(s.fps, 0, 'f', 1) - .arg(s.frame_time_ms, 0, 'f', 1) - .arg(s.visible_objects) - .arg(s.total_objects) - .arg(s.visible_triangles) - .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::updateWindowTitle() { - const QString project_path = federation_ ? federation_->filePath() : QString(); - if (project_path.isEmpty() && (!federation_ || federation_->models().empty())) { - setWindowTitle("IfcOpenShell Interface"); - } else if (project_path.isEmpty()) { - setWindowTitle("untitled[*] - IfcOpenShell Interface"); - } else { - setWindowTitle(QFileInfo(project_path).fileName() + "[*] - IfcOpenShell Interface"); - } -} - -} // namespace ifcinterface::shell diff --git a/src/interface/MainWindow.h b/src/interface/MainWindow.h deleted file mode 100644 index 1439d8bf91..0000000000 --- a/src/interface/MainWindow.h +++ /dev/null @@ -1,102 +0,0 @@ -// 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_SHELL_MAINWINDOW_H -#define IFCINTERFACE_SHELL_MAINWINDOW_H - -#include -#include -#include - -class QLabel; -class QDockWidget; -class QProgressBar; -class QStackedWidget; -class QToolButton; -class Federation; -class SceneLoader; -namespace ifcinterface { class ElementRegistry; } -namespace ifcinterface { class SessionState; } -namespace ifcinterface::components { class TabBar; } -namespace ifcinterface::modules::models { class ModelsPanelController; } -namespace ifcinterface::modules::models { class ModelsPanel; } -namespace ifcinterface::modules::models { class ModelsPanelView; } -namespace ifcinterface::modules::project { class ProjectController; } -namespace ifcinterface::modules::spatial_hierarchy { class SpatialHierarchyPanel; } -namespace ifcinterface::modules::spatial_hierarchy { class SpatialHierarchyPanelView; } -namespace ifcinterface::modules::properties { class PropertiesPanel; } -namespace ifcinterface::modules::properties { class PropertiesPanelView; } -namespace ifcinterface::modules::viewport { class ViewportController; } -namespace ifcinterface::modules::viewport { class ViewportPanel; } - -namespace ifcinterface::shell { - -class MainWindow : public QMainWindow { - Q_OBJECT -public: - explicit MainWindow(QWidget* parent = nullptr); - -private: - void setupChrome(); - void setupRibbon(); - void setupViewport(); - void setupPanels(); - void setupStatus(); - void setupLoader(); - QWidget* buildHomeRibbonPage(); - QWidget* buildNavigateRibbonPage(); - QWidget* buildInspectRibbonPage(); - QWidget* buildPanelsRibbonPage(); - void updateWindowTitle(); - QToolButton* makePanelToggle(const QString& text, QDockWidget* dock); - -private: - Federation* federation_ = nullptr; - QLabel* status_mode_label_ = nullptr; - QLabel* status_selection_label_ = nullptr; - QLabel* status_perf_label_ = nullptr; - QProgressBar* status_progress_bar_ = nullptr; - ifcinterface::components::TabBar* ribbon_tabs_ = nullptr; - QStackedWidget* ribbon_pages_ = nullptr; - ifcinterface::modules::viewport::ViewportPanel* viewport_widget_ = nullptr; - ifcinterface::modules::viewport::ViewportController* viewport_controller_ = nullptr; - SceneLoader* loader_ = nullptr; - ifcinterface::ElementRegistry* element_registry_ = nullptr; - ifcinterface::SessionState* session_state_ = nullptr; - ifcinterface::modules::models::ModelsPanel* models_panel_ = nullptr; - ifcinterface::modules::spatial_hierarchy::SpatialHierarchyPanel* spatial_panel_ = nullptr; - QDockWidget* layers_panel_ = nullptr; - ifcinterface::modules::properties::PropertiesPanel* properties_panel_ = nullptr; - QDockWidget* stored_views_panel_ = nullptr; - QDockWidget* search_panel_ = nullptr; - QDockWidget* spreadsheet_panel_ = nullptr; - QDockWidget* audit_panel_ = nullptr; - QDockWidget* clash_panel_ = nullptr; - QDockWidget* issues_panel_ = nullptr; - ifcinterface::modules::models::ModelsPanelController* models_controller_ = nullptr; - ifcinterface::modules::models::ModelsPanelView* models_view_ = nullptr; - ifcinterface::modules::project::ProjectController* project_controller_ = nullptr; - ifcinterface::modules::spatial_hierarchy::SpatialHierarchyPanelView* spatial_view_ = nullptr; - ifcinterface::modules::properties::PropertiesPanelView* properties_view_ = nullptr; -}; - -} // namespace ifcinterface::shell - -#endif diff --git a/src/interface/main.cpp b/src/interface/main.cpp deleted file mode 100644 index 3c574fef1f..0000000000 --- a/src/interface/main.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// 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 "MainWindow.h" -#include "InterfaceSettings.h" -#include "components/Style.h" - -#include -#include -#include -#include -#include - -namespace { - -void installUiFont() { - const int font_id = QFontDatabase::addApplicationFont( - ":/fonts/DMSans-VariableFont_opsz,wght.ttf"); - QString family; - if (font_id >= 0) { - const QStringList families = QFontDatabase::applicationFontFamilies(font_id); - if (!families.isEmpty()) { - family = families.front(); - } - } - if (!family.isEmpty()) { - QApplication::setFont(QFont(family, 10)); - } -} - -} // namespace - -int main(int argc, char* argv[]) { - QApplication app(argc, argv); - app.setApplicationName("IfcInterfaceMockup"); - app.setOrganizationName("IfcOpenShell"); - - QSurfaceFormat fmt; - fmt.setVersion(4, 5); - fmt.setProfile(QSurfaceFormat::CoreProfile); - fmt.setDepthBufferSize(24); - fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer); - fmt.setSamples(4); - QSurfaceFormat::setDefaultFormat(fmt); - - QCommandLineParser parser; - parser.setApplicationDescription("IfcOpenShell interface mockup with live viewport"); - parser.addHelpOption(); - parser.process(app); - - installUiFont(); - const auto applyStyle = [&app]() { - app.setStyleSheet(ifcinterface::components::style::buildAppStyleSheet()); - }; - applyStyle(); - QObject::connect(&ifcinterface::InterfaceSettings::instance(), - &ifcinterface::InterfaceSettings::themeChanged, - &app, - applyStyle); - - ifcinterface::shell::MainWindow window; - window.show(); - return app.exec(); -}