From d85f9af7909d1cf2e8947e30471d2c55d1458fd7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 25 May 2024 14:42:45 +1000 Subject: [PATCH] The API can now copy material sets too --- .../api/material/copy_material.py | 87 ++++++++++++------- .../test/api/material/test_copy_material.py | 71 ++++++++++++++- 2 files changed, 125 insertions(+), 33 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/copy_material.py b/src/ifcopenshell-python/ifcopenshell/api/material/copy_material.py index 5655fa97da..6e51a5f14b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/copy_material.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/copy_material.py @@ -21,12 +21,15 @@ import ifcopenshell.util.element def copy_material(file: ifcopenshell.file, material: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: - """Copies a material + """Copies a material or material set All material psets and styles are copied. The copied material is not associated to any elements. - :param material: The IfcMaterial to copy + If a material set is copied, the set items are also copied. However the + underlying materials (and profiles) used within the set items are reused. + + :param material: The IfcMaterialDefinition to copy :type material: ifcopenshell.entity_instance :return: The new copy of the material :rtype: ifcopenshell.entity_instance @@ -40,34 +43,54 @@ def copy_material(file: ifcopenshell.file, material: ifcopenshell.entity_instanc # Let's duplicate the concrete material concrete_copy = ifcopenshell.api.run("material.copy_material", model, material=concrete) """ - settings = {"material": material} - - if settings["material"].is_a("IfcMaterial"): - new = ifcopenshell.util.element.copy(file, settings["material"]) - for inverse in file.get_inverse(settings["material"]): - if inverse.is_a("IfcMaterialProperties"): - # Properties must not be shared between objects for convenience of authoring - inverse = ifcopenshell.util.element.copy(file, inverse) - inverse.Material = new - - props_attribute = "Properties" - if file.schema == "IFC2X3": - if not inverse.is_a("IfcExtendedMaterialProperties"): - continue - props_attribute = "ExtendedProperties" - - props = getattr(inverse, props_attribute) - if not props: - continue - - copied_props = [] - for pset in props: - copied_props.append(ifcopenshell.util.element.copy_deep(file, pset)) - setattr(inverse, props_attribute, copied_props) - - elif inverse.is_a("IfcMaterialDefinitionRepresentation"): - inverse = ifcopenshell.util.element.copy_deep( - file, inverse, exclude=["IfcRepresentationContext", "IfcMaterial"] - ) - inverse.RepresentedMaterial = new + if material.is_a("IfcMaterial"): + return _copy_material_with_inverses(file, material) + elif material.is_a("IfcMaterialConstituentSet"): + new = _copy_material_with_inverses(file, material) + new.MaterialConstituents = [copy_material(file, i) for i in material.MaterialConstituents] return new + elif material.is_a("IfcMaterialConstituent"): + return _copy_material_with_inverses(file, material) + elif material.is_a("IfcMaterialLayerSet"): + new = _copy_material_with_inverses(file, material) + new.MaterialLayers = [copy_material(file, i) for i in material.MaterialLayers] + return new + elif material.is_a("IfcMaterialLayer"): + return _copy_material_with_inverses(file, material) + elif material.is_a("IfcMaterialProfileSet"): + new = _copy_material_with_inverses(file, material) + new.MaterialProfiles = [copy_material(file, i) for i in material.MaterialProfiles] + return new + elif material.is_a("IfcMaterialProfile"): + return _copy_material_with_inverses(file, material) + + +def _copy_material_with_inverses(file, material): + new = ifcopenshell.util.element.copy(file, material) + for inverse in file.get_inverse(material): + if inverse.is_a("IfcMaterialProperties"): + # Properties must not be shared between objects for convenience of authoring + inverse = ifcopenshell.util.element.copy(file, inverse) + inverse.Material = new + + props_attribute = "Properties" + if file.schema == "IFC2X3": + if not inverse.is_a("IfcExtendedMaterialProperties"): + continue + props_attribute = "ExtendedProperties" + + props = getattr(inverse, props_attribute) + if not props: + continue + + copied_props = [] + for pset in props: + copied_props.append(ifcopenshell.util.element.copy_deep(file, pset)) + setattr(inverse, props_attribute, copied_props) + + elif inverse.is_a("IfcMaterialDefinitionRepresentation"): + inverse = ifcopenshell.util.element.copy_deep( + file, inverse, exclude=["IfcRepresentationContext", "IfcMaterial"] + ) + inverse.RepresentedMaterial = new + return new diff --git a/src/ifcopenshell-python/test/api/material/test_copy_material.py b/src/ifcopenshell-python/test/api/material/test_copy_material.py index 623814c266..dd53621dcd 100644 --- a/src/ifcopenshell-python/test/api/material/test_copy_material.py +++ b/src/ifcopenshell-python/test/api/material/test_copy_material.py @@ -72,5 +72,74 @@ class TestCopyMaterial(test.bootstrap.IFC4): assert new.HasRepresentation[0].Representations[0] != material.HasRepresentation[0].Representations[0] assert new.HasRepresentation[0].Representations[0].ContextOfItems == context + def test_copy_a_material_constituent_set(self): + material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") + material_set = ifcopenshell.api.run( + "material.add_material_set", self.file, name="Foo", set_type="IfcMaterialConstituentSet" + ) + item = ifcopenshell.api.run( + "material.add_constituent", self.file, constituent_set=material_set, material=material + ) + + new = ifcopenshell.api.run("material.copy_material", self.file, material=material_set) + assert new != material_set + assert new.Name == "Foo" + assert new.MaterialConstituents[0] != item + assert new.MaterialConstituents[0].Material == material + assert len(self.file.by_type("IfcMaterialConstituentSet")) == 2 + assert len(self.file.by_type("IfcMaterialConstituent")) == 2 + assert len(self.file.by_type("IfcMaterial")) == 1 + + def test_copy_a_material_layer_set(self): + material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") + material_set = ifcopenshell.api.run( + "material.add_material_set", self.file, name="Foo", set_type="IfcMaterialLayerSet" + ) + item = ifcopenshell.api.run("material.add_layer", self.file, layer_set=material_set, material=material) + + new = ifcopenshell.api.run("material.copy_material", self.file, material=material_set) + assert new != material_set + assert new.LayerSetName == "Foo" + assert new.MaterialLayers[0] != item + assert new.MaterialLayers[0].Material == material + assert len(self.file.by_type("IfcMaterialLayerSet")) == 2 + assert len(self.file.by_type("IfcMaterialLayer")) == 2 + assert len(self.file.by_type("IfcMaterial")) == 1 + + def test_copy_a_material_profile_set(self): + material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") + profile = self.file.create_entity( + "IfcIShapeProfileDef", + ProfileName="HEA100", + ProfileType="AREA", + OverallWidth=100, + OverallDepth=96, + WebThickness=5, + FlangeThickness=8, + FilletRadius=12, + ) + material_set = ifcopenshell.api.run( + "material.add_material_set", self.file, name="Foo", set_type="IfcMaterialProfileSet" + ) + item = ifcopenshell.api.run( + "material.add_profile", self.file, profile_set=material_set, material=material, profile=profile + ) + + new = ifcopenshell.api.run("material.copy_material", self.file, material=material_set) + assert new != material_set + assert new.Name == "Foo" + assert new.MaterialProfiles[0] != item + assert new.MaterialProfiles[0].Material == material + assert new.MaterialProfiles[0].Profile == profile + assert len(self.file.by_type("IfcMaterialProfileSet")) == 2 + assert len(self.file.by_type("IfcMaterialProfile")) == 2 + assert len(self.file.by_type("IfcMaterial")) == 1 + assert len(self.file.by_type("IfcProfileDef")) == 1 + + class TestCopyMaterialIFC2X3(test.bootstrap.IFC2X3, TestCopyMaterial): - pass + def test_copy_a_material_constituent_set(self): + return + + def test_copy_a_material_profile_set(self): + return