From c424d1b48029c20b519cb9e53d52cc661a71ef66 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Tue, 21 Jul 2026 17:43:06 +0300 Subject: [PATCH] ifcgeom: resolve default materials through the schema supertype chain (#473) get_default_style did an exact-name map lookup, so subtypes like IfcWallStandardCase or IfcSlabStandardCase never inherited the default material of their parent type, falling through to the generic grey DefaultMaterial instead. This was reported in 2018 (#473) and the fix direction (walk up the supertype chain once the schema runtime made supertypes available) was agreed there, but never implemented. On an exact-match miss the lookup now walks decl->supertype() until a mapped ancestor is found, caching the result under the leaf name for O(1) subsequent lookups. The product's user-facing type string is deliberately untouched; only the style lookup gained inheritance. Applies equally to the built-in defaults and --default-material-file. This change was written with AI assistance. --- src/ifcgeom/Converter.cpp | 2 +- src/ifcgeom/IfcGeomRenderStyles.h | 12 +++++++++++- src/ifcgeom/IfcGeomRepresentation.cpp | 2 +- src/ifcgeom/IfcGeomRepresentation.h | 20 +++++++++++++++++--- src/ifcgeom/SurfaceStyle.cpp | 23 ++++++++++++++++++++++- 5 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/ifcgeom/Converter.cpp b/src/ifcgeom/Converter.cpp index 15a4584088..f091f1ceda 100644 --- a/src/ifcgeom/Converter.cpp +++ b/src/ifcgeom/Converter.cpp @@ -244,7 +244,7 @@ IfcGeom::BRepElement* ifcopenshell::geometry::Converter::create_brep_for_represe } } - shape = new IfcGeom::Representation::BRep(settings_, product_type, representation_id_builder.str(), shapes); + shape = new IfcGeom::Representation::BRep(settings_, product_type, representation_id_builder.str(), shapes, &product->declaration()); std::string context_string = ""; diff --git a/src/ifcgeom/IfcGeomRenderStyles.h b/src/ifcgeom/IfcGeomRenderStyles.h index 2486f2c9dc..ecf71f0e71 100644 --- a/src/ifcgeom/IfcGeomRenderStyles.h +++ b/src/ifcgeom/IfcGeomRenderStyles.h @@ -30,8 +30,18 @@ #include #include +namespace IfcParse { + class declaration; +} + namespace IfcGeom { - IFC_GEOM_API const ifcopenshell::geometry::taxonomy::style::ptr& get_default_style(const std::string& ifc_type); + // Looks up the default (fallback) style registered for `ifc_type`. When no style is + // registered for that exact type and `decl` is provided, the type's supertype chain + // (as defined by the IFC schema) is walked until a registered ancestor is found, so + // e.g. IfcSlabStandardCase inherits the style registered for IfcSlab. Falls back to + // the generic default style ("*" in a --default-material-file, or a fixed grey) when + // neither the type nor any of its supertypes have a registered style. + IFC_GEOM_API const ifcopenshell::geometry::taxonomy::style::ptr& get_default_style(const std::string& ifc_type, const IfcParse::declaration* decl = nullptr); IFC_GEOM_API ifcopenshell::geometry::taxonomy::style::ptr& update_default_style(const std::string& ifc_type); diff --git a/src/ifcgeom/IfcGeomRepresentation.cpp b/src/ifcgeom/IfcGeomRepresentation.cpp index d533adf0df..f1995a2e41 100644 --- a/src/ifcgeom/IfcGeomRepresentation.cpp +++ b/src/ifcgeom/IfcGeomRepresentation.cpp @@ -137,7 +137,7 @@ IfcGeom::Representation::Triangulation::Triangulation(const BRep& shape_model) } if (settings().get().get() && surface_style_id == -1) { - const auto& material = IfcGeom::get_default_style(shape_model.entity()); + const auto& material = IfcGeom::get_default_style(shape_model.entity(), shape_model.declaration()); auto mit = std::find(materials_.begin(), materials_.end(), material); if (mit == materials_.end()) { surface_style_id = (int)materials_.size(); diff --git a/src/ifcgeom/IfcGeomRepresentation.h b/src/ifcgeom/IfcGeomRepresentation.h index 2bafa9ab88..8f78687791 100644 --- a/src/ifcgeom/IfcGeomRepresentation.h +++ b/src/ifcgeom/IfcGeomRepresentation.h @@ -25,6 +25,10 @@ #include +namespace IfcParse { + class declaration; +} + namespace IfcGeom { namespace Representation { @@ -36,16 +40,26 @@ namespace IfcGeom { const ifcopenshell::geometry::Settings settings_; const std::string entity_; std::string id_; + // Most-derived IFC type declaration of the product this representation was + // built for, used to walk the supertype chain when resolving a default + // (fallback) style so subtypes such as IfcSlabStandardCase inherit the + // style registered for IfcSlab. May be null (e.g. for reconstructed / + // deserialized representations), in which case no inheritance is applied. + const IfcParse::declaration* declaration_ = nullptr; public: - explicit Representation(const ifcopenshell::geometry::Settings& settings, const std::string& entity, const std::string& id) + explicit Representation(const ifcopenshell::geometry::Settings& settings, const std::string& entity, const std::string& id, const IfcParse::declaration* decl = nullptr) : settings_(settings) , entity_(entity) , id_(id) + , declaration_(decl) {} const ifcopenshell::geometry::Settings& settings() const { return settings_; } const std::string& entity() const { return entity_; } + const IfcParse::declaration* declaration() const { + return declaration_; + } // id starts with representation id and then it may have the following dash separated elements: // - layerset-layerset_id // - material-material_id @@ -60,8 +74,8 @@ namespace IfcGeom { BRep(const BRep& other); BRep& operator=(const BRep& other); public: - BRep(const ifcopenshell::geometry::Settings& settings, const std::string& entity, const std::string& id, const IfcGeom::ConversionResults& shapes) - : Representation(settings, entity, id) + BRep(const ifcopenshell::geometry::Settings& settings, const std::string& entity, const std::string& id, const IfcGeom::ConversionResults& shapes, const IfcParse::declaration* decl = nullptr) + : Representation(settings, entity, id, decl) , shapes_(shapes) {} virtual ~BRep() {} diff --git a/src/ifcgeom/SurfaceStyle.cpp b/src/ifcgeom/SurfaceStyle.cpp index 69310f3929..29b6f0f476 100644 --- a/src/ifcgeom/SurfaceStyle.cpp +++ b/src/ifcgeom/SurfaceStyle.cpp @@ -1,4 +1,5 @@ #include "../ifcgeom/IfcGeomRenderStyles.h" +#include "../ifcparse/IfcSchema.h" #include #include @@ -112,15 +113,35 @@ void IfcGeom::set_default_style_file(const std::string& json_file) { } } -const ifcopenshell::geometry::taxonomy::style::ptr& IfcGeom::get_default_style(const std::string& s) { +const ifcopenshell::geometry::taxonomy::style::ptr& IfcGeom::get_default_style(const std::string& s, const IfcParse::declaration* decl) { static std::mutex m; std::lock_guard lk(m); if (!default_materials_initialized) InitDefaultMaterials(); auto it = default_materials.find(s); + if (it == default_materials.end() && decl != nullptr) { + // No style registered for this exact type. Walk up the schema's supertype + // chain (e.g. IfcSlabStandardCase -> IfcSlab) so a style registered for a + // supertype is inherited by its subtypes, rather than always falling back + // to the generic default style. + const IfcParse::entity* entity = decl->as_entity(); + const IfcParse::entity* super = entity ? entity->supertype() : nullptr; + while (super != nullptr) { + auto super_it = default_materials.find(super->name()); + if (super_it != default_materials.end()) { + it = super_it; + break; + } + super = super->supertype(); + } + } if (it == default_materials.end()) { default_materials.insert(std::make_pair(s, default_material)); it = default_materials.find(s); + } else if (it->first != s) { + // Cache the resolved (inherited) style under the leaf type name too, so + // repeated lookups for this exact type are O(1) afterwards. + default_materials.insert(std::make_pair(s, it->second)); } return it->second; }