diff --git a/src/bonsai/bonsai/core/root.py b/src/bonsai/bonsai/core/root.py index 3a283a6a65..2276e0623f 100644 --- a/src/bonsai/bonsai/core/root.py +++ b/src/bonsai/bonsai/core/root.py @@ -20,8 +20,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Optional -import ifcopenshell.util.element - if TYPE_CHECKING: import bpy import ifcopenshell @@ -58,31 +56,12 @@ def copy_class( geometry.change_object_data(obj, data, is_global=True) geometry.rename_object(data, geometry.get_representation_name(ifc.get_entity(data))) # Only assign styles if element doesn't get them from material - if not _has_material_styles(ifc, new): + if not root.has_material_styles(new): root.assign_body_styles(new, obj) collector.assign(obj) return new -def _has_material_styles(ifc: type[tool.Ifc], element: ifcopenshell.entity_instance) -> bool: - """Check if element has styles defined through its material. - - Returns True if any constituent material has a style representation, - which means styles should NOT be applied directly to the geometry. - """ - materials = ifcopenshell.util.element.get_materials(element) - - if not materials: - return False - - # Check if any of the constituent materials have styles - for material in materials: - if hasattr(material, "HasRepresentation") and material.HasRepresentation: - return True - - return False - - def assign_class( ifc: type[tool.Ifc], collector: type[tool.Collector], diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index d3260fa278..8c12788acd 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -884,6 +884,7 @@ class Root: def get_object_name(cls, obj): pass def get_object_representation(cls, obj): pass def get_representation_context(cls, representation): pass + def has_material_styles(cls, element): pass def is_containable(cls, element): pass def is_drawing_annotation(cls, element): pass def is_element_a(cls, element, ifc_class): pass diff --git a/src/bonsai/bonsai/tool/root.py b/src/bonsai/bonsai/tool/root.py index 8880a168fe..524f590a81 100644 --- a/src/bonsai/bonsai/tool/root.py +++ b/src/bonsai/bonsai/tool/root.py @@ -71,6 +71,18 @@ class Root(bonsai.core.tool.Root): should_use_presentation_style_assignment=props.should_use_presentation_style_assignment, ) + @classmethod + def has_material_styles(cls, element: ifcopenshell.entity_instance) -> bool: + """``True`` if any constituent material on ``element`` carries a style + representation. Body styles should NOT be applied directly when this + is True — the material-inherited style is the authoritative source. + Paired with ``assign_body_styles``: callers check this first and only + call ``assign_body_styles`` when it returns False.""" + materials = ifcopenshell.util.element.get_materials(element) + if not materials: + return False + return any(getattr(m, "HasRepresentation", None) for m in materials) + @classmethod def copy_representation( cls, source: ifcopenshell.entity_instance, dest: ifcopenshell.entity_instance diff --git a/src/bonsai/test/core/test_root.py b/src/bonsai/test/core/test_root.py index 121236803d..96bd389d1d 100644 --- a/src/bonsai/test/core/test_root.py +++ b/src/bonsai/test/core/test_root.py @@ -56,6 +56,7 @@ class TestCopyClass: ifc.get_entity("data").should_be_called().will_return("new_representation") geometry.get_representation_name("new_representation").should_be_called().will_return("name") geometry.rename_object("data", "name").should_be_called() + root.has_material_styles("element").should_be_called().will_return(False) root.assign_body_styles("element", "obj").should_be_called() collector.assign("obj").should_be_called() subject.copy_class(ifc, collector, geometry, root, obj="obj")