diff --git a/src/interface/CMakeLists.txt b/src/interface/CMakeLists.txt
index 015aac0da6..370dabb600 100644
--- a/src/interface/CMakeLists.txt
+++ b/src/interface/CMakeLists.txt
@@ -25,14 +25,20 @@ find_package(Qt${QT_VERSION} COMPONENTS Core Gui Widgets Svg REQUIRED PATHS ${QT
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}/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/KeyValueTable.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/components/KeyValueTable.h
${CMAKE_CURRENT_SOURCE_DIR}/components/Section.cpp
${CMAKE_CURRENT_SOURCE_DIR}/components/Section.h
- ${CMAKE_CURRENT_SOURCE_DIR}/components/PanelChrome.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/components/PanelChrome.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.h
${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelTypes.h
${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelWidget.cpp
${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelWidget.h
diff --git a/src/interface/ElementRegistry.cpp b/src/interface/ElementRegistry.cpp
new file mode 100644
index 0000000000..1e41385d33
--- /dev/null
+++ b/src/interface/ElementRegistry.cpp
@@ -0,0 +1,82 @@
+// 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 "ElementRegistry.h"
+
+#include "../ifcviewer/GeometryStreamer.h"
+#include "../ifcviewer/SceneLoader.h"
+#include "../ifcviewer/SidecarCache.h"
+
+namespace ifcinterface {
+
+ElementRegistry::ElementRegistry(QObject* parent)
+ : QObject(parent)
+{
+}
+
+void ElementRegistry::bindLoader(SceneLoader* loader) {
+ connect(loader, &SceneLoader::sidecarElementsReady,
+ this, &ElementRegistry::onSidecarElementsReady);
+ connect(loader, &SceneLoader::streamedElementsReady,
+ this, &ElementRegistry::onStreamedElementsReady);
+}
+
+std::optional ElementRegistry::find(uint32_t object_id) const {
+ auto it = elements_.find(object_id);
+ if (it == elements_.end()) return std::nullopt;
+ return it->second;
+}
+
+void ElementRegistry::onSidecarElementsReady(uint32_t /*mid*/,
+ std::vector elements,
+ std::string string_table) {
+ auto str = [&](uint32_t offset, uint32_t length) -> QString {
+ if (length == 0 || offset + length > string_table.size()) return {};
+ return QString::fromStdString(string_table.substr(offset, length));
+ };
+
+ for (const auto& pe : elements) {
+ BasicElementInfo 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);
+ elements_[info.object_id] = info;
+ }
+}
+
+void ElementRegistry::onStreamedElementsReady(uint32_t /*mid*/, std::vector elements) {
+ for (const auto& e : elements) {
+ BasicElementInfo info;
+ info.object_id = e.object_id;
+ info.model_id = e.model_id;
+ info.ifc_id = e.ifc_id;
+ info.parent_id = e.parent_id;
+ info.guid = QString::fromStdString(e.guid);
+ info.name = QString::fromStdString(e.name);
+ info.type = QString::fromStdString(e.type);
+ elements_[info.object_id] = info;
+ }
+}
+
+} // namespace ifcinterface
diff --git a/src/interface/ElementRegistry.h b/src/interface/ElementRegistry.h
new file mode 100644
index 0000000000..471e53f31f
--- /dev/null
+++ b/src/interface/ElementRegistry.h
@@ -0,0 +1,66 @@
+// 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_ELEMENTREGISTRY_H
+#define IFCINTERFACE_ELEMENTREGISTRY_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class SceneLoader;
+struct PackedElementInfo;
+struct ElementInfo;
+
+namespace ifcinterface {
+
+struct BasicElementInfo {
+ uint32_t object_id = 0;
+ uint32_t model_id = 0;
+ int ifc_id = 0;
+ int parent_id = 0;
+ QString guid;
+ QString name;
+ QString type;
+};
+
+class ElementRegistry : public QObject {
+ Q_OBJECT
+public:
+ explicit ElementRegistry(QObject* parent = nullptr);
+
+ void bindLoader(SceneLoader* loader);
+ std::optional find(uint32_t object_id) const;
+
+private:
+ void onSidecarElementsReady(uint32_t mid,
+ std::vector elements,
+ std::string string_table);
+ void onStreamedElementsReady(uint32_t mid, std::vector elements);
+
+ std::unordered_map elements_;
+};
+
+} // namespace ifcinterface
+
+#endif
diff --git a/src/interface/MainWindow.cpp b/src/interface/MainWindow.cpp
index 1f0fb7b021..28907cc495 100644
--- a/src/interface/MainWindow.cpp
+++ b/src/interface/MainWindow.cpp
@@ -23,7 +23,9 @@
#include "../ifcviewer/AppSettings.h"
#include "../ifcviewer/SceneLoader.h"
#include "../ifcviewer/ViewportWindow.h"
-#include "components/PanelChrome.h"
+#include "ElementRegistry.h"
+#include "components/Panel.h"
+#include "components/Style.h"
#include "components/SvgIcon.h"
#include "panels/todo/TodoPanelWidget.h"
#include "panels/models/ModelsPanelView.h"
@@ -52,9 +54,10 @@ namespace ifcinterface::shell {
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
{
+ element_registry_ = new ifcinterface::ElementRegistry(this);
setupChrome();
setupViewport();
- setupDocks();
+ setupPanels();
setupStatus();
setupLoader();
setupRibbon();
@@ -66,280 +69,7 @@ void MainWindow::setupChrome() {
setDockOptions(QMainWindow::AllowNestedDocks |
QMainWindow::AllowTabbedDocks |
QMainWindow::GroupedDragging);
-
- setStyleSheet(R"(
- QMainWindow {
- background: #26292f;
- }
- QWidget {
- color: #d0d5dd;
- background: #26292f;
- selection-background-color: #39b54a;
- selection-color: #14161a;
- }
- QFrame#ribbonShell {
- background: #2d3138;
- border-bottom: 1px solid #1b1d22;
- }
- QTabBar::tab {
- background: transparent;
- color: #8d97a7;
- padding: 8px 14px;
- margin-right: 2px;
- border-bottom: 2px solid transparent;
- }
- QTabBar::tab:selected {
- color: #f2f5fa;
- border-bottom: 2px solid #39b54a;
- }
- QTabBar::tab:hover {
- color: #ffffff;
- }
- QFrame#ribbonBand {
- background: #31353d;
- border-top: 1px solid #3b4048;
- }
- QFrame#ribbonPage {
- background: transparent;
- }
- QFrame#ribbonGroup {
- background: transparent;
- border-right: 1px solid #434852;
- }
- QLabel#ribbonGroupLabel {
- color: #7f8796;
- font-size: 9px;
- font-weight: 600;
- letter-spacing: 0.08em;
- }
- QToolButton#ribbonButton {
- background: transparent;
- border: none;
- padding: 6px 4px 4px 4px;
- font-size: 11px;
- color: #d6dce6;
- }
- QToolButton#ribbonButton:hover {
- background: #3a3f48;
- }
- QToolButton#ribbonButton:pressed {
- background: #24282f;
- }
- QFrame#viewportShell {
- background: #202329;
- border-top: 1px solid #1d2025;
- }
- QFrame#viewportFrame {
- background: #1a1d22;
- border: 1px solid #333942;
- }
- QDockWidget {
- color: #d0d5dd;
- }
- QLabel#dockTitleText {
- color: #dfe4ec;
- font-size: 10px;
- font-weight: 700;
- letter-spacing: 0.08em;
- }
- QToolButton#dockTitleButton {
- color: #8e97a5;
- border: none;
- background: transparent;
- }
- QToolButton#dockTitleButton:hover {
- color: #ffffff;
- background: #353a42;
- }
- QFrame#panelFrame {
- background: #2b2f36;
- border: 1px solid #3e444e;
- border-radius: 3px;
- }
- QWidget#panelBody {
- background: #2b2f36;
- }
- QTreeWidget, QListWidget, QTableWidget, QAbstractScrollArea {
- background: #2b2f36;
- border: none;
- outline: none;
- gridline-color: #333842;
- }
- QTreeWidget::viewport, QListWidget::viewport, QTableWidget::viewport {
- background: #2b2f36;
- }
- QHeaderView::section {
- background: #31353d;
- color: #b5becc;
- border: none;
- border-bottom: 1px solid #434a55;
- padding: 7px 8px;
- font-weight: 600;
- }
- QTableCornerButton::section {
- background: #31353d;
- border: none;
- }
- QScrollArea {
- background: #2b2f36;
- border: none;
- }
- QScrollArea > QWidget > QWidget {
- background: #2b2f36;
- }
- QLineEdit {
- background: #31353d;
- border: 1px solid #434a55;
- border-radius: 3px;
- padding: 6px 8px;
- color: #d9dfeb;
- }
- QLineEdit:focus {
- border: 1px solid #5b6472;
- }
- QFrame#entityClassCard {
- background: #26292f;
- border: 1px solid #404650;
- border-radius: 3px;
- }
- QLabel#entityClassLabel {
- color: #eef2f8;
- font-weight: 700;
- background: transparent;
- }
- QLabel#entityTypeLabel {
- color: #9aa4b3;
- background: transparent;
- }
- QWidget#attributeList {
- background: transparent;
- }
- QTreeView::item, QListView::item, QTableView::item {
- padding: 4px;
- }
- QScrollBar:vertical {
- background: transparent;
- width: 10px;
- margin: 2px 2px 2px 0;
- }
- QScrollBar:horizontal {
- background: transparent;
- height: 10px;
- margin: 0 2px 2px 2px;
- }
- QScrollBar::handle:vertical, QScrollBar::handle:horizontal {
- background: #525a67;
- border-radius: 3px;
- min-height: 24px;
- min-width: 24px;
- }
- QScrollBar::handle:vertical:hover, QScrollBar::handle:horizontal:hover {
- background: #697385;
- }
- QScrollBar::add-line, QScrollBar::sub-line,
- QScrollBar::add-page, QScrollBar::sub-page {
- background: transparent;
- border: none;
- }
- QStatusBar {
- background: #24272c;
- border-top: 1px solid #1a1c20;
- }
- QStatusBar QLabel {
- color: #97a1af;
- background: transparent;
- border: none;
- padding: 2px 8px;
- }
- QGroupBox {
- background: transparent;
- border: 1px solid #404650;
- border-radius: 3px;
- margin-top: 10px;
- padding-top: 10px;
- }
- QGroupBox#propertySetCard {
- background: #26292f;
- border: 1px solid #404650;
- border-radius: 3px;
- }
- QGroupBox#propertySetCard::title {
- subcontrol-origin: margin;
- left: 10px;
- padding: 0 4px;
- color: #d5dbe5;
- }
- QGroupBox#propertySetCard > QWidget {
- background: #26292f;
- }
- QWidget#panelSection {
- background: transparent;
- }
- QWidget#panelSectionFilterWrapper {
- background: transparent;
- }
- QWidget#relationshipRow {
- background: transparent;
- }
- QLabel#relationshipIconLabel {
- background: transparent;
- }
- QWidget#inspectorPanel {
- background: #2b2f36;
- }
- QFrame#panelSectionHeader {
- background: #26292f;
- }
- QToolButton#panelSectionHeaderButton {
- background: transparent;
- border: none;
- color: #e1e7f0;
- font-weight: 700;
- text-align: left;
- padding: 2px;
- margin: 0;
- }
- QToolButton#panelSectionHeaderButton:hover {
- color: #ffffff;
- }
- QToolButton#panelSectionHeaderButton::menu-indicator {
- image: none;
- width: 0;
- }
- QToolButton#panelSectionFilterToggle {
- background: transparent;
- border: none;
- padding: 2px;
- }
- QToolButton#panelSectionFilterToggle:hover {
- background: #353a42;
- }
- QWidget#panelSectionBody {
- background: transparent;
- }
- QLabel#propertyKeyLabel {
- color: #9aa4b3;
- background: transparent;
- }
- QLabel#propertyValueLabel {
- color: #dce2eb;
- background: transparent;
- }
- QLabel#relationshipValueLabel {
- color: #dce2eb;
- background: transparent;
- }
- QLabel#todoPanelTitle {
- font-size: 14px;
- font-weight: 600;
- color: #e1e6ee;
- background: transparent;
- }
- QLabel#todoPanelBody {
- color: #8f98a6;
- background: transparent;
- }
- )");
+ setStyleSheet(components::style::buildAppStyleSheet());
}
QToolButton* MainWindow::makeRibbonAction(const QString& text, const QString& icon_path) {
@@ -376,17 +106,13 @@ QWidget* MainWindow::makeRibbonGroup(const QString& title, const QListsetText(mode);
status_selection_label_->setText(detail);
}
QToolButton* MainWindow::makePanelToggle(const QString& text, QDockWidget* dock) {
- auto* button = makeRibbonAction(text, ":/icons/dm_toggle_openings.png");
+ auto* button = makeRibbonAction(text, ":/icons/sidebar-expand.svg");
button->setCheckable(true);
button->setChecked(dock->isVisible());
connect(button, &QToolButton::toggled, dock, [dock](bool checked) {
@@ -565,19 +291,20 @@ QWidget* MainWindow::buildPanelsRibbonPage() {
row->setSpacing(0);
row->addWidget(makeRibbonGroup("DATA", {
- makePanelToggle("Models", models_dock_),
- makePanelToggle("Spatial", spatial_dock_),
- makePanelToggle("Layers", layers_dock_),
- makePanelToggle("Properties", properties_dock_)
+ makePanelToggle("Models", models_panel_),
+ makePanelToggle("Spatial", spatial_panel_),
+ makePanelToggle("Layers", layers_panel_),
+ makePanelToggle("Properties", properties_panel_)
}));
row->addWidget(makeRibbonGroup("QUERY", {
- makePanelToggle("Views", stored_views_dock_),
- makePanelToggle("Search", search_dock_),
- makePanelToggle("Sheets", spreadsheet_dock_)
+ makePanelToggle("Views", stored_views_panel_),
+ makePanelToggle("Search", search_panel_),
+ makePanelToggle("Sheets", spreadsheet_panel_),
+ makePanelToggle("Audit", audit_panel_)
}));
row->addWidget(makeRibbonGroup("COLLABORATE", {
- makePanelToggle("Clash", clash_dock_),
- makePanelToggle("Issues", issues_dock_)
+ makePanelToggle("Clash", clash_panel_),
+ makePanelToggle("Issues", issues_panel_)
}));
row->addStretch(1);
return page;
@@ -644,68 +371,67 @@ void MainWindow::setupViewport() {
setCentralWidget(shell);
}
-void MainWindow::setupDocks() {
- auto* models_panel = new panels::models::ModelsPanelWidget(this);
- auto* spatial_panel = new panels::spatial_hierarchy::SpatialHierarchyPanelWidget(this);
- auto* properties_panel = new panels::properties::PropertiesPanelWidget(this);
+void MainWindow::setupPanels() {
+ auto* models_widget = new panels::models::ModelsPanelWidget(this);
+ auto* spatial_widget = new panels::spatial_hierarchy::SpatialHierarchyPanelWidget(this);
+ auto* properties_widget = new panels::properties::PropertiesPanelWidget(this);
- models_panel_view_ = new panels::models::ModelsPanelView(models_panel, this);
- spatial_panel_view_ = new panels::spatial_hierarchy::SpatialHierarchyPanelView(spatial_panel, this);
- properties_panel_view_ = new panels::properties::PropertiesPanelView(properties_panel, this);
+ models_view_ = new panels::models::ModelsPanelView(models_widget, this);
+ spatial_view_ = new panels::spatial_hierarchy::SpatialHierarchyPanelView(spatial_widget, this);
+ properties_view_ = new panels::properties::PropertiesPanelView(
+ properties_widget, viewport_, element_registry_, this);
- connect(models_panel_view_, &panels::models::ModelsPanelView::statusMessageRequested,
+ connect(models_view_, &panels::models::ModelsPanelView::statusMessageRequested,
this, &MainWindow::setStatusMessage);
- connect(spatial_panel_view_, &panels::spatial_hierarchy::SpatialHierarchyPanelView::statusMessageRequested,
+ connect(spatial_view_, &panels::spatial_hierarchy::SpatialHierarchyPanelView::statusMessageRequested,
this, &MainWindow::setStatusMessage);
- models_dock_ = components::panel::makeDock(
- "Models", components::panel::wrapPanel(models_panel), this, true);
- spatial_dock_ = components::panel::makeDock(
- "Spatial Hierarchy", components::panel::wrapPanel(spatial_panel), this);
- properties_dock_ = components::panel::makeDock(
- "Properties", components::panel::wrapPanel(properties_panel), this);
- layers_dock_ = components::panel::makeDock(
- "Layers", components::panel::wrapPanel(makeComingSoonPanel("Layers")), this);
- stored_views_dock_ = components::panel::makeDock(
- "Stored Views", components::panel::wrapPanel(makeComingSoonPanel("Stored Views")), this);
- search_dock_ = components::panel::makeDock(
- "Search and Query", components::panel::wrapPanel(makeComingSoonPanel("Search and Query")), this);
- spreadsheet_dock_ = components::panel::makeDock(
- "Spreadsheet", components::panel::wrapPanel(makeComingSoonPanel("Spreadsheet")), this);
- clash_dock_ = components::panel::makeDock(
- "Clash", components::panel::wrapPanel(makeComingSoonPanel("Clash")), this);
- issues_dock_ = components::panel::makeDock(
- "Issues", components::panel::wrapPanel(makeComingSoonPanel("Issues")), this);
+ models_panel_ = new components::Panel("Models", models_widget, this, true);
+ spatial_panel_ = new components::Panel("Spatial Hierarchy", spatial_widget, this);
+ properties_panel_ = new components::Panel("Properties", properties_widget, this);
+ layers_panel_ = new components::Panel("Layers", new panels::todo::TodoPanelWidget("Layers", this), this);
+ stored_views_panel_ = new components::Panel(
+ "Stored Views", new panels::todo::TodoPanelWidget("Stored Views", this), this);
+ search_panel_ = new components::Panel(
+ "Search and Query", new panels::todo::TodoPanelWidget("Search and Query", this), this);
+ spreadsheet_panel_ = new components::Panel(
+ "Spreadsheet", new panels::todo::TodoPanelWidget("Spreadsheet", this), this);
+ audit_panel_ = new components::Panel("Audit", new panels::todo::TodoPanelWidget("Audit", this), this);
+ clash_panel_ = new components::Panel("Clash", new panels::todo::TodoPanelWidget("Clash", this), this);
+ issues_panel_ = new components::Panel("Issues", new panels::todo::TodoPanelWidget("Issues", this), this);
- addDockWidget(Qt::LeftDockWidgetArea, models_dock_);
- addDockWidget(Qt::LeftDockWidgetArea, spatial_dock_);
- splitDockWidget(models_dock_, spatial_dock_, Qt::Vertical);
+ addDockWidget(Qt::LeftDockWidgetArea, models_panel_);
+ addDockWidget(Qt::LeftDockWidgetArea, spatial_panel_);
+ splitDockWidget(models_panel_, spatial_panel_, Qt::Vertical);
- addDockWidget(Qt::RightDockWidgetArea, properties_dock_);
- addDockWidget(Qt::RightDockWidgetArea, layers_dock_);
- addDockWidget(Qt::RightDockWidgetArea, stored_views_dock_);
- addDockWidget(Qt::RightDockWidgetArea, search_dock_);
- addDockWidget(Qt::RightDockWidgetArea, spreadsheet_dock_);
- addDockWidget(Qt::RightDockWidgetArea, clash_dock_);
- addDockWidget(Qt::RightDockWidgetArea, issues_dock_);
+ 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_dock_, layers_dock_);
- tabifyDockWidget(layers_dock_, stored_views_dock_);
- tabifyDockWidget(stored_views_dock_, search_dock_);
- tabifyDockWidget(search_dock_, spreadsheet_dock_);
- tabifyDockWidget(spreadsheet_dock_, clash_dock_);
- tabifyDockWidget(clash_dock_, issues_dock_);
- properties_dock_->raise();
+ 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_dock_->hide();
- stored_views_dock_->hide();
- search_dock_->hide();
- spreadsheet_dock_->hide();
- clash_dock_->hide();
- issues_dock_->hide();
+ layers_panel_->hide();
+ stored_views_panel_->hide();
+ search_panel_->hide();
+ spreadsheet_panel_->hide();
+ audit_panel_->hide();
+ clash_panel_->hide();
+ issues_panel_->hide();
- resizeDocks({models_dock_, properties_dock_}, {290, 330}, Qt::Horizontal);
- resizeDocks({models_dock_, spatial_dock_}, {280, 240}, Qt::Vertical);
+ resizeDocks({models_panel_, properties_panel_}, {290, 330}, Qt::Horizontal);
+ resizeDocks({models_panel_, spatial_panel_}, {280, 240}, Qt::Vertical);
}
void MainWindow::setupStatus() {
@@ -728,6 +454,7 @@ void MainWindow::setupStatus() {
void MainWindow::setupLoader() {
AppSettings::instance().setLoadDataSource(false);
loader_ = new SceneLoader(viewport_, this);
+ element_registry_->bindLoader(loader_);
connect(loader_, &SceneLoader::loadStarted, this, &MainWindow::onLoadStarted);
connect(loader_, &SceneLoader::loadedFromSidecar, this, &MainWindow::onLoadedFromSidecar);
connect(loader_, &SceneLoader::loadedFromStream, this, &MainWindow::onLoadedFromStream);
diff --git a/src/interface/MainWindow.h b/src/interface/MainWindow.h
index e4a491a403..0ca4fc317a 100644
--- a/src/interface/MainWindow.h
+++ b/src/interface/MainWindow.h
@@ -31,6 +31,7 @@ class QTabBar;
class QToolButton;
class ViewportWindow;
class SceneLoader;
+namespace ifcinterface { class ElementRegistry; }
namespace ifcinterface::panels::models { class ModelsPanelView; }
namespace ifcinterface::panels::spatial_hierarchy { class SpatialHierarchyPanelView; }
namespace ifcinterface::panels::properties { class PropertiesPanelView; }
@@ -46,7 +47,7 @@ private:
void setupChrome();
void setupRibbon();
void setupViewport();
- void setupDocks();
+ void setupPanels();
void setupStatus();
void setupLoader();
QWidget* buildHomeRibbonPage();
@@ -55,7 +56,6 @@ private:
QWidget* buildPanelsRibbonPage();
QToolButton* makeRibbonAction(const QString& text, const QString& icon_path);
QWidget* makeRibbonGroup(const QString& title, const QList& buttons);
- QWidget* makeComingSoonPanel(const QString& title);
QToolButton* makePanelToggle(const QString& text, QDockWidget* dock);
void setStatusMessage(const QString& mode, const QString& detail);
void addFiles(const QStringList& paths);
@@ -78,19 +78,21 @@ private:
QStackedWidget* ribbon_pages_ = nullptr;
ViewportWindow* viewport_ = nullptr;
SceneLoader* loader_ = nullptr;
+ ifcinterface::ElementRegistry* element_registry_ = nullptr;
QWidget* viewport_container_ = nullptr;
- QDockWidget* models_dock_ = nullptr;
- QDockWidget* spatial_dock_ = nullptr;
- QDockWidget* layers_dock_ = nullptr;
- QDockWidget* properties_dock_ = nullptr;
- QDockWidget* stored_views_dock_ = nullptr;
- QDockWidget* search_dock_ = nullptr;
- QDockWidget* spreadsheet_dock_ = nullptr;
- QDockWidget* clash_dock_ = nullptr;
- QDockWidget* issues_dock_ = nullptr;
- ifcinterface::panels::models::ModelsPanelView* models_panel_view_ = nullptr;
- ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelView* spatial_panel_view_ = nullptr;
- ifcinterface::panels::properties::PropertiesPanelView* properties_panel_view_ = nullptr;
+ QDockWidget* models_panel_ = nullptr;
+ QDockWidget* spatial_panel_ = nullptr;
+ QDockWidget* layers_panel_ = nullptr;
+ QDockWidget* 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::panels::models::ModelsPanelView* models_view_ = nullptr;
+ ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelView* spatial_view_ = nullptr;
+ ifcinterface::panels::properties::PropertiesPanelView* properties_view_ = nullptr;
};
} // namespace ifcinterface::shell
diff --git a/src/interface/components/KeyValueTable.cpp b/src/interface/components/KeyValueTable.cpp
new file mode 100644
index 0000000000..0fbc436b3d
--- /dev/null
+++ b/src/interface/components/KeyValueTable.cpp
@@ -0,0 +1,82 @@
+// 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 "KeyValueTable.h"
+
+#include "SvgIcon.h"
+
+#include
+#include
+#include
+
+namespace ifcinterface::components {
+
+KeyValueTable::KeyValueTable(const QList& rows, QWidget* parent)
+ : QWidget(parent)
+{
+ setObjectName("attributeList");
+
+ auto* layout = new QVBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(6);
+
+ for (const auto& row_data : rows) {
+ auto* row = new QWidget(this);
+ if (!row_data.trailing_icon_path.isEmpty()) {
+ row->setObjectName("relationshipRow");
+ } else {
+ row->setObjectName("keyValueRow");
+ }
+
+ auto* row_layout = new QHBoxLayout(row);
+ row_layout->setContentsMargins(0, 0, 0, 0);
+ row_layout->setSpacing(12);
+
+ auto* key = new QLabel(row_data.key, row);
+ key->setObjectName("propertyKeyLabel");
+ if (row_data.key_minimum_width > 0) {
+ key->setMinimumWidth(row_data.key_minimum_width);
+ }
+
+ auto* value = new QLabel(row_data.value, row);
+ value->setObjectName(row_data.value_object_name.isEmpty()
+ ? "propertyValueLabel"
+ : row_data.value_object_name);
+ value->setWordWrap(true);
+ value->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ row_layout->addWidget(key);
+ row_layout->addWidget(value, 1);
+
+ if (!row_data.trailing_icon_path.isEmpty()) {
+ auto* icon = new QLabel(row);
+ icon->setObjectName(row_data.trailing_icon_object_name.isEmpty()
+ ? "relationshipIconLabel"
+ : row_data.trailing_icon_object_name);
+ icon->setPixmap(icons::makePanelSvgPixmap(row_data.trailing_icon_path, QSize(14, 14)));
+ icon->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+ row_layout->addWidget(icon, 0, Qt::AlignRight | Qt::AlignVCenter);
+ }
+
+ layout->addWidget(row);
+ }
+}
+
+} // namespace ifcinterface::components
diff --git a/src/interface/components/KeyValueTable.h b/src/interface/components/KeyValueTable.h
new file mode 100644
index 0000000000..3e1ba10f73
--- /dev/null
+++ b/src/interface/components/KeyValueTable.h
@@ -0,0 +1,47 @@
+// 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_COMPONENTS_KEYVALUETABLE_H
+#define IFCINTERFACE_COMPONENTS_KEYVALUETABLE_H
+
+#include
+#include
+#include
+
+namespace ifcinterface::components {
+
+struct KeyValueTableRow {
+ QString key;
+ QString value;
+ QString value_object_name = "propertyValueLabel";
+ QString trailing_icon_path;
+ QString trailing_icon_object_name;
+ int key_minimum_width = 0;
+};
+
+class KeyValueTable : public QWidget {
+ Q_OBJECT
+public:
+ explicit KeyValueTable(const QList& rows, QWidget* parent = nullptr);
+};
+
+} // namespace ifcinterface::components
+
+#endif
diff --git a/src/interface/components/PanelChrome.cpp b/src/interface/components/Panel.cpp
similarity index 76%
rename from src/interface/components/PanelChrome.cpp
rename to src/interface/components/Panel.cpp
index 4e49219788..3d73c98f6c 100644
--- a/src/interface/components/PanelChrome.cpp
+++ b/src/interface/components/Panel.cpp
@@ -18,8 +18,9 @@
* *
********************************************************************************/
-#include "PanelChrome.h"
+#include "Panel.h"
+#include "Style.h"
#include "SvgIcon.h"
#include
@@ -30,7 +31,7 @@
#include
#include
-namespace ifcinterface::components::panel {
+namespace ifcinterface::components {
namespace {
@@ -69,32 +70,31 @@ public:
} // namespace
-QDockWidget* makeDock(const QString& title, QWidget* content, QWidget* parent, bool has_settings) {
- auto* dock = new QDockWidget(title, parent);
- dock->setObjectName(title);
- dock->setFeatures(QDockWidget::DockWidgetMovable |
- QDockWidget::DockWidgetFloatable |
- QDockWidget::DockWidgetClosable);
- dock->setTitleBarWidget(new DockTitleBar(title, has_settings, dock));
- dock->setWidget(content);
- return dock;
-}
-
-QFrame* wrapPanel(QWidget* inner) {
+Panel::Panel(const QString& title, QWidget* content, QWidget* parent, bool has_settings)
+ : QDockWidget(title, parent)
+{
auto* outer = new QFrame();
auto* outer_layout = new QVBoxLayout(outer);
- outer_layout->setContentsMargins(6, 6, 6, 6);
+ outer_layout->setContentsMargins(style::metrics::padding,
+ style::metrics::padding,
+ style::metrics::padding,
+ style::metrics::padding);
outer_layout->setSpacing(0);
auto* frame = new QFrame(outer);
- frame->setObjectName("panelFrame");
- auto* layout = new QVBoxLayout(frame);
- layout->setContentsMargins(0, 8, 0, 8);
- layout->setSpacing(0);
- layout->addWidget(inner);
-
+ frame->setObjectName("panel");
+ auto* frame_layout = new QVBoxLayout(frame);
+ frame_layout->setContentsMargins(0, style::metrics::padding, 0, style::metrics::padding);
+ frame_layout->setSpacing(0);
+ frame_layout->addWidget(content);
outer_layout->addWidget(frame);
- return outer;
+
+ setObjectName(title);
+ setFeatures(QDockWidget::DockWidgetMovable |
+ QDockWidget::DockWidgetFloatable |
+ QDockWidget::DockWidgetClosable);
+ setTitleBarWidget(new DockTitleBar(title, has_settings, this));
+ setWidget(outer);
}
-} // namespace ifcinterface::components::panel
+} // namespace ifcinterface::components
diff --git a/src/interface/components/PanelChrome.h b/src/interface/components/Panel.h
similarity index 85%
rename from src/interface/components/PanelChrome.h
rename to src/interface/components/Panel.h
index 806dfeefcd..9739d53552 100644
--- a/src/interface/components/PanelChrome.h
+++ b/src/interface/components/Panel.h
@@ -21,17 +21,19 @@
#ifndef IFCINTERFACE_COMPONENTS_PANEL_PANELCHROME_H
#define IFCINTERFACE_COMPONENTS_PANEL_PANELCHROME_H
-#include
+#include
-class QDockWidget;
-class QFrame;
class QWidget;
-namespace ifcinterface::components::panel {
+namespace ifcinterface::components {
-QDockWidget* makeDock(const QString& title, QWidget* content, QWidget* parent, bool has_settings = false);
-QFrame* wrapPanel(QWidget* inner);
+class Panel : public QDockWidget {
+ Q_OBJECT
-} // namespace ifcinterface::components::panel
+public:
+ explicit Panel(const QString& title, QWidget* content, QWidget* parent = nullptr, bool has_settings = false);
+};
+
+} // namespace ifcinterface::components
#endif
diff --git a/src/interface/components/Section.cpp b/src/interface/components/Section.cpp
index 34790e7960..a7899b9ce6 100644
--- a/src/interface/components/Section.cpp
+++ b/src/interface/components/Section.cpp
@@ -20,6 +20,7 @@
#include "Section.h"
+#include "Style.h"
#include "SvgIcon.h"
#include
@@ -49,14 +50,14 @@ Section::Section(const QString& title, SectionHeaderMode header_mode,
setObjectName("panelSection");
auto* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
- layout->setSpacing(6);
+ layout->setSpacing(style::metrics::section_spacing);
if (header_mode == SectionHeaderMode::Visible) {
auto* header = new QFrame(this);
header->setObjectName("panelSectionHeader");
auto* header_layout = new QHBoxLayout(header);
header_layout->setContentsMargins(0, 0, 0, 0);
- header_layout->setSpacing(6);
+ header_layout->setSpacing(style::metrics::section_spacing);
auto* toggle = new QToolButton(header);
toggle->setObjectName("panelSectionHeaderButton");
@@ -89,7 +90,8 @@ Section::Section(const QString& title, SectionHeaderMode header_mode,
auto* filter_wrapper = new QWidget(this);
filter_wrapper->setObjectName("panelSectionFilterWrapper");
auto* filter_wrapper_layout = new QVBoxLayout(filter_wrapper);
- filter_wrapper_layout->setContentsMargins(10, 0, 10, 0);
+ filter_wrapper_layout->setContentsMargins(style::metrics::filter_body_padding_x, 0,
+ style::metrics::filter_body_padding_x, 0);
filter_wrapper_layout->setSpacing(0);
filter_wrapper_layout->addWidget(filter_field_);
layout->addWidget(filter_wrapper);
@@ -108,7 +110,8 @@ Section::Section(const QString& title, SectionHeaderMode header_mode,
auto* filter_wrapper = new QWidget(this);
filter_wrapper->setObjectName("panelSectionFilterWrapper");
auto* filter_wrapper_layout = new QVBoxLayout(filter_wrapper);
- filter_wrapper_layout->setContentsMargins(8, 0, 8, 0);
+ filter_wrapper_layout->setContentsMargins(style::metrics::hidden_filter_body_padding_x, 0,
+ style::metrics::hidden_filter_body_padding_x, 0);
filter_wrapper_layout->setSpacing(0);
filter_wrapper_layout->addWidget(filter_field_);
layout->addWidget(filter_wrapper);
@@ -118,11 +121,14 @@ Section::Section(const QString& title, SectionHeaderMode header_mode,
body_->setObjectName("panelSectionBody");
body_layout_ = new QVBoxLayout(body_);
if (header_mode == SectionHeaderMode::Visible) {
- body_layout_->setContentsMargins(10, 6, 10, 0);
+ body_layout_->setContentsMargins(style::metrics::section_body_padding_x,
+ style::metrics::section_body_padding_top,
+ style::metrics::section_body_padding_x, 0);
} else {
- body_layout_->setContentsMargins(8, 0, 8, 0);
+ body_layout_->setContentsMargins(style::metrics::hidden_section_body_padding_x, 0,
+ style::metrics::hidden_section_body_padding_x, 0);
}
- body_layout_->setSpacing(6);
+ body_layout_->setSpacing(style::metrics::section_spacing);
layout->addWidget(body_);
}
diff --git a/src/interface/components/Style.cpp b/src/interface/components/Style.cpp
new file mode 100644
index 0000000000..9c0b14a924
--- /dev/null
+++ b/src/interface/components/Style.cpp
@@ -0,0 +1,303 @@
+// 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 "Style.h"
+
+namespace ifcinterface::components::style {
+
+QString buildAppStyleSheet() {
+ return QString(R"(
+ QMainWindow {
+ background: #26292f;
+ }
+ QWidget {
+ color: #d0d5dd;
+ background: #26292f;
+ selection-background-color: #39b54a;
+ selection-color: #14161a;
+ }
+ QFrame#ribbonShell {
+ background: #2d3138;
+ border-bottom: 1px solid #1b1d22;
+ }
+ QTabBar::tab {
+ background: transparent;
+ color: #8d97a7;
+ padding: 8px 14px;
+ margin-right: 2px;
+ border-bottom: 2px solid transparent;
+ }
+ QTabBar::tab:selected {
+ color: #f2f5fa;
+ border-bottom: 2px solid #39b54a;
+ }
+ QTabBar::tab:hover {
+ color: #ffffff;
+ }
+ QFrame#ribbonBand {
+ background: #31353d;
+ border-top: 1px solid #3b4048;
+ }
+ QFrame#ribbonPage {
+ background: transparent;
+ }
+ QFrame#ribbonGroup {
+ background: transparent;
+ border-right: 1px solid #434852;
+ }
+ QLabel#ribbonGroupLabel {
+ color: #7f8796;
+ font-size: 9px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ }
+ QToolButton#ribbonButton {
+ background: transparent;
+ border: none;
+ padding: 6px 4px 4px 4px;
+ font-size: 11px;
+ color: #d6dce6;
+ }
+ QToolButton#ribbonButton:hover {
+ background: #3a3f48;
+ }
+ QToolButton#ribbonButton:pressed {
+ background: #24282f;
+ }
+ QFrame#viewportShell {
+ background: #202329;
+ border-top: 1px solid #1d2025;
+ }
+ QFrame#viewportFrame {
+ background: #1a1d22;
+ border: 1px solid #333942;
+ }
+ QDockWidget {
+ color: #d0d5dd;
+ }
+ QLabel#dockTitleText {
+ color: #dfe4ec;
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ }
+ QToolButton#dockTitleButton {
+ color: #8e97a5;
+ border: none;
+ background: transparent;
+ }
+ QToolButton#dockTitleButton:hover {
+ color: #ffffff;
+ background: #353a42;
+ }
+ QFrame#panel {
+ background: #2b2f36;
+ border: 1px solid #3e444e;
+ border-radius: %1px;
+ }
+ QTreeWidget, QListWidget, QTableWidget, QAbstractScrollArea {
+ background: #2b2f36;
+ border: none;
+ outline: none;
+ gridline-color: #333842;
+ }
+ QTreeWidget::viewport, QListWidget::viewport, QTableWidget::viewport {
+ background: #2b2f36;
+ }
+ QHeaderView::section {
+ background: #31353d;
+ color: #b5becc;
+ border: none;
+ border-bottom: 1px solid #434a55;
+ padding: 7px 8px;
+ font-weight: 600;
+ }
+ QTableCornerButton::section {
+ background: #31353d;
+ border: none;
+ }
+ QScrollArea {
+ background: #2b2f36;
+ border: none;
+ }
+ QScrollArea > QWidget > QWidget {
+ background: #2b2f36;
+ }
+ QLineEdit {
+ background: #31353d;
+ border: 1px solid #434a55;
+ border-radius: %1px;
+ padding: %2px %3px;
+ color: #d9dfeb;
+ }
+ QLineEdit:focus {
+ border: 1px solid #5b6472;
+ }
+ QFrame#entityClassCard {
+ background: #26292f;
+ border: 1px solid #404650;
+ border-radius: %1px;
+ }
+ QLabel#entityClassLabel {
+ color: #eef2f8;
+ font-weight: 700;
+ background: transparent;
+ }
+ QLabel#entityTypeLabel {
+ color: #9aa4b3;
+ background: transparent;
+ }
+ QWidget#attributeList {
+ background: transparent;
+ }
+ QTreeView::item, QListView::item, QTableView::item {
+ padding: 4px;
+ }
+ QScrollBar:vertical {
+ background: transparent;
+ width: 10px;
+ margin: 2px 2px 2px 0;
+ }
+ QScrollBar:horizontal {
+ background: transparent;
+ height: 10px;
+ margin: 0 2px 2px 2px;
+ }
+ QScrollBar::handle:vertical, QScrollBar::handle:horizontal {
+ background: #525a67;
+ border-radius: %1px;
+ min-height: 24px;
+ min-width: 24px;
+ }
+ QScrollBar::handle:vertical:hover, QScrollBar::handle:horizontal:hover {
+ background: #697385;
+ }
+ QScrollBar::add-line, QScrollBar::sub-line,
+ QScrollBar::add-page, QScrollBar::sub-page {
+ background: transparent;
+ border: none;
+ }
+ QStatusBar {
+ background: #24272c;
+ border-top: 1px solid #1a1c20;
+ }
+ QStatusBar QLabel {
+ color: #97a1af;
+ background: transparent;
+ border: none;
+ padding: 2px 8px;
+ }
+ QGroupBox {
+ background: transparent;
+ border: 1px solid #404650;
+ border-radius: %1px;
+ margin-top: 10px;
+ padding-top: 10px;
+ }
+ QGroupBox#propertySetCard {
+ background: #26292f;
+ border: 1px solid #404650;
+ border-radius: %1px;
+ }
+ QGroupBox#propertySetCard::title {
+ subcontrol-origin: margin;
+ left: %4px;
+ padding: 0 4px;
+ color: #d5dbe5;
+ }
+ QGroupBox#propertySetCard > QWidget {
+ background: #26292f;
+ }
+ QWidget#panelSection {
+ background: transparent;
+ }
+ QWidget#panelSectionFilterWrapper {
+ background: transparent;
+ }
+ QWidget#relationshipRow {
+ background: transparent;
+ }
+ QLabel#relationshipIconLabel {
+ background: transparent;
+ }
+ QWidget#inspectorPanel {
+ background: #2b2f36;
+ }
+ QFrame#panelSectionHeader {
+ background: #26292f;
+ }
+ QToolButton#panelSectionHeaderButton {
+ background: transparent;
+ border: none;
+ color: #e1e7f0;
+ font-weight: 700;
+ text-align: left;
+ padding: %5px;
+ margin: 0;
+ }
+ QToolButton#panelSectionHeaderButton:hover {
+ color: #ffffff;
+ }
+ QToolButton#panelSectionHeaderButton::menu-indicator {
+ image: none;
+ width: 0;
+ }
+ QToolButton#panelSectionFilterToggle {
+ background: transparent;
+ border: none;
+ padding: %5px;
+ }
+ QToolButton#panelSectionFilterToggle:hover {
+ background: #353a42;
+ }
+ QWidget#panelSectionBody {
+ background: transparent;
+ }
+ QLabel#propertyKeyLabel {
+ color: #9aa4b3;
+ background: transparent;
+ }
+ QLabel#propertyValueLabel {
+ color: #dce2eb;
+ background: transparent;
+ }
+ QLabel#relationshipValueLabel {
+ color: #dce2eb;
+ background: transparent;
+ }
+ QLabel#todoPanelTitle {
+ font-size: 14px;
+ font-weight: 600;
+ color: #e1e6ee;
+ background: transparent;
+ }
+ QLabel#todoPanelBody {
+ color: #8f98a6;
+ background: transparent;
+ }
+ )")
+ .arg(metrics::panel_radius)
+ .arg(metrics::control_padding_y)
+ .arg(metrics::control_padding_x)
+ .arg(metrics::card_padding)
+ .arg(metrics::section_header_padding);
+}
+
+} // namespace ifcinterface::components::style
diff --git a/src/interface/components/Style.h b/src/interface/components/Style.h
new file mode 100644
index 0000000000..8c382fa804
--- /dev/null
+++ b/src/interface/components/Style.h
@@ -0,0 +1,49 @@
+// 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_COMPONENTS_STYLE_H
+#define IFCINTERFACE_COMPONENTS_STYLE_H
+
+#include
+
+namespace ifcinterface::components::style::metrics {
+
+inline constexpr int padding = 6;
+inline constexpr int section_spacing = 6;
+inline constexpr int section_body_padding_x = 10;
+inline constexpr int section_body_padding_top = 6;
+inline constexpr int hidden_section_body_padding_x = 8;
+inline constexpr int section_header_padding = 2;
+inline constexpr int filter_body_padding_x = 10;
+inline constexpr int hidden_filter_body_padding_x = 8;
+inline constexpr int card_padding = 10;
+inline constexpr int control_padding_y = 6;
+inline constexpr int control_padding_x = 8;
+inline constexpr int panel_radius = 3;
+
+} // namespace ifcinterface::components::style::metrics
+
+namespace ifcinterface::components::style {
+
+QString buildAppStyleSheet();
+
+} // namespace ifcinterface::components::style
+
+#endif
diff --git a/src/interface/icons/sidebar-expand.svg b/src/interface/icons/sidebar-expand.svg
new file mode 100644
index 0000000000..6d7695253c
--- /dev/null
+++ b/src/interface/icons/sidebar-expand.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/interface/interface_resources.qrc b/src/interface/interface_resources.qrc
index 9be3928f7d..453e64d9e7 100644
--- a/src/interface/interface_resources.qrc
+++ b/src/interface/interface_resources.qrc
@@ -4,21 +4,6 @@
../ifctester/webapp/public/fonts/dmsans/DMSans-VariableFont_opsz,wght.ttf
- ../bonsai/bonsai/bim/data/icons/IFC.png
- ../bonsai/bonsai/bim/data/icons/dm_add.png
- ../bonsai/bonsai/bim/data/icons/dm_add_type.png
- ../bonsai/bonsai/bim/data/icons/dm_assign.png
- ../bonsai/bonsai/bim/data/icons/dm_centerline.png
- ../bonsai/bonsai/bim/data/icons/dm_connect_mep_elements.png
- ../bonsai/bonsai/bim/data/icons/dm_decomposition.png
- ../bonsai/bonsai/bim/data/icons/dm_edit_profile.png
- ../bonsai/bonsai/bim/data/icons/dm_ifc.png
- ../bonsai/bonsai/bim/data/icons/dm_perform_quantity_take-off.png
- ../bonsai/bonsai/bim/data/icons/dm_rectangle.png
- ../bonsai/bonsai/bim/data/icons/dm_refresh.png
- ../bonsai/bonsai/bim/data/icons/dm_rotate_90.png
- ../bonsai/bonsai/bim/data/icons/dm_split.png
- ../bonsai/bonsai/bim/data/icons/dm_toggle_openings.png
icons/plus-square.svg
icons/download-square.svg
icons/cloud-square.svg
@@ -55,5 +40,6 @@
icons/filter.svg
icons/cube-dots.svg
icons/cursor-pointer.svg
+ icons/sidebar-expand.svg
diff --git a/src/interface/panels/properties/PropertiesPanelView.cpp b/src/interface/panels/properties/PropertiesPanelView.cpp
index 1b74f40fff..291589762a 100644
--- a/src/interface/panels/properties/PropertiesPanelView.cpp
+++ b/src/interface/panels/properties/PropertiesPanelView.cpp
@@ -22,22 +22,36 @@
#include "PropertiesPanelWidget.h"
+#include "../../ElementRegistry.h"
+#include "../../../ifcviewer/ViewportWindow.h"
+
namespace ifcinterface::panels::properties {
-PropertiesPanelView::PropertiesPanelView(PropertiesPanelWidget* widget, QObject* parent)
- : QObject(parent), widget_(widget)
+PropertiesPanelView::PropertiesPanelView(PropertiesPanelWidget* widget,
+ ViewportWindow* viewport,
+ ifcinterface::ElementRegistry* registry,
+ QObject* parent)
+ : QObject(parent), widget_(widget), registry_(registry)
{
- state_.entity = {"IfcWall", "SOLIDWALL"};
- state_.attributes = {
+ connect(viewport, &ViewportWindow::objectPicked, this, [this](uint32_t object_id) {
+ refresh(object_id);
+ });
+ refresh(0);
+}
+
+void PropertiesPanelView::refresh(uint32_t object_id) {
+ PropertiesPanelState state;
+ state.entity = {"IfcWall", "SOLIDWALL"};
+ state.attributes = {
{"GlobalId", "2Q$n5SLPP9Q8B7wQKjKfUQ"},
{"Name", "Core-EXT-204"},
{"Description", "External load-bearing wall"},
};
- state_.relationships = {
+ state.relationships = {
{"Type", "Basic Wall: Exterior - 200mm"},
{"Container", "Level 02"},
};
- state_.property_sets = {
+ state.property_sets = {
{"Pset_WallCommon",
{{"Reference", "Core-EXT-204"},
{"Status", "Reviewed"},
@@ -53,7 +67,7 @@ PropertiesPanelView::PropertiesPanelView(PropertiesPanelWidget* widget, QObject*
{"Last Review", "2026-04-30"},
{"Assigned To", "Design Coordination"}}},
};
- state_.quantity_sets = {
+ state.quantity_sets = {
{"BaseQuantities",
{{"Length", "6.20 m"},
{"Height", "3.45 m"},
@@ -65,7 +79,25 @@ PropertiesPanelView::PropertiesPanelView(PropertiesPanelWidget* widget, QObject*
{"Paint Coverage", "42.78 m2"}}},
};
- widget_->setState(state_);
+ if (registry_) {
+ auto info = registry_->find(object_id);
+ if (info && !info->type.isEmpty()) {
+ state.entity.entity_class = info->type;
+ if (!state.property_sets.isEmpty() && !state.property_sets[1].rows.isEmpty()) {
+ state.property_sets[1].rows[0].value = info->type;
+ }
+ }
+ if (info && !info->name.isEmpty()) {
+ state.attributes[1].value = info->name;
+ if (state.property_sets.size() > 1 && state.property_sets[1].rows.size() > 1) {
+ state.property_sets[1].rows[1].value = info->name;
+ }
+ }
+ if (info && !info->guid.isEmpty()) {
+ state.attributes[0].value = info->guid;
+ }
+ }
+ widget_->render(state);
}
} // namespace ifcinterface::panels::properties
diff --git a/src/interface/panels/properties/PropertiesPanelView.h b/src/interface/panels/properties/PropertiesPanelView.h
index a61c393d31..65abe82f4f 100644
--- a/src/interface/panels/properties/PropertiesPanelView.h
+++ b/src/interface/panels/properties/PropertiesPanelView.h
@@ -25,6 +25,8 @@
#include
+namespace ifcinterface { class ElementRegistry; }
+class ViewportWindow;
namespace ifcinterface::panels::properties {
class PropertiesPanelWidget;
@@ -32,11 +34,16 @@ class PropertiesPanelWidget;
class PropertiesPanelView : public QObject {
Q_OBJECT
public:
- explicit PropertiesPanelView(PropertiesPanelWidget* widget, QObject* parent = nullptr);
+ explicit PropertiesPanelView(PropertiesPanelWidget* widget,
+ ViewportWindow* viewport,
+ ifcinterface::ElementRegistry* registry,
+ QObject* parent = nullptr);
private:
+ void refresh(uint32_t object_id);
+
PropertiesPanelWidget* widget_ = nullptr;
- PropertiesPanelState state_;
+ ifcinterface::ElementRegistry* registry_ = nullptr;
};
} // namespace ifcinterface::panels::properties
diff --git a/src/interface/panels/properties/PropertiesPanelWidget.cpp b/src/interface/panels/properties/PropertiesPanelWidget.cpp
index 2c17e4c176..33db92fbb4 100644
--- a/src/interface/panels/properties/PropertiesPanelWidget.cpp
+++ b/src/interface/panels/properties/PropertiesPanelWidget.cpp
@@ -20,10 +20,10 @@
#include "PropertiesPanelWidget.h"
+#include "../../components/KeyValueTable.h"
#include "../../components/Section.h"
#include "../../components/SvgIcon.h"
-#include
#include
#include
#include
@@ -36,79 +36,37 @@ namespace {
QWidget* makePropertySetPanel(const ifcinterface::panels::properties::PropertySet& property_set, QWidget* parent = nullptr) {
auto* group = new QGroupBox(property_set.title, parent);
group->setObjectName("propertySetCard");
- auto* form = new QFormLayout(group);
- form->setContentsMargins(10, 10, 10, 10);
- form->setHorizontalSpacing(16);
- form->setVerticalSpacing(6);
- form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
+ auto* layout = new QVBoxLayout(group);
+ layout->setContentsMargins(10, 10, 10, 10);
+ layout->setSpacing(0);
+ QList rows;
for (const auto& row : property_set.rows) {
- auto* key = new QLabel(row.key, group);
- key->setObjectName("propertyKeyLabel");
- auto* value = new QLabel(row.value, group);
- value->setObjectName("propertyValueLabel");
- value->setWordWrap(true);
- form->addRow(key, value);
+ rows.append({row.key, row.value, "propertyValueLabel", "", "", 0});
}
-
+ layout->addWidget(new ifcinterface::components::KeyValueTable(rows, group));
return group;
}
QWidget* makeAttributeList(const QList& rows, QWidget* parent = nullptr) {
- auto* panel = new QWidget(parent);
- panel->setObjectName("attributeList");
- auto* form = new QFormLayout(panel);
- form->setContentsMargins(0, 0, 0, 0);
- form->setHorizontalSpacing(16);
- form->setVerticalSpacing(6);
- form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
-
+ QList table_rows;
for (const auto& row : rows) {
- auto* key = new QLabel(row.key, panel);
- key->setObjectName("propertyKeyLabel");
- auto* value = new QLabel(row.value, panel);
- value->setObjectName("propertyValueLabel");
- value->setWordWrap(true);
- form->addRow(key, value);
+ table_rows.append({row.key, row.value, "propertyValueLabel", "", "", 0});
}
-
- return panel;
+ return new ifcinterface::components::KeyValueTable(table_rows, parent);
}
QWidget* makeRelationshipList(const QList& rows, QWidget* parent = nullptr) {
- auto* panel = new QWidget(parent);
- panel->setObjectName("attributeList");
- auto* layout = new QVBoxLayout(panel);
- layout->setContentsMargins(0, 0, 0, 0);
- layout->setSpacing(6);
-
+ QList table_rows;
for (const auto& row_data : rows) {
- auto* row = new QWidget(panel);
- row->setObjectName("relationshipRow");
- auto* row_layout = new QHBoxLayout(row);
- row_layout->setContentsMargins(0, 0, 0, 0);
- row_layout->setSpacing(12);
-
- auto* key = new QLabel(row_data.key, row);
- key->setObjectName("propertyKeyLabel");
- key->setMinimumWidth(72);
-
- auto* target = new QLabel(row_data.value, row);
- target->setObjectName("relationshipValueLabel");
- target->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
-
- auto* icon = new QLabel(row);
- icon->setObjectName("relationshipIconLabel");
- icon->setPixmap(ifcinterface::components::icons::makePanelSvgPixmap(":/icons/cursor-pointer.svg", QSize(14, 14)));
- icon->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
-
- row_layout->addWidget(key);
- row_layout->addWidget(target, 1);
- row_layout->addWidget(icon, 0, Qt::AlignRight | Qt::AlignVCenter);
- layout->addWidget(row);
+ table_rows.append({row_data.key,
+ row_data.value,
+ "relationshipValueLabel",
+ ":/icons/cursor-pointer.svg",
+ "relationshipIconLabel",
+ 72});
}
-
- return panel;
+ return new ifcinterface::components::KeyValueTable(table_rows, parent);
}
} // namespace
@@ -121,22 +79,28 @@ PropertiesPanelWidget::PropertiesPanelWidget(QWidget* parent)
auto* root = new QVBoxLayout(this);
root->setContentsMargins(0, 0, 0, 0);
root->setSpacing(0);
+
+ scroll_ = new QScrollArea(this);
+ scroll_->setWidgetResizable(true);
+ scroll_->setFrameShape(QFrame::NoFrame);
+
+ content_ = new QWidget(scroll_);
+ content_->setObjectName("inspectorPanel");
+ content_layout_ = new QVBoxLayout(content_);
+ content_layout_->setContentsMargins(0, 0, 0, 0);
+ content_layout_->setSpacing(12);
+
+ scroll_->setWidget(content_);
+ root->addWidget(scroll_);
}
-void PropertiesPanelWidget::setState(const PropertiesPanelState& state) {
- auto* root = qobject_cast(layout());
- while (auto* item = root->takeAt(0)) {
+void PropertiesPanelWidget::render(const PropertiesPanelState& state) {
+ while (auto* item = content_layout_->takeAt(0)) {
if (auto* widget = item->widget()) widget->deleteLater();
delete item;
}
- auto* content = new QWidget(this);
- content->setObjectName("inspectorPanel");
- auto* content_layout = new QVBoxLayout(content);
- content_layout->setContentsMargins(0, 0, 0, 0);
- content_layout->setSpacing(12);
-
- auto* entity_card = new QFrame(content);
+ auto* entity_card = new QFrame(content_);
entity_card->setObjectName("entityClassCard");
auto* entity_layout = new QHBoxLayout(entity_card);
entity_layout->setContentsMargins(10, 8, 10, 8);
@@ -159,37 +123,31 @@ void PropertiesPanelWidget::setState(const PropertiesPanelState& state) {
QList property_set_widgets;
for (const auto& property_set : state.property_sets) {
- property_set_widgets.append(makePropertySetPanel(property_set, content));
+ property_set_widgets.append(makePropertySetPanel(property_set, content_));
}
QList quantity_set_widgets;
for (const auto& property_set : state.quantity_sets) {
- quantity_set_widgets.append(makePropertySetPanel(property_set, content));
+ quantity_set_widgets.append(makePropertySetPanel(property_set, content_));
}
- auto* entity_section = new components::Section("", components::SectionHeaderMode::Hidden, "", content);
+ auto* entity_section = new components::Section("", components::SectionHeaderMode::Hidden, "", content_);
entity_section->addBodyWidget(entity_card);
- auto* attributes_section = new components::Section("Attributes", components::SectionHeaderMode::Visible, "", content);
- attributes_section->addBodyWidget(makeAttributeList(state.attributes, content));
- auto* relationships_section = new components::Section("Relationships", components::SectionHeaderMode::Visible, "", content);
- relationships_section->addBodyWidget(makeRelationshipList(state.relationships, content));
- auto* properties_section = new components::Section("Properties", components::SectionHeaderMode::Visible, "Filter properties or sets", content);
+ auto* attributes_section = new components::Section("Attributes", components::SectionHeaderMode::Visible, "", content_);
+ attributes_section->addBodyWidget(makeAttributeList(state.attributes, content_));
+ auto* relationships_section = new components::Section("Relationships", components::SectionHeaderMode::Visible, "", content_);
+ relationships_section->addBodyWidget(makeRelationshipList(state.relationships, content_));
+ auto* properties_section = new components::Section("Properties", components::SectionHeaderMode::Visible, "Filter properties or sets", content_);
for (auto* widget : property_set_widgets) properties_section->addBodyWidget(widget);
- auto* quantities_section = new components::Section("Quantities", components::SectionHeaderMode::Visible, "Filter quantities or sets", content);
+ auto* quantities_section = new components::Section("Quantities", components::SectionHeaderMode::Visible, "Filter quantities or sets", content_);
for (auto* widget : quantity_set_widgets) quantities_section->addBodyWidget(widget);
- content_layout->addWidget(entity_section);
- content_layout->addWidget(attributes_section);
- content_layout->addWidget(relationships_section);
- content_layout->addWidget(properties_section);
- content_layout->addWidget(quantities_section);
- content_layout->addStretch(1);
-
- auto* scroll = new QScrollArea(this);
- scroll->setWidgetResizable(true);
- scroll->setFrameShape(QFrame::NoFrame);
- scroll->setWidget(content);
- root->addWidget(scroll);
+ content_layout_->addWidget(entity_section);
+ content_layout_->addWidget(attributes_section);
+ content_layout_->addWidget(relationships_section);
+ content_layout_->addWidget(properties_section);
+ content_layout_->addWidget(quantities_section);
+ content_layout_->addStretch(1);
}
} // namespace ifcinterface::panels::properties
diff --git a/src/interface/panels/properties/PropertiesPanelWidget.h b/src/interface/panels/properties/PropertiesPanelWidget.h
index dcd600cbb7..ea929c22de 100644
--- a/src/interface/panels/properties/PropertiesPanelWidget.h
+++ b/src/interface/panels/properties/PropertiesPanelWidget.h
@@ -25,6 +25,9 @@
#include
+class QScrollArea;
+class QVBoxLayout;
+
namespace ifcinterface::panels::properties {
class PropertiesPanelWidget : public QWidget {
@@ -32,7 +35,12 @@ class PropertiesPanelWidget : public QWidget {
public:
explicit PropertiesPanelWidget(QWidget* parent = nullptr);
- void setState(const PropertiesPanelState& state);
+ void render(const PropertiesPanelState& state);
+
+private:
+ QScrollArea* scroll_ = nullptr;
+ QWidget* content_ = nullptr;
+ QVBoxLayout* content_layout_ = nullptr;
};
} // namespace ifcinterface::panels::properties