diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 4c286e3353..5c400d9da0 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -508,6 +508,7 @@ class OverridePasteBuffer(bpy.types.Operator): def execute(self, context): bpy.ops.view3d.pastebuffer() - for obj in context.selected_objects: - blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj) + if IfcStore.get_file(): + for obj in context.selected_objects: + blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index 4dc67e706a..2f88a204a0 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -170,10 +170,10 @@ class AssignClass(bpy.types.Operator): ) if product.is_a("IfcElementType"): - self.place_in_types_collection(obj, context) + tool.Collector.assign(obj) elif product.is_a("IfcOpeningElement"): obj.display_type = "WIRE" - self.place_in_openings_collection(obj, context) + tool.Collector.assign(obj) elif ( product.is_a("IfcSpatialElement") or product.is_a("IfcSpatialStructureElement") @@ -190,30 +190,6 @@ class AssignClass(bpy.types.Operator): self.assign_potential_spatial_container(obj) context.view_layer.objects.active = obj - def place_in_types_collection(self, obj, context): - for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]: - if not [c for c in project.children if "Types" in c.name]: - types = bpy.data.collections.new("Types") - project.collection.children.link(types) - for collection in [c for c in project.children if "Types" in c.name]: - for user_collection in obj.users_collection: - user_collection.objects.unlink(obj) - collection.collection.objects.link(obj) - break - break - - def place_in_openings_collection(self, obj, context): - for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]: - if not [c for c in project.children if "IfcOpeningElements" in c.name]: - opening_elements = bpy.data.collections.new("IfcOpeningElements") - project.collection.children.link(opening_elements) - for collection in [c for c in project.children if "IfcOpeningElements" in c.name]: - for user_collection in obj.users_collection: - user_collection.objects.unlink(obj) - collection.collection.objects.link(obj) - break - break - def place_in_spatial_collection(self, obj, context): for collection in obj.users_collection: if collection.name == obj.name: @@ -229,7 +205,7 @@ class AssignClass(bpy.types.Operator): parent_collection.children.link(collection) blenderbim.core.aggregate.assign_object( tool.Ifc, - tool.Aggregator, + tool.Aggregate, tool.Collector, relating_obj=bpy.data.objects.get(parent_collection.name), related_obj=obj, diff --git a/src/blenderbim/blenderbim/tool/collector.py b/src/blenderbim/blenderbim/tool/collector.py index 80b531b4c2..8ff5a7c9e5 100644 --- a/src/blenderbim/blenderbim/tool/collector.py +++ b/src/blenderbim/blenderbim/tool/collector.py @@ -79,6 +79,22 @@ class Collector(blenderbim.core.tool.Collector): @classmethod def _get_collection(cls, element, obj): + if element.is_a("IfcTypeObject"): + collection = bpy.data.collections.get("Types") + if not collection: + collection = bpy.data.collections.new("Types") + project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0]) + project_obj.users_collection[0].children.link(collection) + return collection + + if element.is_a("IfcOpeningElement"): + collection = bpy.data.collections.get("IfcOpeningElements") + if not collection: + collection = bpy.data.collections.new("IfcOpeningElements") + project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0]) + project_obj.users_collection[0].children.link(collection) + return collection + aggregate = ifcopenshell.util.element.get_aggregate(element) if aggregate: aggregate_obj = tool.Ifc.get_object(aggregate) diff --git a/src/blenderbim/test/bim/feature/root.feature b/src/blenderbim/test/bim/feature/root.feature index 23d9cbcc6b..a90eab9364 100644 --- a/src/blenderbim/test/bim/feature/root.feature +++ b/src/blenderbim/test/bim/feature/root.feature @@ -44,7 +44,7 @@ Scenario: Assign a type class to a cube Then the object "IfcWallType/Cube" is an "IfcWallType" And the object "IfcWallType/Cube" is in the collection "Types" And the object "IfcWallType/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" - + Scenario: Assign a spatial class to a cube Given an empty IFC project And I add a cube @@ -56,6 +56,19 @@ Scenario: Assign a spatial class to a cube And the object "IfcBuilding/Cube" is in the collection "IfcBuilding/Cube" And the object "IfcBuilding/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" +Scenario: Assign a spatial class to a cube already in a collection + Given an empty IFC project + And I add a cube + And the object "Cube" is placed in the collection "IfcBuildingStorey/My Storey" + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcSpatialElement" + And I set "scene.BIMRootProperties.ifc_class" to "IfcSpace" + And I press "bim.assign_class" + Then the object "IfcSpace/Cube" is an "IfcSpace" + And the object "IfcSpace/Cube" is in the collection "IfcSpace/Cube" + And the collection "IfcSpace/Cube" is in the collection "IfcBuildingStorey/My Storey" + And the object "IfcSpace/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" + Scenario: Assign an opening class to a cube Given an empty IFC project And I add a cube diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index bdd29c10dd..7a547c66e8 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -133,6 +133,7 @@ def the_object_name_is_selected(name): additionally_the_object_name_is_selected(name) +@given(parsers.parse('the object "{name}" is placed in the collection "{collection}"')) @when(parsers.parse('the object "{name}" is placed in the collection "{collection}"')) def the_object_name_is_placed_in_the_collection_collection(name, collection): obj = the_object_name_exists(name) diff --git a/src/blenderbim/test/tool/test_collector.py b/src/blenderbim/test/tool/test_collector.py index f4d6705d88..5eec59cfc5 100644 --- a/src/blenderbim/test/tool/test_collector.py +++ b/src/blenderbim/test/tool/test_collector.py @@ -158,3 +158,23 @@ class TestAssign(NewFile): ) subject.assign(subelement_obj) assert subelement_obj.users_collection[0].name == "IfcSite/My Site" + + def test_in_decomposition_mode_types_are_placed_in_the_types_collection(self): + bpy.ops.bim.create_project() + element_obj = bpy.data.objects.new("IfcWallType/Name", None) + element = tool.Ifc.get().createIfcWallType() + tool.Ifc.link(element, element_obj) + bpy.context.scene.collection.objects.link(element_obj) + subject.assign(element_obj) + assert element_obj.users_collection[0].name == "Types" + assert bpy.data.collections.get("IfcProject/My Project").children.get("Types") + + def test_in_decomposition_mode_openings_are_placed_in_the_openings_collection(self): + bpy.ops.bim.create_project() + element_obj = bpy.data.objects.new("IfcOpeningElement/Name", None) + element = tool.Ifc.get().createIfcOpeningElement() + tool.Ifc.link(element, element_obj) + bpy.context.scene.collection.objects.link(element_obj) + subject.assign(element_obj) + assert element_obj.users_collection[0].name == "IfcOpeningElements" + assert bpy.data.collections.get("IfcProject/My Project").children.get("IfcOpeningElements") diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py index 89d54511de..ddbeb8fb20 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py @@ -31,6 +31,8 @@ class Usecase: continue elif inverse.is_a("IfcRelContainedInSpatialStructure") and inverse.RelatingStructure == from_element: continue + elif inverse.is_a("IfcRelDefinesByType") and inverse.RelatingType == from_element: + continue elif inverse.is_a("IfcRelFillsElement"): continue elif inverse.is_a("IfcRelAssociatesMaterial") and "Usage" in inverse.RelatingMaterial.is_a(): diff --git a/src/ifcopenshell-python/test/api/root/test_copy_class.py b/src/ifcopenshell-python/test/api/root/test_copy_class.py index a20306befd..ce7e873020 100644 --- a/src/ifcopenshell-python/test/api/root/test_copy_class.py +++ b/src/ifcopenshell-python/test/api/root/test_copy_class.py @@ -116,3 +116,10 @@ class TestCopyClass(test.bootstrap.IFC4): new = ifcopenshell.api.run("root.copy_class", self.file, product=element) assert new.HasAssociations[0].RelatingMaterial != element.HasAssociations[0].RelatingMaterial assert new.HasAssociations[0].RelatingMaterial.is_a("IfcMaterialLayerSetUsage") + + def test_copying_a_type_and_purging_type_relationships(self): + type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=type) + new = ifcopenshell.api.run("root.copy_class", self.file, product=type) + assert not new.Types