diff --git a/src/blenderbim/blenderbim/bim/export_ifc.py b/src/blenderbim/blenderbim/bim/export_ifc.py index 25cb6bd27d..5dfae0a135 100644 --- a/src/blenderbim/blenderbim/bim/export_ifc.py +++ b/src/blenderbim/blenderbim/bim/export_ifc.py @@ -169,7 +169,7 @@ class IfcExporter: if parent.is_a("IfcSpatialStructureElement") and not element.is_a("IfcSpatialStructureElement"): if parent != ifcopenshell.util.element.get_container(element): blenderbim.core.spatial.assign_container( - tool.Ifc, tool.Collector, tool.Container, structure_obj=parent_obj, element_obj=obj + tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj ) elif parent != ifcopenshell.util.element.get_aggregate(element): blenderbim.core.aggregate.assign_object( diff --git a/src/blenderbim/blenderbim/core/root.py b/src/blenderbim/blenderbim/core/root.py index a363881cde..4ed58d1cb2 100644 --- a/src/blenderbim/blenderbim/core/root.py +++ b/src/blenderbim/blenderbim/core/root.py @@ -25,7 +25,7 @@ def copy_class(ifc, collector, root, obj=None): ifc.link(element, obj) relating_type = root.get_element_type(element) if relating_type and root.does_type_have_representations(relating_type): - ifc.run("type.assign_type", related_object=element, relating_type=relating_type) + ifc.run("type.map_type_representations", related_object=element, relating_type=relating_type) else: root.run_geometry_add_representation(obj=obj, context=root.get_object_context(obj)) collector.assign(obj) diff --git a/src/blenderbim/pytest.ini b/src/blenderbim/pytest.ini index fab708981e..5d251b6622 100644 --- a/src/blenderbim/pytest.ini +++ b/src/blenderbim/pytest.ini @@ -6,6 +6,7 @@ markers = geometry material misc + model owner patch project diff --git a/src/blenderbim/test/bim/feature/geometry.feature b/src/blenderbim/test/bim/feature/geometry.feature index 57744dbb93..44cd9eab4a 100644 --- a/src/blenderbim/test/bim/feature/geometry.feature +++ b/src/blenderbim/test/bim/feature/geometry.feature @@ -89,3 +89,19 @@ Scenario: Override duplicate move - with active IFC data And the object "IfcWall/Cube.001" is an "IfcWall" And the object "IfcBuildingStorey/My Storey.001" exists And the object "IfcBuildingStorey/My Storey.001" is an "IfcBuildingStorey" + +Scenario: Override duplicate move - copying a type instance with a representation map + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" + And I press "bim.assign_class" + And I set "scene.BIMTypeProperties.ifc_class" to "IfcWallType" + And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()" + And I set "scene.BIMTypeProperties.relating_type" to "{cube}" + And I press "bim.add_type_instance" + And the object "IfcWall/Instance" is selected + When I press "object.duplicate_move" + Then the object "IfcWall/Instance.001" exists + And the object "IfcWall/Instance.001" has a "MappedRepresentation" representation of "Model/Body/MODEL_VIEW" diff --git a/src/blenderbim/test/bim/feature/model.feature b/src/blenderbim/test/bim/feature/model.feature new file mode 100644 index 0000000000..dd4a870c1b --- /dev/null +++ b/src/blenderbim/test/bim/feature/model.feature @@ -0,0 +1,28 @@ +@model +Feature: Model + +Scenario: Add type instance - add from a mesh + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" + And I press "bim.assign_class" + And I set "scene.BIMTypeProperties.ifc_class" to "IfcWallType" + And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()" + And I set "scene.BIMTypeProperties.relating_type" to "{cube}" + When I press "bim.add_type_instance" + Then the object "IfcWall/Instance" exists + +Scenario: Add type instance - add from an empty + Given an empty IFC project + And I add an empty + And the object "Empty" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" + And I press "bim.assign_class" + And I set "scene.BIMTypeProperties.ifc_class" to "IfcWallType" + And the variable "empty" is "{ifc}.by_type('IfcWallType')[0].id()" + And I set "scene.BIMTypeProperties.relating_type" to "{empty}" + When I press "bim.add_type_instance" + Then the object "IfcWall/Instance" exists diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index 664a0ca73e..d70dd54651 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -18,6 +18,7 @@ import os import bpy +import webbrowser import ifcopenshell import blenderbim.tool as tool import blenderbim.bim @@ -29,6 +30,9 @@ scenarios("feature") variables = {"cwd": os.getcwd(), "ifc": "IfcStore.get_file()"} +# Monkey-patch webbrowser opening since we want to test headlessly +webbrowser.open = lambda x: True + def replace_variables(value): for key, new_value in variables.items(): @@ -204,14 +208,14 @@ def the_object_name_has_a_body_of_value(name, value): assert the_object_name_exists(name).data.body == value -@then(parsers.parse('the object "{name}" has a "{_type}" representation of "{context}"')) -def the_object_name_has_a_type_representation_of_context(name, _type, context): +@then(parsers.parse('the object "{name}" has a "{type}" representation of "{context}"')) +def the_object_name_has_a_representation_type_of_context(name, type, context): ifc = an_ifc_file_exists() element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id) context, subcontext, target_view = context.split("/") - assert ifcopenshell.util.representation.get_representation( - element, context, subcontext or None, target_view or None - ) + rep = ifcopenshell.util.representation.get_representation(element, context, subcontext or None, target_view or None) + assert rep + assert rep.RepresentationType == type @then(parsers.parse('the material "{name}" exists')) diff --git a/src/blenderbim/test/core/test_root.py b/src/blenderbim/test/core/test_root.py index f9c715ad01..c3ce50cb2f 100644 --- a/src/blenderbim/test/core/test_root.py +++ b/src/blenderbim/test/core/test_root.py @@ -32,7 +32,7 @@ class TestCopyClass: ifc.link("element", "obj").should_be_called() root.get_element_type("element").should_be_called().will_return("type") root.does_type_have_representations("type").should_be_called().will_return(True) - ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called() + ifc.run("type.map_type_representations", related_object="element", relating_type="type").should_be_called() collector.assign("obj").should_be_called() root.is_opening_element("element").should_be_called().will_return(False) subject.copy_class(ifc, collector, root, obj="obj") @@ -65,7 +65,7 @@ class TestCopyClass: ifc.link("element", "obj").should_be_called() root.get_element_type("element").should_be_called().will_return("type") root.does_type_have_representations("type").should_be_called().will_return(True) - ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called() + ifc.run("type.map_type_representations", related_object="element", relating_type="type").should_be_called() collector.assign("obj").should_be_called() root.is_opening_element("element").should_be_called().will_return(True) root.add_dynamic_opening_voids("element", "obj").should_be_called() diff --git a/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py b/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py index e79319377e..435db477ea 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py +++ b/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py @@ -54,8 +54,14 @@ class Usecase: } ) - self.map_representations() + ifcopenshell.api.run( + "type.map_type_representations", + self.file, + related_object=self.settings["related_object"], + relating_type=self.settings["relating_type"], + ) self.map_material_usages() + return types def map_representations(self): if not self.settings["relating_type"].RepresentationMaps: diff --git a/src/ifcopenshell-python/ifcopenshell/api/type/map_type_representations.py b/src/ifcopenshell-python/ifcopenshell/api/type/map_type_representations.py new file mode 100644 index 0000000000..6b76a29f0a --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/type/map_type_representations.py @@ -0,0 +1,41 @@ +import ifcopenshell +import ifcopenshell.api +import ifcopenshell.util.element + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "related_object": None, + "relating_type": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + if not self.settings["relating_type"].RepresentationMaps: + return + representations = [] + if self.settings["related_object"].Representation: + representations = self.settings["related_object"].Representation.Representations + for representation in representations: + print('for each rep', representation) + ifcopenshell.api.run( + "geometry.unassign_representation", + self.file, + product=self.settings["related_object"], + representation=representation, + ) + ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation}) + for representation_map in self.settings["relating_type"].RepresentationMaps: + representation = representation_map.MappedRepresentation + mapped_representation = ifcopenshell.api.run( + "geometry.map_representation", self.file, representation=representation + ) + ifcopenshell.api.run( + "geometry.assign_representation", + self.file, + product=self.settings["related_object"], + representation=mapped_representation, + ) diff --git a/src/ifcopenshell-python/test/api/type/test_map_type_representation.py b/src/ifcopenshell-python/test/api/type/test_map_type_representation.py new file mode 100644 index 0000000000..c57dee5034 --- /dev/null +++ b/src/ifcopenshell-python/test/api/type/test_map_type_representation.py @@ -0,0 +1,33 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestMapTypeRepresentations(test.bootstrap.IFC4): + def test_doing_nothing_if_the_type_has_no_representation_maps(self): + element = self.file.createIfcWall() + type = self.file.createIfcWallType() + ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=type) + total_elements = len([e for e in self.file]) + ifcopenshell.api.run("type.map_type_representations", self.file, related_object=element, relating_type=type) + assert len([e for e in self.file]) == total_elements + + def test_removing_existing_element_representations_and_mapping_type_representations(self): + context = self.file.createIfcGeometricRepresentationSubContext() + element = self.file.createIfcWall( + Representation=self.file.createIfcProductRepresentation( + Representations=[self.file.createIfcShapeRepresentation(ContextOfItems=context)] + ) + ) + type = self.file.createIfcWallType( + RepresentationMaps=[ + self.file.createIfcRepresentationMap( + MappedRepresentation=self.file.createIfcShapeRepresentation(ContextOfItems=context) + ) + ] + ) + self.file.createIfcRelDefinesByType(RelatingType=type, RelatedObjects=[element]) + ifcopenshell.api.run("type.map_type_representations", self.file, related_object=element, relating_type=type) + rep = element.Representation.Representations[0] + assert rep.RepresentationType == "MappedRepresentation" + assert rep.Items[0].MappingSource == type.RepresentationMaps[0] + assert len(self.file.by_type("IfcShapeRepresentation")) == 2