diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py index 59ee5c0e95..f36462a9e7 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py @@ -55,21 +55,24 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, Operator): if not relating_obj: return - for obj in bpy.context.selected_objects + [bpy.context.active_object]: + for obj in tool.Blender.get_selected_objects(): if obj == relating_obj: continue element = tool.Ifc.get_entity(obj) if not element: continue - result = core.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=relating_obj, - related_obj=obj, - ) - if not result: - self.report({"ERROR"}, f" Cannot aggregate {obj.name} to {relating_obj.name}") + try: + core.assign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=relating_obj, + related_obj=obj, + ) + except core.IncompatibleAggregateError: + self.report({"ERROR"}, f"Cannot aggregate {obj.name} to {relating_obj.name}") + except core.AggregateRepresentationError: + self.report({"ERROR"}, f"Cannot aggregate to {relating_obj.name} with a body representation") class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator): diff --git a/src/blenderbim/blenderbim/core/aggregate.py b/src/blenderbim/blenderbim/core/aggregate.py index 44d4444f79..00fd67b3c8 100644 --- a/src/blenderbim/blenderbim/core/aggregate.py +++ b/src/blenderbim/blenderbim/core/aggregate.py @@ -41,9 +41,12 @@ def assign_object( related_obj: Optional[bpy.types.Object] = None, ) -> Union[ifcopenshell.entity_instance, None]: if not aggregator.can_aggregate(relating_obj, related_obj): - return + raise IncompatibleAggregateError + relating_object = ifc.get_entity(relating_obj) + if aggregator.has_physical_body_representation(relating_object): + raise AggregateRepresentationError rel = ifc.run( - "aggregate.assign_object", products=[ifc.get_entity(related_obj)], relating_object=ifc.get_entity(relating_obj) + "aggregate.assign_object", products=[ifc.get_entity(related_obj)], relating_object=relating_object ) collector.assign(relating_obj) collector.assign(related_obj) @@ -84,3 +87,11 @@ def add_part_to_object( part_obj = blender.create_ifc_object(ifc_class=part_class, name=part_name) assign_object(ifc, aggregator, collector, relating_obj=obj, related_obj=part_obj) blender.set_active_object(obj) + + +class IncompatibleAggregateError(Exception): + pass + + +class AggregateRepresentationError(Exception): + pass diff --git a/src/blenderbim/blenderbim/tool/aggregate.py b/src/blenderbim/blenderbim/tool/aggregate.py index 9188e12a32..9d4c6ff09a 100644 --- a/src/blenderbim/blenderbim/tool/aggregate.py +++ b/src/blenderbim/blenderbim/tool/aggregate.py @@ -30,9 +30,9 @@ class Aggregate(blenderbim.core.tool.Aggregate): related_object = tool.Ifc.get_entity(related_obj) if not relating_object or not related_object: return False - if (relating_object.is_a("IfcElement") or relating_object.is_a("IfcElementType")) and related_object.is_a("IfcElement"): - if relating_obj.data: # See #3973 - return False + if (relating_object.is_a("IfcElement") or relating_object.is_a("IfcElementType")) and related_object.is_a( + "IfcElement" + ): return True if tool.Ifc.get_schema() == "IFC2X3": if relating_object.is_a("IfcSpatialStructureElement") and related_object.is_a("IfcSpatialStructureElement"): @@ -46,6 +46,13 @@ class Aggregate(blenderbim.core.tool.Aggregate): return True return False + @classmethod + def has_physical_body_representation(cls, element: ifcopenshell.entity_instance) -> bool: + if element.is_a("IfcElement") or element.is_a("IfcElementType"): # See 3973 + if ifcopenshell.util.representation.get_representation(element, "Model", "Body"): + return True + return False + @classmethod def disable_editing(cls, obj: bpy.types.Object) -> None: obj.BIMObjectAggregateProperties.is_editing = False diff --git a/src/blenderbim/test/tool/test_aggregate.py b/src/blenderbim/test/tool/test_aggregate.py index 6540d2fcb7..9d7f693157 100644 --- a/src/blenderbim/test/tool/test_aggregate.py +++ b/src/blenderbim/test/tool/test_aggregate.py @@ -18,6 +18,9 @@ import bpy import ifcopenshell +import ifcopenshell.api.root +import ifcopenshell.api.unit +import ifcopenshell.api.context import blenderbim.core.tool import blenderbim.tool as tool from test.bim.bootstrap import NewFile @@ -92,16 +95,24 @@ class TestCanAggregate(NewFile): subelement_obj = bpy.data.objects.new("Object", None) assert subject.can_aggregate(element_obj, subelement_obj) is False - def test_aggregates_with_meshes_are_invalid(self): + +class TestHasPhysicalBodyRepresentation(NewFile): + def test_run(self): ifc = ifcopenshell.file() - tool.Ifc.set(ifc) element = ifc.createIfcElementAssembly() - element_obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) - tool.Ifc.link(element, element_obj) - subelement = ifc.createIfcBeam() - subelement_obj = bpy.data.objects.new("Object", None) - tool.Ifc.link(subelement, subelement_obj) - assert subject.can_aggregate(element_obj, subelement_obj) is False + assert subject.has_physical_body_representation(element) is False + ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcProject") + ifcopenshell.api.unit.assign_unit(ifc) + context = ifcopenshell.api.context.add_context(ifc, context_type="Model") + body = ifcopenshell.api.context.add_context( + ifc, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=context + ) + builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc) + origin = builder.create_axis2_placement_3d() + block = ifc.createIfcCsgSolid(ifc.createIfcBlock(origin, 200, 200, 200)) + rep = builder.get_representation(context=body, items=[block]) + ifcopenshell.api.geometry.assign_representation(ifc, product=element, representation=rep) + assert subject.has_physical_body_representation(element) is True class TestDisableEditing(NewFile):