mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-05 23:41:44 +00:00
Route _has_material_styles through tool.Root.has_material_styles
Pre-existing architectural smell on v0.8.0: core/root.py.copy_class
called a module-level _has_material_styles helper that did
ifcopenshell.util.element.get_materials() directly, bypassing the
Prophecy mock seam that every other branch in copy_class flowed
through. Symptom: test/core/test_root.py::TestCopyClass::
test_AAAAAAAAAAAA passed mock strings into copy_class, the helper
called .is_a() on the string, AttributeError.
Move the check to tool.Root.has_material_styles (paired with
assign_body_styles — they're called in sequence as "is there a
material style? if not, assign body style"). core/root.py now
calls root.has_material_styles(new) like every other dependency,
fixing the test failure and dropping the ifcopenshell.util.element
import that was the only consumer of the ifcopenshell import at
module load in core/root.py.
* core/tool.py: add abstract has_material_styles to Root interface.
* tool/root.py: add concrete classmethod near assign_body_styles.
* core/root.py: replace _has_material_styles helper call site with
root.has_material_styles; drop the local helper and its import.
* test/core/test_root.py: add the new mock expectation
root.has_material_styles("element").will_return(False) before the
existing assign_body_styles expectation.
Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -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],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user