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