diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index a617549bdf..d3260fa278 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -1156,8 +1156,8 @@ class Style: @interface class Surveyor: def get_absolute_matrix(cls, obj): pass - def get_z_rotation(cls, obj: "bpy.types.Object") -> float: pass - def set_z_rotation(cls, obj: "bpy.types.Object", z: float) -> None: pass + def get_z_rotation(cls, obj): pass + def set_z_rotation(cls, obj, z): pass @interface diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index b0c9423906..0ecb767da1 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -554,7 +554,7 @@ class Blender(bonsai.core.tool.Blender): def sync_all( cls, context: bpy.types.Context, - enabled: Mapping[type[ViewportDecorator], bool], + enabled: Mapping[type[Blender.ViewportDecorator], bool], ) -> None: """Drive each listed decorator to its desired install state in one call. diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 5b7aafab7c..33bc310c22 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -75,10 +75,8 @@ if TYPE_CHECKING: from bonsai.bim.module.model.prop import ( BIMArrayProperties, BIMDoorProperties, - BIMDuctSegmentProperties, BIMExternalParametricGeometryProperties, BIMModelProperties, - BIMPipeSegmentProperties, BIMPolylineProperties, BIMRailingProperties, BIMRoofProperties, @@ -118,14 +116,6 @@ class Model(bonsai.core.tool.Model): def get_railing_props(cls, obj: bpy.types.Object) -> BIMRailingProperties: return obj.BIMRailingProperties # pyright: ignore[reportAttributeAccessIssue] - @classmethod - def get_pipe_segment_props(cls, obj: bpy.types.Object) -> BIMPipeSegmentProperties: - return obj.BIMPipeSegmentProperties # pyright: ignore[reportAttributeAccessIssue] - - @classmethod - def get_duct_segment_props(cls, obj: bpy.types.Object) -> BIMDuctSegmentProperties: - return obj.BIMDuctSegmentProperties # pyright: ignore[reportAttributeAccessIssue] - @classmethod def get_sverchok_props(cls, obj: bpy.types.Object) -> BIMSverchokProperties: return obj.BIMSverchokProperties # pyright: ignore[reportAttributeAccessIssue] @@ -2881,20 +2871,10 @@ class Model(bonsai.core.tool.Model): @classmethod def recreate_wall(cls, element: ifcopenshell.entity_instance, obj: bpy.types.Object) -> None: - # Curved fillet-corner walls own a hand-built banana body that - # ``regenerate_wall_representation`` would flatten — it reads the - # axis as a 2-point reference line and builds a straight extrusion. - # Instead rebuild the curve in place: ``regenerate_fillet_corner_wall`` - # keeps radius + placement from the pset / current ``ObjectPlacement`` - # while picking up new thickness / height from the wall type, which - # is what we want when a type-property edit triggered this call. - if ifcopenshell.util.element.get_pset(element, "BBIM_Wall", "IsFilletCorner"): - # Lazy import: ``tool.Model`` loads before ``bim/module/model`` - # at addon enable; a module-level import would cycle. - from bonsai.bim.module.model.wall import regenerate_fillet_corner_wall - - regenerate_fillet_corner_wall(element, obj) - return + # FIXME(PR4): the fillet-corner branch lands with PR4's + # `regenerate_fillet_corner_wall` (bim/module/model/wall.py). On v0.8.0 + # the function doesn't exist; falling through to the straight-extrusion + # path preserves v0.8.0 behaviour for fillet walls until PR4 ships. rep = ifcopenshell.api.geometry.regenerate_wall_representation(tool.Ifc.get(), element) bonsai.core.geometry.switch_representation( tool.Ifc, diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index 11a41672bc..163a3ea3c2 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -90,6 +90,32 @@ class Spatial(bonsai.core.tool.Spatial): break return element + @classmethod + def get_host_element(cls, filling: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance | None: + """The building element that hosts a filling (door/window) via the + standard ``FillsVoids → RelatingOpeningElement → VoidsElements → + RelatingBuildingElement`` chain, with safety guards at each hop. + Returns ``None`` if any link is missing, or if the given entity is + not a fillable type (no ``FillsVoids`` inverse). + + For the wall-only case (gizmos that only make sense on walls), use + `get_host_wall` which adds an ``IfcWall`` type filter on top of this.""" + if not getattr(filling, "FillsVoids", None): + return None + opening = filling.FillsVoids[0].RelatingOpeningElement + if not opening.VoidsElements: + return None + return opening.VoidsElements[0].RelatingBuildingElement + + @classmethod + def get_host_wall(cls, filling: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance | None: + """The ``IfcWall`` that hosts a filling (door/window), or ``None``. + + Walls only — fillings hosted in slabs / roofs / arbitrary elements + produce ``None`` so wall-offset callers stay opted out cleanly.""" + host = cls.get_host_element(filling) + return host if host and host.is_a("IfcWall") else None + @classmethod def can_contain(cls, container: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance) -> bool: if tool.Ifc.get_schema() == "IFC2X3": diff --git a/src/bonsai/bonsai/tool/system.py b/src/bonsai/bonsai/tool/system.py index 8d2b421370..bc11f3d642 100644 --- a/src/bonsai/bonsai/tool/system.py +++ b/src/bonsai/bonsai/tool/system.py @@ -299,29 +299,15 @@ class System(bonsai.core.tool.System): system_props = cls.get_system_props() return tool.Ifc.get_entity_by_id(system_props.active_system_id) - # Decoration-data cache, keyed on (decorator_cache_token, id(decorated_elements_set)). - _decoration_data_cache_key: tuple | None = None - _decoration_data_cache: dict[str, Any] | None = None - @classmethod def get_decoration_data(cls) -> dict[str, Any]: - from bonsai.bim.decorator_cache import get_decorator_cache_token from bonsai.bim.module.system.data import ObjectSystemData, SystemDecorationData if not ObjectSystemData.is_loaded: ObjectSystemData.load() if not SystemDecorationData.is_loaded: SystemDecorationData.load() - - token = get_decorator_cache_token() - key = (token, id(SystemDecorationData.data["decorated_elements"])) - if key == cls._decoration_data_cache_key and cls._decoration_data_cache is not None: - return cls._decoration_data_cache - - result = cls._build_decoration_data() - cls._decoration_data_cache_key = key - cls._decoration_data_cache = result - return result + return cls._build_decoration_data() @classmethod def _build_decoration_data(cls) -> dict[str, Any]: