mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 02:07:36 +00:00
spatial hierarchy: storey elevation + Long Name column + resizable layout
- helpers/placement: port get_storey_elevation (placement Z, falling back to the Elevation attribute), matching ifcopenshell.util.placement. - Add a secondary column: the storey elevation for IfcBuildingStorey, otherwise the LongName when filled. Elevations are right-aligned. - Columns: Name is drag-resizable (interactive) and defaults to 20% of the width, Long Name stretches to fill the rest, and the eye is pinned to the right at a fixed width. Header shown so the divider can be grabbed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
#include <QShowEvent>
|
||||||
#include <QSizePolicy>
|
#include <QSizePolicy>
|
||||||
#include <QTreeWidget>
|
#include <QTreeWidget>
|
||||||
#include <QTreeWidgetItem>
|
#include <QTreeWidgetItem>
|
||||||
@@ -50,21 +51,26 @@ SpatialHierarchyPanel::SpatialHierarchyPanel(QWidget* parent)
|
|||||||
|
|
||||||
tree_ = new QTreeWidget(section);
|
tree_ = new QTreeWidget(section);
|
||||||
tree_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
tree_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
tree_->setColumnCount(2);
|
tree_->setColumnCount(3);
|
||||||
tree_->setHeaderLabels({"Spatial Item", ""});
|
tree_->setHeaderLabels({"Name", "Long Name", ""});
|
||||||
tree_->setIconSize(QSize(16, 16));
|
tree_->setIconSize(QSize(16, 16));
|
||||||
tree_->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
tree_->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
tree_->setUniformRowHeights(true);
|
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()->setStretchLastSection(false);
|
||||||
tree_->header()->setSectionResizeMode(0, QHeaderView::Stretch);
|
tree_->header()->setSectionsMovable(false);
|
||||||
tree_->header()->setSectionResizeMode(1, QHeaderView::Fixed);
|
tree_->header()->setSectionResizeMode(0, QHeaderView::Interactive); // name
|
||||||
tree_->header()->resizeSection(1, 28);
|
tree_->header()->setSectionResizeMode(1, QHeaderView::Stretch); // LongName / elevation
|
||||||
tree_->header()->hide();
|
tree_->header()->setSectionResizeMode(2, QHeaderView::Fixed); // visibility
|
||||||
|
tree_->header()->resizeSection(2, 28);
|
||||||
section->addBodyWidget(tree_);
|
section->addBodyWidget(tree_);
|
||||||
addBodyWidget(section);
|
addBodyWidget(section);
|
||||||
|
|
||||||
connect(tree_, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem* item, int column) {
|
connect(tree_, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem* item, int column) {
|
||||||
if (!item || column != 1) return;
|
if (!item || column != 2) return;
|
||||||
emit visibilityToggleRequested(itemPath(item));
|
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<TreeNode>& nodes) {
|
void SpatialHierarchyPanel::setNodes(const QList<TreeNode>& nodes) {
|
||||||
tree_->clear();
|
tree_->clear();
|
||||||
for (const auto& node : nodes) {
|
for (const auto& node : nodes) {
|
||||||
@@ -92,11 +112,15 @@ void SpatialHierarchyPanel::setNodes(const QList<TreeNode>& nodes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SpatialHierarchyPanel::addNode(QTreeWidgetItem* parent, const TreeNode& node) {
|
void SpatialHierarchyPanel::addNode(QTreeWidgetItem* parent, const TreeNode& node) {
|
||||||
auto* item = new QTreeWidgetItem(parent, {node.name, ""});
|
auto* item = new QTreeWidgetItem(parent, {node.name, node.detail, ""});
|
||||||
item->setData(1, Qt::UserRole, node.visible);
|
item->setData(2, Qt::UserRole, node.visible);
|
||||||
item->setSizeHint(0, QSize(0, 24));
|
item->setSizeHint(0, QSize(0, 24));
|
||||||
item->setIcon(0, components::icons::makeSvgIcon(iconPath(node.kind)));
|
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) {
|
for (const auto& child : node.children) {
|
||||||
addNode(item, child);
|
addNode(item, child);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
class QTreeWidget;
|
class QTreeWidget;
|
||||||
class QTreeWidgetItem;
|
class QTreeWidgetItem;
|
||||||
|
class QShowEvent;
|
||||||
|
|
||||||
namespace bonsaiviewer::modules::spatial_hierarchy {
|
namespace bonsaiviewer::modules::spatial_hierarchy {
|
||||||
|
|
||||||
@@ -40,12 +41,16 @@ public:
|
|||||||
signals:
|
signals:
|
||||||
void visibilityToggleRequested(const NodePath& path);
|
void visibilityToggleRequested(const NodePath& path);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void showEvent(QShowEvent* event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addNode(QTreeWidgetItem* parent, const TreeNode& node);
|
void addNode(QTreeWidgetItem* parent, const TreeNode& node);
|
||||||
NodePath itemPath(QTreeWidgetItem* item) const;
|
NodePath itemPath(QTreeWidgetItem* item) const;
|
||||||
QString iconPath(ItemKind kind) const;
|
QString iconPath(ItemKind kind) const;
|
||||||
|
|
||||||
QTreeWidget* tree_ = nullptr;
|
QTreeWidget* tree_ = nullptr;
|
||||||
|
bool column_widths_initialized_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bonsaiviewer::modules::spatial_hierarchy
|
} // namespace bonsaiviewer::modules::spatial_hierarchy
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ enum class ItemKind {
|
|||||||
|
|
||||||
struct TreeNode {
|
struct TreeNode {
|
||||||
QString name;
|
QString name;
|
||||||
|
QString detail; // secondary column: LongName, or the elevation for storeys
|
||||||
ItemKind kind = ItemKind::Space;
|
ItemKind kind = ItemKind::Space;
|
||||||
bool visible = true;
|
bool visible = true;
|
||||||
QList<TreeNode> children;
|
QList<TreeNode> children;
|
||||||
|
|||||||
@@ -27,7 +27,8 @@
|
|||||||
#include "../../../ifcparse/file.h"
|
#include "../../../ifcparse/file.h"
|
||||||
#include "../../../ifcparse/schema.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 <QCollator>
|
#include <QCollator>
|
||||||
|
|
||||||
@@ -79,6 +80,13 @@ TreeNode buildNode(const express::Base& element) {
|
|||||||
node.name = displayName(element);
|
node.name = displayName(element);
|
||||||
node.kind = kindOf(element);
|
node.kind = kindOf(element);
|
||||||
node.visible = true;
|
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)) {
|
for (const auto& child : get_spatial_children(element)) {
|
||||||
node.children.append(buildNode(child));
|
node.children.append(buildNode(child));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,11 @@
|
|||||||
#include "placement.h"
|
#include "placement.h"
|
||||||
|
|
||||||
#include "../ifcparse/exception.h"
|
#include "../ifcparse/exception.h"
|
||||||
|
#include "../ifcparse/instance_data.h"
|
||||||
|
#include "../ifcparse/schema.h"
|
||||||
#include "schema_dispatch.i"
|
#include "schema_dispatch.i"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -125,6 +128,31 @@ Eigen::Matrix4d get_local_placement_s(const express::Base& placement) {
|
|||||||
return get_axis2_placement_s<Schema>(placement);
|
return get_axis2_placement_s<Schema>(placement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ifcopenshell.util.placement.get_storey_elevation: the Z of the storey's
|
||||||
|
// placement in project units, falling back to the Elevation attribute.
|
||||||
|
template <typename Schema>
|
||||||
|
double get_storey_elevation_s(const express::Base& storey) {
|
||||||
|
const auto typed = storey.template as<typename Schema::IfcBuildingStorey>();
|
||||||
|
if (!typed) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (const auto placement = typed.ObjectPlacement()) {
|
||||||
|
return get_local_placement_s<Schema>(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<std::size_t>(index));
|
||||||
|
if (!value.isNull() && value.type() == ifcopenshell::Argument_DOUBLE) {
|
||||||
|
return static_cast<double>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Eigen::Matrix4d axes_to_placement(const Eigen::Vector3d& origin,
|
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
|
#undef IFCOPENSHELL_DISPATCH
|
||||||
unsupported_schema(name);
|
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<Schema>(storey);
|
||||||
|
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
|
||||||
|
#undef IFCOPENSHELL_DISPATCH
|
||||||
|
unsupported_schema(name);
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,4 +47,9 @@ Eigen::Matrix4d get_axis2_placement(const express::Base& placement);
|
|||||||
// identity for a null input.
|
// identity for a null input.
|
||||||
Eigen::Matrix4d get_local_placement(const express::Base& placement);
|
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
|
#endif // PLACEMENT_H
|
||||||
|
|||||||
Reference in New Issue
Block a user