From bdc6d2340fdad7797870ab1c7dc532ae1c68ed94 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Fri, 14 Aug 2026 08:20:52 -0500 Subject: [PATCH] Fix malformed LoadBearing property templates in IFC4 psets Pset_RoofCommon, Pset_RampCommon and Pset_StairCommon each declared their LoadBearing IfcSimplePropertyTemplate with 11 attributes instead of 12 - the `$` placeholder for OwnerHistory was dropped in 822107a6ed, shifting every following attribute by one: 1 OwnerHistory 'LoadBearing' <- should be $ 2 Name 'Indicates whether the object is intended to carry...' 4 TemplateType 'IfcBoolean' <- not a valid P_* enum The property therefore surfaced with its description as a name and no valid template type, so it never showed up in the property set UI. Restore the missing OwnerHistory placeholder, and add regression tests covering both the three affected psets and the general well-formedness of all bundled Pset_*.ifc property templates. Co-Authored-By: Claude Opus 5 --- .../util/schema/Pset_IFC4_ADD2.ifc | 6 ++-- .../test/util/test_pset.py | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/schema/Pset_IFC4_ADD2.ifc b/src/ifcopenshell-python/ifcopenshell/util/schema/Pset_IFC4_ADD2.ifc index 940c2c4d84..b096ce9259 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/schema/Pset_IFC4_ADD2.ifc +++ b/src/ifcopenshell-python/ifcopenshell/util/schema/Pset_IFC4_ADD2.ifc @@ -3811,7 +3811,7 @@ DATA; #3805= IFCLIBRARYREFERENCE($,$,'\X2\662F5426591690E867844EF6\X0\','\X2\8868793A8BE567844EF6662F54268BBE8BA14E3A591690E867844EF6300282E5662FFF0C52198BE567844EF64E3A591690E867844EF6FF0C671D54115EFA7B517269768459164FA73002\X0\','zh-CN',$); #3806= IFCRELASSOCIATESLIBRARY('0ny33eYS11mR15A8dSdsB_',$,$,$,(#3796),#3805); #3807= IFCSIMPLEPROPERTYTEMPLATE('3MLW9Uqir2HPZ8iRLwgUEW',$,'ThermalTransmittance','Thermal transmittance coefficient (U-Value) of an element, within the direction of the thermal flow (including all materials).',.P_SINGLEVALUE.,'IfcThermalTransmittanceMeasure',$,$,$,$,$,.READWRITE.); -#3808= IFCSIMPLEPROPERTYTEMPLATE('1s7fxao7L4X9mgLw5vDLbk','LoadBearing','Indicates whether the object is intended to carry loads (TRUE) or not (FALSE).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); +#3808= IFCSIMPLEPROPERTYTEMPLATE('1s7fxao7L4X9mgLw5vDLbk',$,'LoadBearing','Indicates whether the object is intended to carry loads (TRUE) or not (FALSE).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); #3809= IFCSIMPLEPROPERTYTEMPLATE('1LZHC0qVyHuO00025QrE$V',$,'FireRating','Fire rating for this object.\X\0AIt is given according to the national fire safety classification.',.P_SINGLEVALUE.,'IfcLabel','',$,$,$,$,.READWRITE.); #3810= IFCLIBRARYREFERENCE($,$,'Feuerwiderstandsklasse','Feuerwiderstandasklasse gem\S\d\S\_ der nationalen oder regionalen Brandschutzverordnung.','de-DE',$); #3811= IFCRELASSOCIATESLIBRARY('2rsNjFcwD3xfKDL2kSNZLX',$,$,$,(#3809),#3810); @@ -3986,7 +3986,7 @@ DATA; #3980= IFCRELASSOCIATESLIBRARY('07nF4Mq1D99gQJGAXkyvYq',$,$,$,(#3972),#3979); #3981= IFCLIBRARYREFERENCE($,$,'\X2\5BFC70ED7CFB6570\X0\','\X2\6750659976845BFC70ED7CFB6570FF08\X0\U\X2\503CFF093002\X0\\X\0A\X2\8868793A7A7F8FC78BE55C4B987688689762768465744F535BFC70ED7CFB6570FF08530562EC6240670967506599FF09\X0\','zh-CN',$); #3982= IFCRELASSOCIATESLIBRARY('0gCn46liz97xidokY4jCk4',$,$,$,(#3972),#3981); -#3983= IFCSIMPLEPROPERTYTEMPLATE('27gGzXJgHElgJNKZaas8aH','LoadBearing','Indicates whether the object is intended to carry loads (TRUE) or not (FALSE).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); +#3983= IFCSIMPLEPROPERTYTEMPLATE('27gGzXJgHElgJNKZaas8aH',$,'LoadBearing','Indicates whether the object is intended to carry loads (TRUE) or not (FALSE).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); #3984= IFCSIMPLEPROPERTYTEMPLATE('2rXBa0qW4HuO00025QrE$V',$,'FireRating','Fire rating for this object. It is given according to the national fire safety classification.',.P_SINGLEVALUE.,'IfcLabel','',$,$,$,$,.READWRITE.); #3985= IFCLIBRARYREFERENCE($,$,'Feuerwiderstandsklasse','Feuerwiderstandasklasse gem\S\d\S\_ der nationalen oder regionalen Brandschutzverordnung.','de-DE',$); #3986= IFCRELASSOCIATESLIBRARY('068HwrG5X1bfDVqpvbqRhT',$,$,$,(#3984),#3985); @@ -4432,7 +4432,7 @@ DATA; #4426= IFCLIBRARYREFERENCE($,$,'\X2\662F5426591690E867844EF6\X0\','\X2\8868793A8BE567844EF6662F54268BBE8BA14E3A591690E867844EF6300282E5662FFF0C52198BE567844EF64E3A591690E867844EF6FF0C671D54115EFA7B517269768459164FA73002\X0\','zh-CN',$); #4427= IFCRELASSOCIATESLIBRARY('0H77FP34b3XRusar_DVCi_',$,$,$,(#4417),#4426); #4428= IFCSIMPLEPROPERTYTEMPLATE('3YhweyeoP4yfJpfFi81P$3',$,'ThermalTransmittance','Thermal transmittance coefficient (U-Value) of an element, within the direction of the thermal flow (including all materials).',.P_SINGLEVALUE.,'IfcThermalTransmittanceMeasure',$,$,$,$,$,.READWRITE.); -#4429= IFCSIMPLEPROPERTYTEMPLATE('2FnJNsRdfDQ9dzEq8OinWs','LoadBearing','Indicates whether the object is intended to carry loads (TRUE) or not (FALSE).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); +#4429= IFCSIMPLEPROPERTYTEMPLATE('2FnJNsRdfDQ9dzEq8OinWs',$,'LoadBearing','Indicates whether the object is intended to carry loads (TRUE) or not (FALSE).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); #4430= IFCSIMPLEPROPERTYTEMPLATE('0P_KM0qWqHuO00025QrE$V',$,'FireRating','Fire rating for this object.\X\0AIt is given according to the national fire safety classification.',.P_SINGLEVALUE.,'IfcLabel','',$,$,$,$,.READWRITE.); #4431= IFCLIBRARYREFERENCE($,$,'Feuerwiderstandsklasse','Feuerwiderstandasklasse gem\S\d\S\_ der nationalen oder regionalen Brandschutzverordnung.','de-DE',$); #4432= IFCRELASSOCIATESLIBRARY('2t9U7nMxj0UurpJn97sDMt',$,$,$,(#4430),#4431); diff --git a/src/ifcopenshell-python/test/util/test_pset.py b/src/ifcopenshell-python/test/util/test_pset.py index bec1a10ba0..35f7031afb 100644 --- a/src/ifcopenshell-python/test/util/test_pset.py +++ b/src/ifcopenshell-python/test/util/test_pset.py @@ -18,10 +18,39 @@ """Run this test from src/ifcopenshell-python folder: pytest --durations=0 ifcopenshell/util/test_pset.py""" +import pytest + from ifcopenshell.util import pset from ifcopenshell.util.pset import ApplicableEntity +class TestPsetTemplateFiles: + @pytest.mark.parametrize("schema", ("IFC2X3", "IFC4", "IFC4X3")) + def test_property_templates_are_well_formed(self, schema): + """Guard against attribute shifts when the bundled Pset_*.ifc files are patched by hand. + + A missing `$` for OwnerHistory silently shifts every following attribute, + which makes the property show up with its description as a name and no + template type - so it's dropped from the UI. See #5708. + """ + template = pset.get_template(schema) + for ifc_file in template.templates: + for property_template in ifc_file.by_type("IfcSimplePropertyTemplate"): + assert property_template.OwnerHistory is None, property_template + assert property_template.Name, property_template + template_type = property_template.TemplateType + assert template_type and template_type.startswith(("P_", "Q_")), property_template + + @pytest.mark.parametrize("pset_name", ("Pset_RoofCommon", "Pset_RampCommon", "Pset_StairCommon")) + def test_load_bearing_is_defined_in_ifc4(self, pset_name): + template = pset.get_template("IFC4") + pset_template = template.get_by_name(pset_name) + assert pset_template is not None + properties = {p.Name: p for p in pset_template.HasPropertyTemplates} + assert "LoadBearing" in properties + assert properties["LoadBearing"].PrimaryMeasureType == "IfcBoolean" + + class TestPsetQto: @classmethod def setup_class(cls):