From c5ea61211087ed0292e64cd55cec5949266e1cc4 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 8 Jul 2026 14:48:09 +1000 Subject: [PATCH] helpers: port util.element.get_predefined_type; show it in properties Add src/helpers/element.{h,cpp}, a schema-dispatched C++ port of ifcopenshell.util.element.get_predefined_type: prefers the associated type element's predefined type (IsTypedBy / IsDefinedBy), falls back to ElementType / ProcessType when USERDEFINED, then the occurrence's own PredefinedType / ObjectType. Attribute reads are by-name so they work across the IfcElement / IfcType* subtypes that carry these attributes. Wire it into the properties panel entity summary: live IFC entities show their real predefined type; geometry-only elements (a .ifcview loaded without its .ifc/.rdb) show "N/A". Clears the placeholder so a stale predefined type no longer leaks once a project is loaded. Co-Authored-By: Claude Opus 4.8 --- src/bonsaiviewer/modules/properties/View.cpp | 12 ++ src/helpers/CMakeLists.txt | 1 + src/helpers/element.cpp | 146 +++++++++++++++++++ src/helpers/element.h | 38 +++++ 4 files changed, 197 insertions(+) create mode 100644 src/helpers/element.cpp create mode 100644 src/helpers/element.h diff --git a/src/bonsaiviewer/modules/properties/View.cpp b/src/bonsaiviewer/modules/properties/View.cpp index 0e23e57c5c..0b992e0f1a 100644 --- a/src/bonsaiviewer/modules/properties/View.cpp +++ b/src/bonsaiviewer/modules/properties/View.cpp @@ -25,6 +25,8 @@ #include "../../ElementRegistry.h" #include "../../SessionState.h" +#include "element.h" // helpers: get_predefined_type + namespace bonsaiviewer::modules::properties { PropertiesPanelView::PropertiesPanelView(PropertiesPanel* widget, @@ -90,9 +92,17 @@ void PropertiesPanelView::refresh(uint32_t object_id) { return; } + // A real project is loaded — don't leak the placeholder predefined type. + // It's populated below from live IFC data, or set to "N/A" for + // geometry-only elements that have no data to read it from. + state.entity.predefined_type.clear(); + auto entity = registry->findEntity(object_id); if (entity) { state.entity.entity_class = QString::fromStdString(entity->declaration().name()); + if (auto predefined_type = get_predefined_type(*entity)) { + state.entity.predefined_type = QString::fromStdString(*predefined_type); + } if (!state.property_sets.isEmpty() && !state.property_sets[1].rows.isEmpty()) { state.property_sets[1].rows[0].value = state.entity.entity_class; } @@ -104,6 +114,8 @@ void PropertiesPanelView::refresh(uint32_t object_id) { auto info = registry->findBasicElementInfo(object_id); if (info && !info->type.isEmpty()) { state.entity.entity_class = info->type; + // Geometry only — no live IFC entity to read a predefined type from. + state.entity.predefined_type = "N/A"; if (!state.property_sets.isEmpty() && !state.property_sets[1].rows.isEmpty()) { state.property_sets[1].rows[0].value = info->type; } diff --git a/src/helpers/CMakeLists.txt b/src/helpers/CMakeLists.txt index 7abd7411de..c21c56113f 100644 --- a/src/helpers/CMakeLists.txt +++ b/src/helpers/CMakeLists.txt @@ -32,6 +32,7 @@ message("Running CMakeLists.txt in /src/helpers") # (ifcopenshell.util.placement) # * Pset — property and quantity retrieval # (ifcopenshell.util.element) +# * Element — get_predefined_type (ifcopenshell.util.element) find_package(Eigen3 REQUIRED) diff --git a/src/helpers/element.cpp b/src/helpers/element.cpp new file mode 100644 index 0000000000..b9968a8aba --- /dev/null +++ b/src/helpers/element.cpp @@ -0,0 +1,146 @@ +/******************************************************************************** + * * + * 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 * + * GNU Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +// This file was generated with the assistance of an AI coding tool. + +#include "element.h" + +#include "../ifcparse/exception.h" +#include "../ifcparse/file.h" +#include "../ifcparse/instance_data.h" +#include "../ifcparse/schema.h" +#include "schema_dispatch.i" + +#include +#include + +namespace { + +template +struct is_ifc4_or_higher : std::false_type {}; + +template +struct is_ifc4_or_higher> : std::true_type {}; + +std::string schema_name(const express::Base& instance) { + return instance.declaration().schema()->name(); +} + +[[noreturn]] void unsupported_schema(const std::string& name) { + throw ifcopenshell::exception("No helper implementation was built for schema " + name); +} + +// getattr(element, name) for a string- or enum-valued attribute. std::nullopt +// when the attribute is not part of this entity's type (Python's absent +// getattr) or when it is IFC null. Reads by name, so it works uniformly across +// the various IfcElement / IfcType* subtypes that carry PredefinedType et al. +std::optional get_string_attribute(const express::Base& element, + const std::string& name) { + const ifcopenshell::entity* declaration = element.declaration().as_entity(); + if (declaration == nullptr) { + return std::nullopt; + } + const std::ptrdiff_t index = declaration->attribute_index(name); + if (index < 0) { + return std::nullopt; + } + const attribute_value value = element.get_attribute_value(static_cast(index)); + if (value.isNull()) { + return std::nullopt; + } + switch (value.type()) { + case ifcopenshell::Argument_ENUMERATION: { + const enumeration_reference enumeration = value; + return enumeration.value() ? std::string(enumeration.value()) : std::string(); + } + case ifcopenshell::Argument_STRING: + return static_cast(value); + default: + return std::nullopt; + } +} + +// ifcopenshell.util.element.get_type: the construction type of an occurrence +// (get_type(type_element) == type_element). +template +express::Base get_type_s(const express::Base& element) { + if (element.template as()) { + return element; + } + const auto object = element.template as(); + if (!object) { + return {}; + } + if constexpr (is_ifc4_or_higher::value) { + const auto relationships = object.IsTypedBy(); + if (!relationships.empty()) { + return relationships.front().RelatingType(); + } + } else { + for (const auto& relationship : object.IsDefinedBy()) { + if (auto by_type = relationship.template as()) { + return by_type.RelatingType(); + } + } + } + return {}; +} + +template +std::optional get_predefined_type_s(const express::Base& element) { + // Prefer the associated type element's predefined type. + if (const express::Base type = get_type_s(element)) { + std::optional predefined_type = get_string_attribute(type, "PredefinedType"); + if (!predefined_type || *predefined_type == "USERDEFINED") { + // ElementType (IfcElementType) or ProcessType (IfcTypeProcess) — the + // two are mutually exclusive by type, so whichever is present wins. + std::optional custom = get_string_attribute(type, "ElementType"); + if (!custom) { + custom = get_string_attribute(type, "ProcessType"); + } + predefined_type = custom; + } + if (predefined_type && !predefined_type->empty() && *predefined_type != "NOTDEFINED") { + return predefined_type; + } + } + + // Fall back to the occurrence's own predefined type / user-defined ObjectType. + std::optional predefined_type = get_string_attribute(element, "PredefinedType"); + if (!predefined_type || *predefined_type == "USERDEFINED") { + predefined_type = get_string_attribute(element, "ObjectType"); + } + return predefined_type; +} + +} // namespace + +std::optional get_predefined_type(const express::Base& element) { + if (!element) { + return std::nullopt; + } + const std::string name = schema_name(element); +#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \ + if (name == Identifier) { \ + return get_predefined_type_s(element); \ + } + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(name); +} diff --git a/src/helpers/element.h b/src/helpers/element.h new file mode 100644 index 0000000000..c340c262f4 --- /dev/null +++ b/src/helpers/element.h @@ -0,0 +1,38 @@ +/******************************************************************************** + * * + * 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 * + * GNU Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +// This file was generated with the assistance of an AI coding tool. + +#ifndef ELEMENT_H +#define ELEMENT_H + +#include "../ifcparse/express.h" + +#include +#include + +// Mirrors ifcopenshell.util.element.get_predefined_type. Returns the element's +// PredefinedType, falling back to the user-defined ObjectType / ElementType / +// ProcessType when it is USERDEFINED or unset, and preferring the predefined +// type of the associated type element (via IsTypedBy / IsDefinedBy) first. +// std::nullopt when there is no such attribute (e.g. the element is not an +// IfcObject, or a geometry-only proxy with no live IFC data). +std::optional get_predefined_type(const express::Base& element); + +#endif // ELEMENT_H