diff --git a/src/bonsaiviewer/modules/spatial_hierarchy/Panel.cpp b/src/bonsaiviewer/modules/spatial_hierarchy/Panel.cpp index 2aa30cc95e..26f535cc5e 100644 --- a/src/bonsaiviewer/modules/spatial_hierarchy/Panel.cpp +++ b/src/bonsaiviewer/modules/spatial_hierarchy/Panel.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -50,21 +51,26 @@ SpatialHierarchyPanel::SpatialHierarchyPanel(QWidget* parent) tree_ = new QTreeWidget(section); tree_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - tree_->setColumnCount(2); - tree_->setHeaderLabels({"Spatial Item", ""}); + tree_->setColumnCount(3); + tree_->setHeaderLabels({"Name", "Long Name", ""}); tree_->setIconSize(QSize(16, 16)); tree_->setSelectionMode(QAbstractItemView::ExtendedSelection); tree_->setUniformRowHeights(true); + // Name is drag-resizable, Long Name fills the rest, the eye is pinned to + // the right at a fixed width. Header stays visible so the Name/Long-Name + // divider can be dragged. Initial 20/80 split applied in showEvent once the + // real width is known. tree_->header()->setStretchLastSection(false); - tree_->header()->setSectionResizeMode(0, QHeaderView::Stretch); - tree_->header()->setSectionResizeMode(1, QHeaderView::Fixed); - tree_->header()->resizeSection(1, 28); - tree_->header()->hide(); + tree_->header()->setSectionsMovable(false); + tree_->header()->setSectionResizeMode(0, QHeaderView::Interactive); // name + tree_->header()->setSectionResizeMode(1, QHeaderView::Stretch); // LongName / elevation + tree_->header()->setSectionResizeMode(2, QHeaderView::Fixed); // visibility + tree_->header()->resizeSection(2, 28); section->addBodyWidget(tree_); addBodyWidget(section); connect(tree_, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem* item, int column) { - if (!item || column != 1) return; + if (!item || column != 2) return; emit visibilityToggleRequested(itemPath(item)); }); @@ -83,6 +89,20 @@ SpatialHierarchyPanel::SpatialHierarchyPanel(QWidget* parent) }); } +void SpatialHierarchyPanel::showEvent(QShowEvent* event) { + components::Panel::showEvent(event); + // Default the Name column to 20% of the width once the panel has a real + // layout size; Long Name (stretch) takes the rest. Left interactive after, + // so the user's own drag persists. + if (!column_widths_initialized_) { + const int available = tree_->viewport()->width(); + if (available > 100) { + tree_->header()->resizeSection(0, available / 5); + column_widths_initialized_ = true; + } + } +} + void SpatialHierarchyPanel::setNodes(const QList& nodes) { tree_->clear(); for (const auto& node : nodes) { @@ -92,11 +112,15 @@ void SpatialHierarchyPanel::setNodes(const QList& nodes) { } void SpatialHierarchyPanel::addNode(QTreeWidgetItem* parent, const TreeNode& node) { - auto* item = new QTreeWidgetItem(parent, {node.name, ""}); - item->setData(1, Qt::UserRole, node.visible); + auto* item = new QTreeWidgetItem(parent, {node.name, node.detail, ""}); + item->setData(2, Qt::UserRole, node.visible); item->setSizeHint(0, QSize(0, 24)); item->setIcon(0, components::icons::makeSvgIcon(iconPath(node.kind))); - item->setIcon(1, components::icons::makeSvgIcon(node.visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg")); + // Storey elevations read as right-aligned numbers; LongNames stay left. + if (node.kind == ItemKind::Storey) { + item->setTextAlignment(1, Qt::AlignRight | Qt::AlignVCenter); + } + item->setIcon(2, components::icons::makeSvgIcon(node.visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg")); for (const auto& child : node.children) { addNode(item, child); } diff --git a/src/bonsaiviewer/modules/spatial_hierarchy/Panel.h b/src/bonsaiviewer/modules/spatial_hierarchy/Panel.h index aaa91c858a..1ff951c158 100644 --- a/src/bonsaiviewer/modules/spatial_hierarchy/Panel.h +++ b/src/bonsaiviewer/modules/spatial_hierarchy/Panel.h @@ -27,6 +27,7 @@ class QTreeWidget; class QTreeWidgetItem; +class QShowEvent; namespace bonsaiviewer::modules::spatial_hierarchy { @@ -40,12 +41,16 @@ public: signals: void visibilityToggleRequested(const NodePath& path); +protected: + void showEvent(QShowEvent* event) override; + private: void addNode(QTreeWidgetItem* parent, const TreeNode& node); NodePath itemPath(QTreeWidgetItem* item) const; QString iconPath(ItemKind kind) const; QTreeWidget* tree_ = nullptr; + bool column_widths_initialized_ = false; }; } // namespace bonsaiviewer::modules::spatial_hierarchy diff --git a/src/bonsaiviewer/modules/spatial_hierarchy/Types.h b/src/bonsaiviewer/modules/spatial_hierarchy/Types.h index 1818944648..719ef2896e 100644 --- a/src/bonsaiviewer/modules/spatial_hierarchy/Types.h +++ b/src/bonsaiviewer/modules/spatial_hierarchy/Types.h @@ -36,6 +36,7 @@ enum class ItemKind { struct TreeNode { QString name; + QString detail; // secondary column: LongName, or the elevation for storeys ItemKind kind = ItemKind::Space; bool visible = true; QList children; diff --git a/src/bonsaiviewer/modules/spatial_hierarchy/View.cpp b/src/bonsaiviewer/modules/spatial_hierarchy/View.cpp index 6829e26628..765fe7273c 100644 --- a/src/bonsaiviewer/modules/spatial_hierarchy/View.cpp +++ b/src/bonsaiviewer/modules/spatial_hierarchy/View.cpp @@ -27,7 +27,8 @@ #include "../../../ifcparse/file.h" #include "../../../ifcparse/schema.h" -#include "element.h" // helpers: get_spatial_children, get_string_attribute +#include "element.h" // helpers: get_spatial_children, get_string_attribute +#include "placement.h" // helpers: get_storey_elevation #include @@ -79,6 +80,13 @@ TreeNode buildNode(const express::Base& element) { node.name = displayName(element); node.kind = kindOf(element); node.visible = true; + // Secondary column: the storey elevation, else the LongName when filled. + if (node.kind == ItemKind::Storey) { + node.detail = QString::number(get_storey_elevation(element)); + } else if (auto long_name = get_string_attribute(element, "LongName"); + long_name && !long_name->empty()) { + node.detail = QString::fromStdString(*long_name); + } for (const auto& child : get_spatial_children(element)) { node.children.append(buildNode(child)); } diff --git a/src/helpers/placement.cpp b/src/helpers/placement.cpp index f0fdfd75b9..37c0c5d29a 100644 --- a/src/helpers/placement.cpp +++ b/src/helpers/placement.cpp @@ -20,8 +20,11 @@ #include "placement.h" #include "../ifcparse/exception.h" +#include "../ifcparse/instance_data.h" +#include "../ifcparse/schema.h" #include "schema_dispatch.i" +#include #include #include @@ -125,6 +128,31 @@ Eigen::Matrix4d get_local_placement_s(const express::Base& placement) { return get_axis2_placement_s(placement); } +// ifcopenshell.util.placement.get_storey_elevation: the Z of the storey's +// placement in project units, falling back to the Elevation attribute. +template +double get_storey_elevation_s(const express::Base& storey) { + const auto typed = storey.template as(); + if (!typed) { + return 0.0; + } + if (const auto placement = typed.ObjectPlacement()) { + return get_local_placement_s(placement)(2, 3); + } + // Fallback: the optional Elevation attribute (read by name). + const ifcopenshell::entity* declaration = storey.declaration().as_entity(); + if (declaration != nullptr) { + const std::ptrdiff_t index = declaration->attribute_index("Elevation"); + if (index >= 0) { + const attribute_value value = storey.get_attribute_value(static_cast(index)); + if (!value.isNull() && value.type() == ifcopenshell::Argument_DOUBLE) { + return static_cast(value); + } + } + } + return 0.0; +} + } // namespace Eigen::Matrix4d axes_to_placement(const Eigen::Vector3d& origin, @@ -167,3 +195,16 @@ Eigen::Matrix4d get_local_placement(const express::Base& placement) { #undef IFCOPENSHELL_DISPATCH unsupported_schema(name); } + +double get_storey_elevation(const express::Base& storey) { + if (!storey) { + return 0.0; + } + const auto name = storey.declaration().schema()->name(); +#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \ + if (name == Identifier) \ + return get_storey_elevation_s(storey); + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(name); +} diff --git a/src/helpers/placement.h b/src/helpers/placement.h index 72dd8a3c1e..1cf2d711f9 100644 --- a/src/helpers/placement.h +++ b/src/helpers/placement.h @@ -47,4 +47,9 @@ Eigen::Matrix4d get_axis2_placement(const express::Base& placement); // identity for a null input. Eigen::Matrix4d get_local_placement(const express::Base& placement); +// ifcopenshell.util.placement.get_storey_elevation: the Z elevation of an +// IfcBuildingStorey in the project's length unit — the Z of its placement, or +// the Elevation attribute as a fallback. 0 for a non-storey or null input. +double get_storey_elevation(const express::Base& storey); + #endif // PLACEMENT_H