From 1bbbc82b72f80cc30e9109efcd6b53de42267871 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 18 Jan 2022 17:44:50 +1100 Subject: [PATCH] Fix #1962. You can now easily append product assets using the API. --- .../ifcopenshell/api/project/append_asset.py | 75 ++++++-- .../test/api/project/test_append_asset.py | 164 ++++++++++++++++++ .../test/api/project/test_create_file.py | 16 ++ 3 files changed, 242 insertions(+), 13 deletions(-) create mode 100644 src/ifcopenshell-python/test/api/project/test_append_asset.py create mode 100644 src/ifcopenshell-python/test/api/project/test_create_file.py diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py index 9fd01fac4c..534ea8207a 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py @@ -14,6 +14,8 @@ class Usecase: self.whitelisted_inverse_attributes = {} if self.settings["element"].is_a("IfcTypeProduct"): return self.append_type_product() + elif self.settings["element"].is_a("IfcProduct"): + return self.append_product() elif self.settings["element"].is_a("IfcMaterial"): return self.append_material() elif self.settings["element"].is_a("IfcCostSchedule"): @@ -21,10 +23,9 @@ class Usecase: elif self.settings["element"].is_a("IfcProfileDef"): return self.append_profile_def() - def is_already_appended(self): + def get_existing_element(self): try: - self.file.by_guid(self.settings["element"].GlobalId) - return True + return self.file.by_guid(self.settings["element"].GlobalId) except: return False @@ -34,8 +35,9 @@ class Usecase: return self.file.add(self.settings["element"]) def append_cost_schedule(self): - if self.is_already_appended(): - return + element = self.get_existing_element() + if element: + return element self.whitelisted_inverse_attributes = {"IfcCostSchedule": ["Controls"], "IfcCostItem": ["IsNestedBy"]} return self.add_element(self.settings["element"]) @@ -46,8 +48,9 @@ class Usecase: return self.add_element(self.settings["element"]) def append_type_product(self): - if self.is_already_appended(): - return + element = self.get_existing_element() + if element: + return element self.whitelisted_inverse_attributes = { "IfcObjectDefinition": ["HasAssociations"], "IfcMaterialDefinition": ["HasExternalReferences", "HasProperties"], @@ -66,6 +69,46 @@ class Usecase: ifcopenshell.util.element.remove_deep(self.file, added_context) return element + def append_product(self): + element = self.get_existing_element() + if element: + return element + self.whitelisted_inverse_attributes = { + "IfcObjectDefinition": ["HasAssociations"], + "IfcObject": ["IsDefinedBy.IfcRelDefinesByProperties"], + "IfcElement": ["HasOpenings"], + "IfcMaterialDefinition": ["HasExternalReferences", "HasProperties"], + "IfcRepresentationItem": ["StyledByItem"], + } + element = self.add_element(self.settings["element"]) + self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext") + added_contexts = [e for e in self.file.traverse(element) if e.is_a("IfcGeometricRepresentationContext")] + for added_context in added_contexts: + equivalent_existing_context = self.get_equivalent_existing_context(added_context) + if not equivalent_existing_context: + equivalent_existing_context = self.create_equivalent_context(added_context) + for inverse in self.file.get_inverse(added_context): + ifcopenshell.util.element.replace_attribute(inverse, added_context, equivalent_existing_context) + for added_context in added_contexts: + ifcopenshell.util.element.remove_deep(self.file, added_context) + + element_type = ifcopenshell.util.element.get_type(self.settings["element"]) + if element_type: + ifcopenshell.api.owner.settings.factory_reset() + new_type = ifcopenshell.api.run( + "project.append_asset", self.file, library=self.settings["library"], element=element_type + ) + ifcopenshell.api.run( + "type.assign_type", + self.file, + should_run_listeners=False, + related_object=element, + relating_type=new_type, + ) + ifcopenshell.api.owner.settings.restore() + + return element + def add_element(self, element): if element.id() == 0 or element.id() in self.added_elements: return @@ -77,12 +120,18 @@ class Usecase: return new def add_inverse(self, element): - inverse_attributes = [] - [inverse_attributes.extend(v) for k, v in self.whitelisted_inverse_attributes.items() if element.is_a(k)] - inverse_attributes = set(inverse_attributes) - for attribute in inverse_attributes: - for inverse in getattr(element, attribute, []): - self.add_element(inverse) + for source_class, attributes in self.whitelisted_inverse_attributes.items(): + if not element.is_a(source_class): + continue + for attribute in attributes: + attribute_class = None + if "." in attribute: + attribute, attribute_class = attribute.split(".") + for inverse in getattr(element, attribute, []): + if attribute_class and inverse.is_a(attribute_class): + self.add_element(inverse) + elif not attribute_class: + self.add_element(inverse) def get_equivalent_existing_context(self, added_context): for context in self.existing_contexts: diff --git a/src/ifcopenshell-python/test/api/project/test_append_asset.py b/src/ifcopenshell-python/test/api/project/test_append_asset.py new file mode 100644 index 0000000000..3d8b0ef387 --- /dev/null +++ b/src/ifcopenshell-python/test/api/project/test_append_asset.py @@ -0,0 +1,164 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestAppendAsset(test.bootstrap.IFC4): + def test_do_not_append_twice(self): + library = ifcopenshell.api.run("project.create_file") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType") + material = ifcopenshell.api.run("material.add_material", library, name="Material") + schedule = ifcopenshell.api.run("cost.add_cost_schedule", library, name="Schedule") + profile = library.createIfcIShapeProfileDef() + + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=material) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=material) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=schedule) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=schedule) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=profile) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=profile) + assert len(self.file.by_type("IfcWallType")) == 1 + + def test_append_a_type_product(self): + library = ifcopenshell.api.run("project.create_file") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType") + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + assert len(self.file.by_type("IfcWallType")) == 1 + + def test_append_a_type_product_with_its_materials(self): + library = ifcopenshell.api.run("project.create_file") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType") + material = ifcopenshell.api.run("material.add_material", library, name="Material") + ifcopenshell.api.run("material.assign_material", library, product=element, material=material) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + assert self.file.by_type("IfcWallType")[0].HasAssociations[0].RelatingMaterial.Name == "Material" + + def test_append_a_type_product_with_its_styles(self): + library = ifcopenshell.api.run("project.create_file") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType") + item = library.createIfcBoundingBox() + library.createIfcStyledItem(Item=item) + mapped_rep = library.createIfcShapeRepresentation(Items=[item]) + element.RepresentationMaps = [library.createIfcRepresentationMap(MappedRepresentation=mapped_rep)] + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + assert self.file.by_type("IfcStyledItem")[0].Item == self.file.by_type("IfcBoundingBox")[0] + + def test_append_a_material(self): + library = ifcopenshell.api.run("project.create_file") + material = ifcopenshell.api.run("material.add_material", library, name="Material") + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=material) + assert len(self.file.by_type("IfcMaterial")) == 1 + + def test_append_a_cost_schedule(self): + library = ifcopenshell.api.run("project.create_file") + schedule = ifcopenshell.api.run("cost.add_cost_schedule", library, name="Schedule") + item = ifcopenshell.api.run("cost.add_cost_item", library, cost_schedule=schedule) + item2 = ifcopenshell.api.run("cost.add_cost_item", library, cost_item=item) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=schedule) + assert len(self.file.by_type("IfcCostSchedule")) == 1 + assert len(self.file.by_type("IfcCostItem")) == 2 + assert self.file.by_type("IfcCostSchedule")[0].Name == "Schedule" + appended_item = self.file.by_type("IfcCostSchedule")[0].Controls[0].RelatedObjects[0] + assert appended_item.is_a("IfcCostItem") + assert appended_item.IsNestedBy[0].RelatedObjects[0].is_a("IfcCostItem") + + def test_append_a_profile_def(self): + library = ifcopenshell.api.run("project.create_file") + profile = library.createIfcIShapeProfileDef() + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=profile) + assert len(self.file.by_type("IfcIShapeProfileDef")) == 1 + + def test_append_a_profile_def_with_all_properties(self): + library = ifcopenshell.api.run("project.create_file") + profile = library.createIfcIShapeProfileDef() + ifcopenshell.api.run("pset.add_pset", library, product=profile, name="Foo_Bar") + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=profile) + assert len(self.file.by_type("IfcIShapeProfileDef")) == 1 + assert self.file.by_type("IfcIShapeProfileDef")[0].HasProperties[0].Name == "Foo_Bar" + + def test_append_a_product(self): + library = ifcopenshell.api.run("project.create_file") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + assert len(self.file.by_type("IfcWall")) == 1 + + def test_append_a_product_with_all_properties(self): + library = ifcopenshell.api.run("project.create_file") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + ifcopenshell.api.run("pset.add_pset", library, product=element, name="Foo_Bar") + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + assert ifcopenshell.util.element.get_psets(self.file.by_type("IfcWall")[0])["Foo_Bar"] + + def test_append_a_product_with_its_type(self): + library = ifcopenshell.api.run("project.create_file") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", library, related_object=element, relating_type=element_type) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + assert ifcopenshell.util.element.get_type(self.file.by_type("IfcWall")[0]).is_a("IfcWallType") + + def test_append_only_specified_occurrences_of_a_typed_product(self): + library = ifcopenshell.api.run("project.create_file") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + element3 = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", library, related_object=element, relating_type=element_type) + ifcopenshell.api.run("type.assign_type", library, related_object=element2, relating_type=element_type) + ifcopenshell.api.run("type.assign_type", library, related_object=element3, relating_type=element_type) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element2) + assert len(ifcopenshell.util.element.get_types(self.file.by_type("IfcWallType")[0])) == 2 + assert len(self.file.by_type("IfcWall")) == 2 + + def test_append_a_product_with_materials(self): + library = ifcopenshell.api.run("project.create_file") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + material = ifcopenshell.api.run("material.add_material", library, name="Material") + ifcopenshell.api.run("material.assign_material", library, product=element, material=material) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + assert ifcopenshell.util.element.get_material(self.file.by_type("IfcWall")[0]).Name == "Material" + + def test_append_a_product_with_its_styles(self): + library = ifcopenshell.api.run("project.create_file") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + item = library.createIfcBoundingBox() + library.createIfcStyledItem(Item=item) + representation = library.createIfcShapeRepresentation(Items=[item]) + element.Representation = library.createIfcProductDefinitionShape(Representations=[representation]) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + assert self.file.by_type("IfcStyledItem")[0].Item == self.file.by_type("IfcBoundingBox")[0] + + def test_append_a_product_with_openings(self): + library = ifcopenshell.api.run("project.create_file") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + opening = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcOpeningElement") + ifcopenshell.api.run("void.add_opening", library, opening=opening, element=element) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + assert self.file.by_type("IfcWall")[0].HasOpenings[0].RelatedOpeningElement.is_a("IfcOpeningElement") + + +class TestAppendAssetIFC2X3(test.bootstrap.IFC2X3): + def test_append_a_product_with_its_type(self): + library = ifcopenshell.api.run("project.create_file", version="IFC2X3") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", library, related_object=element, relating_type=element_type) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + assert ifcopenshell.util.element.get_type(self.file.by_type("IfcWall")[0]).is_a("IfcWallType") + + def test_append_only_specified_occurrences_of_a_typed_product(self): + library = ifcopenshell.api.run("project.create_file", version="IFC2X3") + element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + element3 = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", library, related_object=element, relating_type=element_type) + ifcopenshell.api.run("type.assign_type", library, related_object=element2, relating_type=element_type) + ifcopenshell.api.run("type.assign_type", library, related_object=element3, relating_type=element_type) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) + ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element2) + assert len(ifcopenshell.util.element.get_types(self.file.by_type("IfcWallType")[0])) == 2 + assert len(self.file.by_type("IfcWall")) == 2 + diff --git a/src/ifcopenshell-python/test/api/project/test_create_file.py b/src/ifcopenshell-python/test/api/project/test_create_file.py new file mode 100644 index 0000000000..c16bc7ee79 --- /dev/null +++ b/src/ifcopenshell-python/test/api/project/test_create_file.py @@ -0,0 +1,16 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestCreateFile(test.bootstrap.IFC4): + def test_run(self): + ifc = ifcopenshell.api.run("project.create_file") + assert ifc.schema == "IFC4" + ifc = ifcopenshell.api.run("project.create_file", version="IFC2X3") + assert ifc.schema == "IFC2X3" + assert ifc.wrapped_data.header.file_name.name == "/dev/null" + assert ifc.wrapped_data.header.file_name.time_stamp + assert "IfcOpenShell" in ifc.wrapped_data.header.file_name.preprocessor_version + assert "IfcOpenShell" in ifc.wrapped_data.header.file_name.originating_system + assert ifc.wrapped_data.header.file_name.authorization == "Nobody" + assert ifc.wrapped_data.header.file_description.description == ("ViewDefinition[DesignTransferView]",)