From de690a385c2541be23993a5024bb0e9ce1069a61 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 3 May 2024 14:37:59 +0500 Subject: [PATCH] project.assign_declaration - support batching #4474 --- .../blenderbim/bim/module/project/operator.py | 2 +- src/blenderbim/scripts/generate_au_library.py | 10 +-- .../scripts/generate_demo_library.py | 8 +- .../scripts/generate_entourage_library.py | 4 +- .../scripts/generate_furniture_library.py | 12 +-- .../scripts/generate_landscape_library.py | 6 +- .../scripts/generate_site_library.py | 4 +- .../generate_steel_profiles_library.py | 4 +- .../scripts/shape_builder_examples.py | 8 +- .../ifcopenshell/api/__init__.py | 3 + .../ifcopenshell/api/project/append_asset.py | 4 +- .../api/project/assign_declaration.py | 80 ++++++++++++------- .../api/project/unassign_declaration.py | 2 +- .../ifcopenshell/api/resource/add_resource.py | 2 +- .../api/sequence/add_work_calendar.py | 2 +- .../api/sequence/add_work_plan.py | 2 +- .../api/sequence/add_work_schedule.py | 2 +- .../api/project/test_assign_declaration.py | 70 ++++++++++++++++ src/ifcopenshell-python/test/api/test_api.py | 27 +++++-- 19 files changed, 182 insertions(+), 70 deletions(-) create mode 100644 src/ifcopenshell-python/test/api/project/test_assign_declaration.py diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 5bc8fab544..68c562c1c0 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -302,7 +302,7 @@ class AssignLibraryDeclaration(bpy.types.Operator): ifcopenshell.api.run( "project.assign_declaration", self.file, - definition=self.file.by_id(self.definition), + definitions=[self.file.by_id(self.definition)], relating_context=self.file.by_type("IfcProjectLibrary")[0], ) element_name = self.props.active_library_element diff --git a/src/blenderbim/scripts/generate_au_library.py b/src/blenderbim/scripts/generate_au_library.py index e75cf739b0..3e20808f75 100644 --- a/src/blenderbim/scripts/generate_au_library.py +++ b/src/blenderbim/scripts/generate_au_library.py @@ -40,7 +40,7 @@ class LibraryGenerator: "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name="Australian Library" ) ifcopenshell.api.run( - "project.assign_declaration", self.file, definition=self.library, relating_context=self.project + "project.assign_declaration", self.file, definitions=[self.library], relating_context=self.project ) unit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", prefix="MILLI") ifcopenshell.api.run("unit.assign_unit", self.file, units=[unit]) @@ -196,7 +196,7 @@ class LibraryGenerator: ) layer.Name = layer_data[0] layer.LayerThickness = layer_data[2] - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) return element def create_layer_type(self, ifc_class, name, thickness): @@ -205,7 +205,7 @@ class LibraryGenerator: layer_set = rel.RelatingMaterial layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=layer_set, material=self.materials["TBD"]["ifc"]) layer.LayerThickness = thickness - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) return element def create_profile_type(self, ifc_class, name, profile): @@ -216,7 +216,7 @@ class LibraryGenerator: "material.add_profile", self.file, profile_set=profile_set, material=self.materials["TBD"]["ifc"] ) ifcopenshell.api.run("material.assign_profile", self.file, material_profile=material_profile, profile=profile) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) def create_type(self, ifc_class, name, representations): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name) @@ -248,7 +248,7 @@ class LibraryGenerator: ifcopenshell.api.run( "geometry.assign_representation", self.file, product=element, representation=representation ) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) LibraryGenerator().generate() diff --git a/src/blenderbim/scripts/generate_demo_library.py b/src/blenderbim/scripts/generate_demo_library.py index 14f97bc52d..15fda40220 100644 --- a/src/blenderbim/scripts/generate_demo_library.py +++ b/src/blenderbim/scripts/generate_demo_library.py @@ -35,7 +35,7 @@ class LibraryGenerator: "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name="BlenderBIM Demo Library" ) ifcopenshell.api.run( - "project.assign_declaration", self.file, definition=self.library, relating_context=self.project + "project.assign_declaration", self.file, definitions=[self.library], relating_context=self.project ) ifcopenshell.api.run("unit.assign_unit", self.file, length={"is_metric": True, "raw": "METERS"}) model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model") @@ -209,7 +209,7 @@ class LibraryGenerator: layer_set = rel.RelatingMaterial layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=layer_set, material=self.material) layer.LayerThickness = thickness - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) return element def create_profile_type(self, ifc_class, name, profile): @@ -220,7 +220,7 @@ class LibraryGenerator: "material.add_profile", self.file, profile_set=profile_set, material=self.material ) ifcopenshell.api.run("material.assign_profile", self.file, material_profile=material_profile, profile=profile) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) def create_type(self, ifc_class, name, representations): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name) @@ -252,7 +252,7 @@ class LibraryGenerator: ifcopenshell.api.run( "geometry.assign_representation", self.file, product=element, representation=representation ) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) LibraryGenerator().generate() diff --git a/src/blenderbim/scripts/generate_entourage_library.py b/src/blenderbim/scripts/generate_entourage_library.py index a84c73e4ef..07ddcf7e01 100644 --- a/src/blenderbim/scripts/generate_entourage_library.py +++ b/src/blenderbim/scripts/generate_entourage_library.py @@ -42,7 +42,7 @@ class LibraryGenerator: "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name=library_name ) ifcopenshell.api.run( - "project.assign_declaration", self.file, definition=self.library, relating_context=self.project + "project.assign_declaration", self.file, definitions=[self.library], relating_context=self.project ) unit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", prefix="MILLI") ifcopenshell.api.run("unit.assign_unit", self.file, units=[unit]) @@ -131,7 +131,7 @@ class LibraryGenerator: ifcopenshell.api.run( "geometry.assign_representation", self.file, product=element, representation=representation ) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) if __name__ == "__main__": diff --git a/src/blenderbim/scripts/generate_furniture_library.py b/src/blenderbim/scripts/generate_furniture_library.py index 6d38170ed0..9a4c12492f 100644 --- a/src/blenderbim/scripts/generate_furniture_library.py +++ b/src/blenderbim/scripts/generate_furniture_library.py @@ -37,7 +37,7 @@ class LibraryGenerator: "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name=library_name ) ifcopenshell.api.run( - "project.assign_declaration", self.file, definition=self.library, relating_context=self.project + "project.assign_declaration", self.file, definitions=[self.library], relating_context=self.project ) unit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", prefix="MILLI") ifcopenshell.api.run("unit.assign_unit", self.file, units=[unit]) @@ -1797,7 +1797,7 @@ class LibraryGenerator: ifcopenshell.api.run( "geometry.assign_representation", self.file, product=element, representation=representation_2d ) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) return element def create_layer_set_type(self, name, data): @@ -1811,7 +1811,7 @@ class LibraryGenerator: ) layer.Name = layer_data[0] layer.LayerThickness = layer_data[2] - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) return element def create_layer_type(self, ifc_class, name, thickness): @@ -1822,7 +1822,7 @@ class LibraryGenerator: "material.add_layer", self.file, layer_set=layer_set, material=self.materials["TBD"]["ifc"] ) layer.LayerThickness = thickness - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) return element def create_profile_type(self, ifc_class, name, profile): @@ -1837,7 +1837,7 @@ class LibraryGenerator: # material=self.materials["TBD"]["ifc"] ) ifcopenshell.api.run("material.assign_profile", self.file, material_profile=material_profile, profile=profile) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) def create_type(self, ifc_class, name, representations): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name) @@ -1869,7 +1869,7 @@ class LibraryGenerator: ifcopenshell.api.run( "geometry.assign_representation", self.file, product=element, representation=representation ) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) if __name__ == "__main__": diff --git a/src/blenderbim/scripts/generate_landscape_library.py b/src/blenderbim/scripts/generate_landscape_library.py index be7bff7b69..06cec8c6df 100644 --- a/src/blenderbim/scripts/generate_landscape_library.py +++ b/src/blenderbim/scripts/generate_landscape_library.py @@ -319,7 +319,7 @@ class LibraryGenerator: "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name=library_name ) ifcopenshell.api.run( - "project.assign_declaration", self.file, definition=self.library, relating_context=self.project + "project.assign_declaration", self.file, definitions=[self.library], relating_context=self.project ) unit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", prefix="MILLI") ifcopenshell.api.run("unit.assign_unit", self.file, units=[unit]) @@ -447,7 +447,7 @@ class LibraryGenerator: ifcopenshell.api.run( "geometry.assign_representation", self.file, product=element, representation=representation_2d ) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) return element def create_type(self, ifc_class, name, representations): @@ -480,7 +480,7 @@ class LibraryGenerator: ifcopenshell.api.run( "geometry.assign_representation", self.file, product=element, representation=representation ) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) diff --git a/src/blenderbim/scripts/generate_site_library.py b/src/blenderbim/scripts/generate_site_library.py index ba07c77dc8..73a9ad96a7 100644 --- a/src/blenderbim/scripts/generate_site_library.py +++ b/src/blenderbim/scripts/generate_site_library.py @@ -35,7 +35,7 @@ class LibraryGenerator: "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name="BlenderBIM Demo Library" ) ifcopenshell.api.run( - "project.assign_declaration", self.file, definition=self.library, relating_context=self.library + "project.assign_declaration", self.file, definitions=[self.library], relating_context=self.library ) ifcopenshell.api.run("unit.assign_unit", self.file, length={"is_metric": True, "raw": "METERS"}) model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model") @@ -98,7 +98,7 @@ class LibraryGenerator: ifcopenshell.api.run( "geometry.assign_representation", self.file, product=element, representation=representation ) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) LibraryGenerator().generate() diff --git a/src/blenderbim/scripts/generate_steel_profiles_library.py b/src/blenderbim/scripts/generate_steel_profiles_library.py index 685435660f..7eeabbd5ce 100644 --- a/src/blenderbim/scripts/generate_steel_profiles_library.py +++ b/src/blenderbim/scripts/generate_steel_profiles_library.py @@ -43,7 +43,7 @@ class LibraryGenerator: "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name=f"{parse_profiles_type} Steel Profiles Library" ) ifcopenshell.api.run( - "project.assign_declaration", self.file, definition=self.library, relating_context=self.project + "project.assign_declaration", self.file, definitions=[self.library], relating_context=self.project ) dim_exponents = self.file.createIfcDimensionalExponents(0, 0, 0, 0, 0, 0, 0) length_unit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", prefix="MILLI") @@ -182,7 +182,7 @@ class LibraryGenerator: # material=self.materials["TBD"]["ifc"] ) ifcopenshell.api.run("material.assign_profile", self.file, material_profile=material_profile, profile=profile) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) + ifcopenshell.api.run("project.assign_declaration", self.file, definitions=[element], relating_context=self.library) def create_double_l_profile(self, profile, resulting_profile_name=None, profiles_gap=0, mode = "LLBB"): def create_derived_profile(profile, mirrored=False): diff --git a/src/blenderbim/scripts/shape_builder_examples.py b/src/blenderbim/scripts/shape_builder_examples.py index f8ba737e74..6561f0badc 100644 --- a/src/blenderbim/scripts/shape_builder_examples.py +++ b/src/blenderbim/scripts/shape_builder_examples.py @@ -91,7 +91,7 @@ def mirror_placement_test(): library = ifcopenshell.api.run( "root.create_entity", ifc_file, ifc_class="IfcProjectLibrary", name=f"Non-structural assets library" ) - ifcopenshell.api.run("project.assign_declaration", ifc_file, definition=library, relating_context=project) + ifcopenshell.api.run("project.assign_declaration", ifc_file, definitions=[library], relating_context=project) unit = ifcopenshell.api.run("unit.add_si_unit", ifc_file, unit_type="LENGTHUNIT", prefix="MILLI") ifcopenshell.api.run("unit.assign_unit", ifc_file, units=[unit]) model = ifcopenshell.api.run("context.add_context", ifc_file, context_type="Model") @@ -152,7 +152,7 @@ def mirror_placement_test(): element = ifcopenshell.api.run("root.create_entity", ifc_file, ifc_class="IfcFurnitureType", name="test") ifcopenshell.api.run("geometry.assign_representation", ifc_file, product=element, representation=representation_3d) - ifcopenshell.api.run("project.assign_declaration", ifc_file, definition=element, relating_context=library) + ifcopenshell.api.run("project.assign_declaration", ifc_file, definitions=[element], relating_context=library) ifc_file.write("tmp.ifc") @@ -165,7 +165,7 @@ def curve_between_two_points_test(): library = ifcopenshell.api.run( "root.create_entity", ifc_file, ifc_class="IfcProjectLibrary", name=f"Non-structural assets library" ) - ifcopenshell.api.run("project.assign_declaration", ifc_file, definition=library, relating_context=project) + ifcopenshell.api.run("project.assign_declaration", ifc_file, definitions=[library], relating_context=project) unit = ifcopenshell.api.run("unit.add_si_unit", ifc_file, unit_type="LENGTHUNIT", prefix="MILLI") ifcopenshell.api.run("unit.assign_unit", ifc_file, units=[unit]) model = ifcopenshell.api.run("context.add_context", ifc_file, context_type="Model") @@ -217,7 +217,7 @@ def curve_between_two_points_test(): print(representation_2d) element = ifcopenshell.api.run("root.create_entity", ifc_file, ifc_class="IfcFurnitureType", name="test") ifcopenshell.api.run("geometry.assign_representation", ifc_file, product=element, representation=representation_2d) - ifcopenshell.api.run("project.assign_declaration", ifc_file, definition=element, relating_context=library) + ifcopenshell.api.run("project.assign_declaration", ifc_file, definitions=[element], relating_context=library) ifc_file.write("tmp.ifc") diff --git a/src/ifcopenshell-python/ifcopenshell/api/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/__init__.py index f5a56279c0..1d21eaf40f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/__init__.py @@ -117,6 +117,9 @@ ARGUMENTS_DEPRECATION = { "constraint.unassign_constraint": partial( batching_argument_deprecation, prev_argument="product", new_argument="products" ), + "project.assign_declaration": partial( + batching_argument_deprecation, prev_argument="definition", new_argument="definitions" + ), } diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py index 4bef25ee75..b3640ca53e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py @@ -61,7 +61,7 @@ class Usecase: root = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProject", name="Demo Library") context = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProjectLibrary", name="Demo Library") - ifcopenshell.api.run("project.assign_declaration", library, definition=context, relating_context=root) + ifcopenshell.api.run("project.assign_declaration", library, definitions=[context], relating_context=root) # Assign units for our example library unit = ifcopenshell.api.run("unit.add_si_unit", library, @@ -80,7 +80,7 @@ class Usecase: # Mark our wall type as a reusable asset in our library. ifcopenshell.api.run("project.assign_declaration", library, - definition=wall_type, relating_context=context) + definitions=[wall_type], relating_context=context) # Let's imagine we're starting a new project model = ifcopenshell.api.run("project.create_file") diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/assign_declaration.py b/src/ifcopenshell-python/ifcopenshell/api/project/assign_declaration.py index c11e84be9a..be4d076de0 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/assign_declaration.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/assign_declaration.py @@ -18,11 +18,18 @@ import ifcopenshell import ifcopenshell.api +import ifcopenshell.util.element +from typing import Union class Usecase: - def __init__(self, file, definition=None, relating_context=None): - """Declares an element to the project + def __init__( + self, + file: ifcopenshell.entity_instance, + definitions: list[ifcopenshell.entity_instance], + relating_context: ifcopenshell.entity_instance, + ): + """Declares the list of elements to the project All data in a model must be directly or indirectly related to the project. Most data is indirectly related, existing instead within the @@ -35,13 +42,14 @@ class Usecase: project libraries for future use (such as an assets library). Assigning a declaration lets you say that an object belongs to a library. - :param definition: The object you want to declare. Typically an asset. - :type definition: ifcopenshell.entity_instance.entity_instance + :param definitions: The list of objects you want to declare. Typically a list of assets. + :type definitions: list[ifcopenshell.entity_instance.entity_instance] :param relating_context: The IfcProject, or more commonly the IfcProjectLibrary that you want the object to be part of. :type relating_context: ifcopenshell.entity_instance.entity_instance - :return: The new IfcRelDeclares relationship - :rtype: ifcopenshell.entity_instance.entity_instance + :return: The new IfcRelDeclares relationship or None if all definitions + were already declared / do not support declaration. + :rtype: Union[ifcopenshell.entity_instance.entity_instance, None] Example: @@ -54,7 +62,7 @@ class Usecase: ifc_class="IfcProjectLibrary", name="Demo Library") # It's necessary to say our library is part of our project. - ifcopenshell.api.run("project.assign_declaration", library, definition=context, relating_context=root) + ifcopenshell.api.run("project.assign_declaration", library, definitions=[context], relating_context=root) # Assign units for our example library unit = ifcopenshell.api.run("unit.add_si_unit", library, @@ -73,45 +81,61 @@ class Usecase: # Mark our wall type as a reusable asset in our library. ifcopenshell.api.run("project.assign_declaration", library, - definition=wall_type, relating_context=context) + definitions=[wall_type], relating_context=context) # All done, just for fun let's save our asset library to disk for later use. library.write("/path/to/my-library.ifc") """ self.file = file self.settings = { - "definition": definition, + "definitions": definitions, "relating_context": relating_context, } - def execute(self): - declares = None - if self.settings["relating_context"].Declares: - declares = self.settings["relating_context"].Declares[0] + def execute(self) -> Union[ifcopenshell.entity_instance, None]: + relating_context = self.settings["relating_context"] + all_declares = relating_context.Declares + definitions = set(self.settings["definitions"]) - if not hasattr(self.settings["definition"], "HasContext"): - return + previous_declares_rels: set[ifcopenshell.entity_instance] = set() + objects_without_contexts: list[ifcopenshell.entity_instance] = [] + objects_with_contexts: list[ifcopenshell.entity_instance] = [] - has_context = None - if self.settings["definition"].HasContext: - has_context = self.settings["definition"].HasContext[0] + # check if there is anything to change + for definition in definitions: + has_context = getattr(definition, "HasContext", None) + if has_context is None: + continue - if has_context and has_context == declares: - return + object_rel = next(iter(has_context), None) + if object_rel is None: + objects_without_contexts.append(definition) + continue - if has_context: - related_definitions = list(has_context.RelatedDefinitions) - related_definitions.remove(self.settings["definition"]) + # either rel doesn't exist or product is part of different rel + if object_rel not in all_declares: + previous_declares_rels.add(object_rel) + objects_with_contexts.append(definition) + + objects_to_change = objects_without_contexts + objects_with_contexts + # nothing to change + if not objects_to_change: + return None + + for has_context in previous_declares_rels: + related_definitions = set(has_context.RelatedDefinitions) - objects_with_contexts if related_definitions: has_context.RelatedDefinitions = related_definitions ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": has_context}) else: + history = has_context.OwnerHistory self.file.remove(has_context) + if history: + ifcopenshell.util.element.remove_deep2(self.file, history) + declares = next(iter(all_declares), None) if declares: - related_definitions = set(declares.RelatedDefinitions) - related_definitions.add(self.settings["definition"]) - declares.RelatedDefinitions = list(related_definitions) + declares.RelatedDefinitions = list(set(declares.RelatedDefinitions) | set(objects_to_change)) ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": declares}) else: declares = self.file.create_entity( @@ -119,8 +143,8 @@ class Usecase: **{ "GlobalId": ifcopenshell.guid.new(), "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), - "RelatedDefinitions": [self.settings["definition"]], - "RelatingContext": self.settings["relating_context"], + "RelatedDefinitions": list(objects_to_change), + "RelatingContext": relating_context, } ) return declares diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/unassign_declaration.py b/src/ifcopenshell-python/ifcopenshell/api/project/unassign_declaration.py index 25f3bdf64c..7e93f558fc 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/unassign_declaration.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/unassign_declaration.py @@ -46,7 +46,7 @@ class Usecase: ifc_class="IfcProjectLibrary", name="Demo Library") # It's necessary to say our library is part of our project. - ifcopenshell.api.run("project.assign_declaration", library, definition=context, relating_context=root) + ifcopenshell.api.run("project.assign_declaration", library, definitions=[context], relating_context=root) # Remove the library from our project ifcopenshell.api.run("project.unassign_declaration", library, definition=context, relating_context=root) diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource.py b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource.py index 9580389ab6..f2c3bf99ee 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource.py @@ -105,7 +105,7 @@ class Usecase: ifcopenshell.api.run( "project.assign_declaration", self.file, - definition=resource, + definitions=[resource], relating_context=context, ) return resource diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar.py index 8823c3f215..9df45247c5 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar.py @@ -92,7 +92,7 @@ class Usecase: ifcopenshell.api.run( "project.assign_declaration", self.file, - definition=work_calendar, + definitions=[work_calendar], relating_context=context, ) return work_calendar diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py index 76d2133494..4e907a0ad5 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py @@ -84,7 +84,7 @@ class Usecase: ifcopenshell.api.run( "project.assign_declaration", self.file, - definition=work_plan, + definitions=[work_plan], relating_context=context, ) return work_plan diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_schedule.py index 3da4d3b10e..f50745471f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_schedule.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_schedule.py @@ -118,7 +118,7 @@ class Usecase: ifcopenshell.api.run( "project.assign_declaration", self.file, - definition=work_schedule, + definitions=[work_schedule], relating_context=context, ) return work_schedule diff --git a/src/ifcopenshell-python/test/api/project/test_assign_declaration.py b/src/ifcopenshell-python/test/api/project/test_assign_declaration.py new file mode 100644 index 0000000000..ea9f7c1fe1 --- /dev/null +++ b/src/ifcopenshell-python/test/api/project/test_assign_declaration.py @@ -0,0 +1,70 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import test.bootstrap +import ifcopenshell.api + + +# NOTE: supported only in IFC4+ +class TestAssignDeclaration(test.bootstrap.IFC4): + def get_declared_definitions(self, project: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]: + definitions = set() + for declares in project.Declares: + definitions.update(declares.RelatedDefinitions) + return definitions + + def test_assign_a_declaration(self): + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element_type2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + library = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProjectLibrary") + ifcopenshell.api.run( + "project.assign_declaration", + self.file, + definitions=[element_type, element_type2], + relating_context=library, + ) + assert self.get_declared_definitions(library) == {element_type, element_type2} + assert len(self.file.by_type("IfcRelDeclares")) == 1 + + def test_doing_nothing_if_the_library_is_already_assigned(self): + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element_type2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + library = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProjectLibrary") + ifcopenshell.api.run( + "project.assign_declaration", self.file, definitions=[element_type, element_type2], relating_context=library + ) + total_elements = len([e for e in self.file]) + ifcopenshell.api.run( + "project.assign_declaration", self.file, definitions=[element_type, element_type2], relating_context=library + ) + assert len([e for e in self.file]) == total_elements + + def test_that_old_relationships_are_updated_if_they_still_contain_elements(self): + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + library = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProjectLibrary") + ifcopenshell.api.run( + "project.assign_declaration", self.file, definitions=[element_type], relating_context=library + ) + rel = self.file.by_type("IfcRelDeclares")[0] + + element_type2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element_type3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run( + "project.assign_declaration", self.file, definitions=[element_type2, element_type3], relating_context=library + ) + assert len(rel.RelatedDefinitions) == 3 diff --git a/src/ifcopenshell-python/test/api/test_api.py b/src/ifcopenshell-python/test/api/test_api.py index 72d20392f3..2f4db8786f 100644 --- a/src/ifcopenshell-python/test/api/test_api.py +++ b/src/ifcopenshell-python/test/api/test_api.py @@ -303,11 +303,26 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4): def test_unassigning_a_constraint(self): constraint = ifcopenshell.api.run("constraint.add_objective", self.file) element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run( - "constraint.assign_constraint", self.file, product=element, constraint=constraint - ) - ifcopenshell.api.run( - "constraint.unassign_constraint", self.file, product=element, constraint=constraint - ) + ifcopenshell.api.run("constraint.assign_constraint", self.file, product=element, constraint=constraint) + ifcopenshell.api.run("constraint.unassign_constraint", self.file, product=element, constraint=constraint) assert ifcopenshell.util.constraint.get_constrained_elements(element) == set() assert len(self.file.by_type("IfcRelAssociatesConstraint")) == 0 + + @deprecation_check + def test_assign_a_declaration(self): + def get_declared_definitions(project: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]: + definitions = set() + for declares in project.Declares: + definitions.update(declares.RelatedDefinitions) + return definitions + + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + library = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProjectLibrary") + ifcopenshell.api.run( + "project.assign_declaration", + self.file, + definition=element_type, + relating_context=library, + ) + assert get_declared_definitions(library) == {element_type} + assert len(self.file.by_type("IfcRelDeclares")) == 1