diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/unassign_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/unassign_representation.py index da5f0d99d1..bc9f4d5453 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/unassign_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/unassign_representation.py @@ -50,13 +50,16 @@ class Usecase: return representations.remove(representation) if not representations: - self.file.remove(product.Representation) + product_def = product.Representation + # TODO: should somehow find matching shape aspect and remove it + # even before the last representation is removed. + self.process_shape_aspects(product_def) + self.file.remove(product_def) else: product.Representation.Representations = representations def unassign_type_representation(self) -> None: matching_representation_map = None - representation_maps = self.settings["product"].RepresentationMaps or [] for representation_map in self.settings["product"].RepresentationMaps or []: if representation_map.MappedRepresentation == self.settings["representation"]: @@ -68,8 +71,26 @@ class Usecase: self.settings["product"].RepresentationMaps = [ rm for rm in self.settings["product"].RepresentationMaps if rm != matching_representation_map ] or None + self.process_shape_aspects(matching_representation_map) self.remove_representation_map_only(matching_representation_map) + def process_shape_aspects(self, product_representation: ifcopenshell.entity_instance) -> None: + # Technically IfcShapeAspect doesn't become invalid when product representation is removed, + # but shape aspect makes sense only in context of some other representation. + if self.file.schema == "IFC2X3" and product_representation.is_a("IfcRepresentationMap"): + shape_aspects = [ + a + for a in self.file.by_type("IfcShapeAspect") + if a.PartOfProductDefinitionShape == product_representation + ] + else: + shape_aspects = product_representation.HasShapeAspects + for shape_aspect in shape_aspects: + representations = shape_aspect.ShapeRepresentations + self.file.remove(shape_aspect) + for rep in representations: + ifcopenshell.api.geometry.remove_representation(self.file, rep) + def remove_representation_map_only(self, representation_map: ifcopenshell.entity_instance) -> None: representation_map.MappedRepresentation = self.file.createIfcShapeRepresentation() ifcopenshell.util.element.remove_deep2(self.file, representation_map) diff --git a/src/ifcopenshell-python/test/api/geometry/test_unassign_representation.py b/src/ifcopenshell-python/test/api/geometry/test_unassign_representation.py index 579cee589b..316c6cbe90 100644 --- a/src/ifcopenshell-python/test/api/geometry/test_unassign_representation.py +++ b/src/ifcopenshell-python/test/api/geometry/test_unassign_representation.py @@ -25,26 +25,41 @@ class TestUnassignRepresentation(test.bootstrap.IFC4): def test_unassigning_a_product_representation(self): representation = self.file.createIfcShapeRepresentation() representation2 = self.file.createIfcShapeRepresentation() + item = self.file.create_entity("IfcExtrudedAreaSolid") + representation2.Items = (item,) wall = self.file.createIfcWall( Representation=self.file.createIfcProductDefinitionShape(Representations=[representation, representation2]) ) + + shape_aspect = self.file.create_entity("IfcShapeAspect") + shape_aspect.ShapeRepresentations = (self.file.createIfcShapeRepresentation(Items=(item,)),) + shape_aspect.PartOfProductDefinitionShape = wall.Representation + ifcopenshell.api.geometry.unassign_representation(self.file, product=wall, representation=representation) assert representation not in wall.Representation.Representations ifcopenshell.api.geometry.unassign_representation(self.file, product=wall, representation=representation2) assert not wall.Representation assert len(self.file.by_type("IfcShapeRepresentation")) == 2 assert len(self.file.by_type("IfcProductDefinitionShape")) == 0 + assert len(self.file.by_type("IfcShapeAspect")) == 0 def test_unassigning_a_type_product_representation(self): - representation = self.file.createIfcShapeRepresentation() + item = self.file.create_entity("IfcExtrudedAreaSolid") + representation = self.file.createIfcShapeRepresentation(Items=(item,)) origin = self.file.createIfcAxis2Placement3D() repmap = self.file.createIfcRepresentationMap(MappedRepresentation=representation, MappingOrigin=origin) walltype = self.file.createIfcWallType(RepresentationMaps=[repmap]) + + shape_aspect = self.file.create_entity("IfcShapeAspect") + shape_aspect.ShapeRepresentations = (self.file.createIfcShapeRepresentation(Items=(item,)),) + shape_aspect.PartOfProductDefinitionShape = repmap + ifcopenshell.api.geometry.unassign_representation(self.file, product=walltype, representation=representation) assert not walltype.RepresentationMaps assert len(self.file.by_type("IfcAxis2Placement3D")) == 0 assert len(self.file.by_type("IfcRepresentationMap")) == 0 assert len(self.file.by_type("IfcShapeRepresentation")) == 1 + assert len(self.file.by_type("IfcShapeAspect")) == 0 def test_unassigning_a_type_product_representation_used_by_instances(self): representation = self.file.createIfcShapeRepresentation()