diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py index c7bb5e9291..75420301d3 100644 --- a/src/blenderbim/blenderbim/core/geometry.py +++ b/src/blenderbim/blenderbim/core/geometry.py @@ -86,7 +86,10 @@ def switch_representation( should_sync_changes_first=False, ): if should_sync_changes_first and geometry.is_edited(obj) and not geometry.is_box_representation(representation): + representation_id = geometry.get_representation_id(representation) geometry.run_geometry_update_representation(obj=obj) + if not geometry.does_representation_id_exist(representation_id): + return representation = geometry.resolve_mapped_representation(representation) existing_data = geometry.get_representation_data(representation) diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 8a6f3a94ba..6a07ad5ab6 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -79,6 +79,14 @@ class Geometry(blenderbim.core.tool.Geometry): def does_object_have_mesh_with_faces(cls, obj): return bool(isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons)) + @classmethod + def does_representation_id_exist(cls, representation_id): + try: + tool.Ifc.get().by_id(representation_id) + return True + except: + return False + @classmethod def duplicate_object_data(cls, obj): return obj.data.copy() @@ -149,6 +157,10 @@ class Geometry(blenderbim.core.tool.Geometry): def get_representation_data(cls, representation): return bpy.data.meshes.get(cls.get_representation_name(representation)) + @classmethod + def get_representation_id(cls, representation): + return representation.id() + @classmethod def get_representation_name(cls, representation): return f"{representation.ContextOfItems.id()}/{representation.id()}" diff --git a/src/blenderbim/test/bim/feature/geometry.feature b/src/blenderbim/test/bim/feature/geometry.feature index ea2b7fbcc4..5bf3491be0 100644 --- a/src/blenderbim/test/bim/feature/geometry.feature +++ b/src/blenderbim/test/bim/feature/geometry.feature @@ -33,14 +33,14 @@ Scenario: Add representation - add a new representation to a typed instance And I press "bim.assign_class" And I press "bim.add_type_instance" And I press "bim.add_type_instance" - Then the object "IfcWall/Instance" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" - And the object "IfcWall/Instance.001" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" - When the object "IfcWall/Instance" is selected + Then the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" + And the object "IfcWall/Wall.001" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" + When the object "IfcWall/Wall" is selected And the variable "context" is "{ifc}.by_type('IfcGeometricRepresentationSubContext')[-1].id()" And I set "scene.BIMRootProperties.contexts" to "{context}" And I press "bim.add_representation" - Then the object "IfcWall/Instance" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" - And the object "IfcWall/Instance.001" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" + Then the object "IfcWall/Wall" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" + And the object "IfcWall/Wall.001" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" Scenario: Add representation - add a representation with a scale factor applied Given an empty IFC project @@ -79,14 +79,14 @@ Scenario: Switch representation - current edited representation is updated prior And the object "Cube" is selected And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" And I press "bim.assign_class" - And the variable "context" is "{ifc}.by_type('IfcGeometricRepresentationSubContext')[-1].id()" + And the variable "context" is "[c for c in {ifc}.by_type('IfcGeometricRepresentationSubContext') if c.ContextIdentifier=='Annotation'][0].id()" And I set "scene.BIMRootProperties.contexts" to "{context}" And I press "bim.add_representation" When the object "IfcWall/Cube" is scaled to "2" - And the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()" - And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation}, should_reload=True)" - And the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[-1].id()" - And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation}, should_reload=True)" + And the variable "representation" is "[r for r in {ifc}.by_type('IfcShapeRepresentation') if r.RepresentationType=='Tessellation'][0].id()" + And I press "bim.switch_representation(ifc_definition_id={representation}, should_reload=True)" + And the variable "representation" is "[r for r in {ifc}.by_type('IfcShapeRepresentation') if r.RepresentationType=='Annotation2D'][0].id()" + And I press "bim.switch_representation(ifc_definition_id={representation}, should_reload=True)" When I press "export_ifc.bim(filepath='{cwd}/test/files/export.ifc')" Then the object "IfcWall/Cube" dimensions are "4,4,0" @@ -153,8 +153,8 @@ Scenario: Remove representation - remove an instanced representation from an act When the variable "representation" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[1].MappedRepresentation.id()" And I press "bim.remove_representation(representation_id={representation})" Then the object "IfcWallType/Cube" has no data - Then the object "IfcWall/Instance" has no data - Then the object "IfcWall/Instance.001" has no data + Then the object "IfcWall/Wall" has no data + Then the object "IfcWall/Wall.001" has no data Scenario: Remove representation - remove an instanced representation from an active instance object Given an empty IFC project @@ -168,12 +168,12 @@ Scenario: Remove representation - remove an instanced representation from an act And I set "scene.BIMModelProperties.relating_type" to "{cube}" And I press "bim.add_type_instance" And I press "bim.add_type_instance" - And the object "IfcWall/Instance" is selected + And the object "IfcWall/Wall" is selected When the variable "representation" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[1].id()" And I press "bim.remove_representation(representation_id={representation})" Then the object "IfcWallType/Cube" has no data - Then the object "IfcWall/Instance" has no data - Then the object "IfcWall/Instance.001" has no data + Then the object "IfcWall/Wall" has no data + Then the object "IfcWall/Wall.001" has no data Scenario: Update representation - updating a tessellation Given an empty IFC project @@ -341,10 +341,10 @@ Scenario: Override duplicate move - copying a type instance with a representatio And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()" And I set "scene.BIMModelProperties.relating_type" to "{cube}" And I press "bim.add_type_instance" - And the object "IfcWall/Instance" is selected + And the object "IfcWall/Wall" 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" + Then the object "IfcWall/Wall.001" exists + And the object "IfcWall/Wall.001" has a "MappedRepresentation" representation of "Model/Body/MODEL_VIEW" Scenario: Override duplicate move - copying a layered extrusion Given an empty IFC project diff --git a/src/blenderbim/test/bim/feature/model.feature b/src/blenderbim/test/bim/feature/model.feature index ede7e3628c..4a22b908c1 100644 --- a/src/blenderbim/test/bim/feature/model.feature +++ b/src/blenderbim/test/bim/feature/model.feature @@ -12,7 +12,7 @@ Scenario: Add type instance - add from a mesh And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()" And I set "scene.BIMModelProperties.relating_type" to "{cube}" When I press "bim.add_type_instance" - Then the object "IfcWall/Instance" exists + Then the object "IfcWall/Wall" exists Scenario: Add type instance - add from an empty Given an empty IFC project @@ -25,7 +25,7 @@ Scenario: Add type instance - add from an empty And the variable "empty" is "{ifc}.by_type('IfcWallType')[0].id()" And I set "scene.BIMModelProperties.relating_type" to "{empty}" When I press "bim.add_type_instance" - Then the object "IfcWall/Instance" exists + Then the object "IfcWall/Wall" exists Scenario: Add type instance - add a mesh where existing instances have changed context Given an empty IFC project @@ -38,15 +38,15 @@ Scenario: Add type instance - add a mesh where existing instances have changed c And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()" And I set "scene.BIMModelProperties.relating_type" to "{cube}" And I press "bim.add_type_instance" - And the object "IfcWall/Instance" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" - And the object "IfcWall/Instance" is selected + And the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" + And the object "IfcWall/Wall" is selected And the variable "context" is "{ifc}.by_type('IfcGeometricRepresentationSubContext')[-1].id()" And I set "scene.BIMRootProperties.contexts" to "{context}" And I press "bim.add_representation" - And the object "IfcWall/Instance" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" + And the object "IfcWall/Wall" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" When I press "bim.add_type_instance" - Then the object "IfcWall/Instance" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" - And the object "IfcWall/Instance.001" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" + Then the object "IfcWall/Wall" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" + And the object "IfcWall/Wall.001" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" Scenario: Add grid Given an empty IFC project diff --git a/src/blenderbim/test/core/test_geometry.py b/src/blenderbim/test/core/test_geometry.py index d4606d5261..5f218a3b3b 100644 --- a/src/blenderbim/test/core/test_geometry.py +++ b/src/blenderbim/test/core/test_geometry.py @@ -272,7 +272,9 @@ class TestSwitchRepresentation: def test_updating_a_representation_if_the_blender_object_has_been_edited_prior_to_switching(self, geometry): geometry.is_edited("obj").should_be_called().will_return(True) geometry.is_box_representation("mapped_rep").should_be_called().will_return(False) + geometry.get_representation_id("mapped_rep").should_be_called().will_return("representation_id") geometry.run_geometry_update_representation(obj="obj").should_be_called() + geometry.does_representation_id_exist("representation_id").should_be_called().will_return(True) geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation") geometry.get_representation_data("representation").should_be_called().will_return("data") geometry.change_object_data("obj", "data", is_global=False).should_be_called() @@ -287,6 +289,22 @@ class TestSwitchRepresentation: should_sync_changes_first=True, ) + def test_not_switching_if_an_updated_representation_is_the_same_one_we_were_going_to_switch_to(self, geometry): + geometry.is_edited("obj").should_be_called().will_return(True) + geometry.is_box_representation("mapped_rep").should_be_called().will_return(False) + geometry.get_representation_id("mapped_rep").should_be_called().will_return("representation_id") + geometry.run_geometry_update_representation(obj="obj").should_be_called() + geometry.does_representation_id_exist("representation_id").should_be_called().will_return(False) + subject.switch_representation( + geometry, + obj="obj", + representation="mapped_rep", + should_reload=False, + enable_dynamic_voids=False, + is_global=False, + should_sync_changes_first=True, + ) + class TestGetRepresentationIfcParameters: def test_run(self, geometry): diff --git a/src/blenderbim/test/tool/test_geometry.py b/src/blenderbim/test/tool/test_geometry.py index 5f857685ef..6f1e1ce21f 100644 --- a/src/blenderbim/test/tool/test_geometry.py +++ b/src/blenderbim/test/tool/test_geometry.py @@ -121,6 +121,15 @@ class TestDoesObjectHaveMeshWithFaces(NewFile): assert subject.does_object_have_mesh_with_faces(obj) is True +class TestDoesRepresentationIdExist(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + representation = ifc.createIfcShapeRepresentation() + assert subject.does_representation_id_exist(representation.id()) is True + assert subject.does_representation_id_exist(12345) is False + + class TestDuplicateObjectData(NewFile): def test_run(self): data = bpy.data.meshes.new("Mesh") @@ -160,6 +169,13 @@ class TestGetRepresentationData(NewFile): assert subject.get_representation_data(representation) == data +class TestGetRepresentationId(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + representation = ifc.createIfcShapeRepresentation() + assert subject.get_representation_id(representation) == representation.id() + + class TestGetRepresentationName(NewFile): def test_run(self): ifc = ifcopenshell.file()