From ef04402ed83fcfddeab51216068a95c20117b1b9 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 21 Aug 2024 14:47:07 +0500 Subject: [PATCH] Fix Image empty interpreted as Mesh #5194 Apparently we can't trust `obj.data == None` to identify empties since in Blender there are empties that can use `bpy.types.Image` as their `obj.data`. --- .../bonsai/bim/module/geometry/operator.py | 3 ++- src/bonsai/bonsai/bim/module/root/operator.py | 8 ++++++- src/bonsai/bonsai/core/geometry.py | 7 ++++--- src/bonsai/bonsai/core/tool.py | 1 + src/bonsai/bonsai/tool/geometry.py | 21 ++++++++++++++++--- src/bonsai/test/core/test_geometry.py | 5 ++++- 6 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index 03fbb234a0..12d0844017 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -364,7 +364,8 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator): # TODO: write unit tests to see how this bulk operation handles # contradictory ifc_representation_class values and when # ifc_representation_class is IfcTextLiteral - if not obj.data: + data = obj.data + if not tool.Geometry.is_data_supported_for_adding_representation(data): continue self.update_obj_mesh_representation(context, obj) tool.Ifc.finish_edit(obj) diff --git a/src/bonsai/bonsai/bim/module/root/operator.py b/src/bonsai/bonsai/bim/module/root/operator.py index f695d8e102..ca84543dea 100644 --- a/src/bonsai/bonsai/bim/module/root/operator.py +++ b/src/bonsai/bonsai/bim/module/root/operator.py @@ -172,7 +172,13 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): props = context.scene.BIMRootProperties - objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects or [context.active_object] + objects: list[bpy.types.Object] = [] + if self.obj: + objects = [bpy.data.objects[self.obj]] + elif objects := context.selected_objects: + pass + elif obj := context.active_object: + objects = [obj] if not objects: self.report({"INFO"}, "No objects selected.") diff --git a/src/bonsai/bonsai/core/geometry.py b/src/bonsai/bonsai/core/geometry.py index 9b158b399f..73625af636 100644 --- a/src/bonsai/bonsai/core/geometry.py +++ b/src/bonsai/bonsai/core/geometry.py @@ -17,7 +17,7 @@ # along with Bonsai. If not, see . from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Union if TYPE_CHECKING: import bpy @@ -47,7 +47,8 @@ def add_representation( context: ifcopenshell.entity_instance, ifc_representation_class: Optional[str] = None, profile_set_usage: Optional[ifcopenshell.entity_instance] = None, -) -> ifcopenshell.entity_instance: +) -> Union[ifcopenshell.entity_instance, None]: + """Add IFC representation based on object `.data`.""" element = ifc.get_entity(obj) if not element: return @@ -55,7 +56,7 @@ def add_representation( edit_object_placement(ifc, geometry, surveyor, obj=obj) data = geometry.get_object_data(obj) - if not data and ifc_representation_class != "IfcTextLiteral": + if not geometry.is_data_supported_for_adding_representation(data) and ifc_representation_class != "IfcTextLiteral": return representation = ifc.run( diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index d7bfe192c5..d5e74e757b 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -407,6 +407,7 @@ class Geometry: def import_representation_parameters(cls, data): pass def is_body_representation(cls, representation): pass def is_box_representation(cls, representation): pass + def is_data_supported_for_adding_representation(cls, data): pass def is_edited(cls, obj): pass def is_mapped_representation(cls, representation): pass def is_type_product(cls, element): pass diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index 1f99323683..c14d33bd24 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -41,8 +41,8 @@ from collections import defaultdict from math import radians, pi from mathutils import Vector, Matrix from bonsai.bim.ifc import IfcStore -from typing import Union, Iterable, Optional, Literal -from typing import Iterator +from typing import Union, Iterable, Optional, Literal, Iterator +from typing_extensions import TypeIs class Geometry(bonsai.core.tool.Geometry): @@ -477,7 +477,7 @@ class Geometry(bonsai.core.tool.Geometry): return hasher.hexdigest() @classmethod - def get_object_data(cls, obj: bpy.types.Object) -> bpy.types.ID: + def get_object_data(cls, obj: bpy.types.Object) -> Union[bpy.types.ID, None]: return obj.data @classmethod @@ -646,6 +646,21 @@ class Geometry(bonsai.core.tool.Geometry): def is_box_representation(cls, representation: ifcopenshell.entity_instance) -> bool: return representation.ContextOfItems.ContextIdentifier == "Box" + @classmethod + def is_data_supported_for_adding_representation(cls, data: Union[bpy.types.ID, None]) -> TypeIs[ + Union[ + bpy.types.Mesh, + bpy.types.Curve, + ] + ]: + supported_types = ( + bpy.types.Mesh, + bpy.types.Curve, + ) + if not data: + return False + return isinstance(data, supported_types) + @classmethod def is_edited(cls, obj: bpy.types.Object) -> bool: return not all([tool.Cad.is_x(o, 1.0) for o in obj.scale]) or obj in IfcStore.edited_objs diff --git a/src/bonsai/test/core/test_geometry.py b/src/bonsai/test/core/test_geometry.py index 2c9a052cee..d0210cfd15 100644 --- a/src/bonsai/test/core/test_geometry.py +++ b/src/bonsai/test/core/test_geometry.py @@ -42,6 +42,7 @@ class TestAddRepresentation: # Add representation geometry.get_object_data("obj").should_be_called().will_return("data") + geometry.is_data_supported_for_adding_representation("data").should_be_called().will_return(True) geometry.get_cartesian_point_coordinate_offset("obj").should_be_called().will_return("coordinate_offset") geometry.get_total_representation_items("obj").should_be_called().will_return(1) geometry.should_force_faceted_brep().should_be_called().will_return(False) @@ -108,6 +109,7 @@ class TestAddRepresentation: # Add representation geometry.get_object_data("obj").should_be_called().will_return("data") + geometry.is_data_supported_for_adding_representation("data").should_be_called().will_return(True) geometry.get_cartesian_point_coordinate_offset("obj").should_be_called().will_return("coordinate_offset") geometry.get_total_representation_items("obj").should_be_called().will_return(1) geometry.should_force_faceted_brep().should_be_called().will_return(False) @@ -158,7 +160,8 @@ class TestAddRepresentation: TestEditObjectPlacement.predict(self, ifc, geometry, surveyor) # Add representation - geometry.get_object_data("obj").should_be_called().will_return(None) + geometry.get_object_data("obj").should_be_called().will_return("data") + geometry.is_data_supported_for_adding_representation("data").should_be_called().will_return(False) assert ( subject.add_representation( ifc,