diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/add_pset.py b/src/ifcopenshell-python/ifcopenshell/api/pset/add_pset.py index db585c3276..f1a99bd847 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/add_pset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/add_pset.py @@ -66,6 +66,10 @@ def add_pset( like "Pset_WallCommon". If you create your own, you must not use that prefix. It is recommended to use your own prefix tailored to your project, company, or local government requirement. + + In IFC2X3 should be provided as an empty string for profile properties + (they all don't have a name property) and all material properties + besides IfcExtendedMaterialProperties. :param ifc2x3_subclass: IFC2X3 subclass for material or profile properties. In IFC2X3 IfcProfileProperties and IfcMaterialProperties are abstract so you need one of their subclasses to instantiate them. diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 4f21aa8a98..0643d7480a 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -72,6 +72,7 @@ def get_pset( type_pset = None ifc_file = element.file is_ifc2x3 = ifc_file.schema == "IFC2X3" + is_profile = False if element.is_a("IfcTypeObject"): for definition in element.HasPropertySets or []: @@ -81,14 +82,19 @@ def get_pset( elif ( (is_ifc2x3_material := (is_ifc2x3 and element.is_a("IfcMaterial"))) or element.is_a("IfcMaterialDefinition") - or element.is_a("IfcProfileDef") + or (is_profile := element.is_a("IfcProfileDef")) ): if is_ifc2x3_material: + # Support extended props as they do have a name. for definition in ifc_file.by_type("IfcExtendedMaterialProperties"): if definition.Material == element and definition.Name == name: pset = definition break + elif is_ifc2x3 and is_profile: + # Don't support them as they don't have a name. + pass else: + # IfcProfileDef or IfcMaterialDefinition, IFC4+. for definition in element.HasProperties or []: if definition.Name == name: pset = definition @@ -165,6 +171,8 @@ def get_psets( qsets = ifcopenshell.util.element.get_psets(element, qtos_only=True) psets_and_qtos = ifcopenshell.util.element.get_psets(element) """ + ifc_file = element.file + is_ifc2x3 = ifc_file.schema == "IFC2X3" psets = {} if element.is_a("IfcTypeObject"): for definition in element.HasPropertySets or []: @@ -174,8 +182,22 @@ def get_psets( continue psets.setdefault(definition.Name, {}).update(get_property_definition(definition, verbose=verbose)) # NOTE: doesn't account for IFC2X3 missing HasProperties - elif element.is_a("IfcMaterialDefinition") or element.is_a("IfcProfileDef"): - for definition in getattr(element, "HasProperties", None) or []: + elif ( + (is_ifc2x3_material := (is_ifc2x3 and element.is_a("IfcMaterial"))) + or element.is_a("IfcMaterialDefinition") + or element.is_a("IfcProfileDef") + ): + definitions: list[ifcopenshell.entity_instance] + if is_ifc2x3: + if is_ifc2x3_material: + # Only extended props have a name. + definitions = [d for d in ifc_file.by_type("IfcExtendedMaterialProperties") if d.Material == element] + else: + # Ignoring profiles as they don't have names. + definitions = [] + else: + definitions = getattr(element, "HasProperties", None) or [] + for definition in definitions: if qtos_only: continue psets.setdefault(definition.Name, {}).update(get_property_definition(definition, verbose=verbose)) @@ -247,6 +269,9 @@ def get_property_definition( elif ifc_class == "IfcMaterialProperties" or ifc_class == "IfcProfileProperties": # 2 IfcExtendedProperties.Properties props.update(get_properties(definition[2], verbose=verbose)) + elif ifc_class == "IfcExtendedMaterialProperties": + # 1 IfcExtendedMaterialProperties.ExtendedProperties + props.update(get_properties(definition[1], verbose=verbose)) else: # Entity introduced in IFC4 # definition.is_a('IfcPreDefinedPropertySet'): diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index 54e4db0c58..b691620824 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -41,6 +41,28 @@ import ifcopenshell.util.element as subject from ifcopenshell.util.shape_builder import ShapeBuilder +class TestIFC2X3MaterialProfilePsts(test.bootstrap.IFC2X3): + def test_get_profile_pset(self): + profile = ifcopenshell.api.profile.add_parameterized_profile(self.file, "IfcRectangleProfileDef") + pset = ifcopenshell.api.pset.add_pset(self.file, profile, "") + pset.Perimeter = 25.0 + # We don't support them, just making sure there are no errors. + assert subject.get_pset(profile, "Test") is None + assert subject.get_psets(profile) == {} + + def get_material_pset_extended_params(self): + material_with_extended_params = ifcopenshell.api.material.add_material(self.file) + pset = ifcopenshell.api.pset.add_pset(self.file, material_with_extended_params, "Test") + ifcopenshell.api.pset.edit_pset(self.file, pset, "Test", {"GassPressure": 25.0}) + pset_data = subject.get_pset(material_with_extended_params, "Test") + del pset_data["id"] + assert pset_data == {"GassPressure": 25.0} + psets_data = subject.get_psets(material_with_extended_params) + for value in psets_data.values(): + del value["id"] + assert psets_data == {"Test": {"GassPressure": 25.0}} + + class TestGetPsetIFC4(test.bootstrap.IFC4): def test_getting_the_psets_of_a_product_as_a_dictionary(self): element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")