diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index b0b341aac8..239c4ceee2 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -190,13 +190,17 @@ def get_elements_by_material(ifc_file, material): """ Retrieves the elements related to a material. + This includes elements using the material as part of a material set or set + usage. + :param ifc_file: The IFC file :param material: The IFC Material entity - :return: The elements related to the material + :return: A list of elements using the to the material Example:: - material = file.by_type("IfcMaterial")[0] - elements = ifcopenshell.util.element.get_elements_by_material(file, material) + + material = file.by_type("IfcMaterial")[0] + elements = ifcopenshell.util.element.get_elements_by_material(file, material) """ results = set() for inverse in ifc_file.get_inverse(material): @@ -229,9 +233,9 @@ def get_elements_by_style(ifc_file, style): :return: The elements related to the style Example:: - style = file.by_type("IfcSurfaceStyle")[0] - elements = ifcopenshell.util.element.get_elements_by_style(file, style) + style = file.by_type("IfcSurfaceStyle")[0] + elements = ifcopenshell.util.element.get_elements_by_style(file, style) """ results = set() inverses = list(ifc_file.get_inverse(style)) @@ -482,6 +486,15 @@ def remove_deep2(ifc_file, element, also_consider=[], do_not_delete=[]): def copy(ifc_file, element): + """ + Copy a single element. Any referenced elements are not copied. + + GlobalIds are regenerated. + + :param ifc_file: The IFC file object + :param element: The IFC element to copy + :return: The newly copied element + """ new = ifc_file.create_entity(element.is_a()) for i, attribute in enumerate(element): if attribute is None: @@ -493,16 +506,33 @@ def copy(ifc_file, element): return new -def copy_deep(ifc_file, element): +def copy_deep(ifc_file, element, exclude=None): + """ + Recursively copy an element and all of its directly related subelements. + + GlobalIds are regenerated. + + :param ifc_file: The IFC file object + :param element: The IFC element to copy + :param exclude: An optional list of strings of IFC class names to not copy. + If any of the subelement is this class, it will not be copied and the + original instance will be referenced. + :return: The newly copied element + """ new = ifc_file.create_entity(element.is_a()) for i, attribute in enumerate(element): if attribute is None: continue if isinstance(attribute, ifcopenshell.entity_instance): - attribute = copy_deep(ifc_file, attribute) + if not exclude or (exclude and not any([attribute.is_a(e) for e in exclude])): + attribute = copy_deep(ifc_file, attribute, exclude=exclude) elif isinstance(attribute, tuple) and attribute and isinstance(attribute[0], ifcopenshell.entity_instance): - attribute = list(attribute) - for j, item in enumerate(attribute): - attribute[j] = copy_deep(ifc_file, item) - new[i] = attribute + if not exclude or (exclude and not any([attribute[0].is_a(e) for e in exclude])): + attribute = list(attribute) + for j, item in enumerate(attribute): + attribute[j] = copy_deep(ifc_file, item, exclude=exclude) + if new.attribute_name(i) == "GlobalId": + new[i] = ifcopenshell.guid.new() + else: + new[i] = attribute return new diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index 33a3e3cc39..a63275713e 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -668,6 +668,7 @@ class TestCopyDeepIFC4(test.bootstrap.IFC4): element = self.file.createIfcWall(GlobalId="id", Name="name", OwnerHistory=owner) element2 = subject.copy_deep(self.file, element) assert element.OwnerHistory != element2.OwnerHistory + assert element.GlobalId != element2.GlobalId assert element.OwnerHistory.State == element2.OwnerHistory.State def test_copying_an_element_recursively_even_if_references_are_aggregated(self): @@ -677,3 +678,17 @@ class TestCopyDeepIFC4(test.bootstrap.IFC4): rel2 = subject.copy_deep(self.file, rel) assert rel.RelatedObjects != rel2.RelatedObjects assert rel.RelatedObjects[0].Name == rel2.RelatedObjects[0].Name + + def test_copying_an_element_recursively_with_an_exclude_filter(self): + owner = self.file.createIfcOwnerHistory() + owner.State = "READWRITE" + element = self.file.createIfcWall(GlobalId="id", Name="name", OwnerHistory=owner) + element2 = subject.copy_deep(self.file, element, exclude=["IfcOwnerHistory"]) + assert element.OwnerHistory == element2.OwnerHistory + + def test_copying_an_element_recursively_with_aggregates_with_an_exclude_filter(self): + element = self.file.createIfcWall(Name="name") + rel = self.file.createIfcRelAggregates() + rel.RelatedObjects = [element] + rel2 = subject.copy_deep(self.file, rel, exclude=["IfcWall"]) + assert rel.RelatedObjects == rel2.RelatedObjects