Fix latent runtime bugs + ty annotations surfaced by CI

Five code paths in slim PR2 referenced symbols that don't exist in
v0.8.0's bim layer, raising at first call. Plus three type
annotations that ty flagged as unresolved.

1. tool/system.py:get_decoration_data — drop the cache layer that
   keyed on a token from a bim/decorator_cache.py module. The cache
   is dead-or-broken in slim: the depsgraph bump handler that would
   invalidate the token lives in PR3's bim/handler.py decompose, so
   the token stays at 0 forever. Either the cache never hits
   (decorated_elements rebuilt → new id() per call) or returns
   stale data (list reused). Revert to direct
   `_build_decoration_data()` calls. PR3 reintroduces the cache
   atomically: decorator_cache module + handler install + cache
   wrap + tests. Keeps `_build_decoration_data` extraction
   (cleaner than v0.8.0's monolithic version regardless of cache).

2. tool/spatial.py — add `get_host_element` + `get_host_wall`.
   The interface stubs in `core/tool.py:1037-1038` were declared
   but never implemented. `tool/duplicate.py:99` (object duplication
   with fills) and `tool/model.py:1260` (array per-child opening
   mirror) call these and would raise AttributeError.

3. tool/model.py:recreate_wall — drop the fillet-corner branch
   that function-locally imports `regenerate_fillet_corner_wall`
   from `bim/module/model/wall`. The function lands with PR4; fall
   through to the straight-extrusion path preserves v0.8.0
   behaviour for fillet walls until then. Tag FIXME(PR4).

4. tool/model.py — drop `get_pipe_segment_props` /
   `get_duct_segment_props` accessors. Their return types reference
   `BIMPipeSegmentProperties` / `BIMDuctSegmentProperties` which
   land with PR4's prop.py; calling either accessor on v0.8.0 would
   AttributeError on `obj.BIM<X>SegmentProperties`. Zero callers in
   slim — PR4 reintroduces both accessors together with the
   PropertyGroups they wrap. Also drops the matching TYPE_CHECKING
   imports.

5. tool/blender.py:557 — `Mapping[type[ViewportDecorator], bool]`
   needs the qualified `Blender.ViewportDecorator` because the
   annotation is on a method INSIDE the same nested class; the
   bare name doesn't resolve at type-check time.

6. core/tool.py Surveyor — drop the `obj: "bpy.types.Object"` /
   `z: float` / `-> float` / `-> None` annotations on
   `get_z_rotation` / `set_z_rotation`. The `@interface` decorator
   wraps each method as `classmethod(abstractmethod(...))` at
   import time, but ty doesn't track the wrap and flags every
   call site as `missing-argument` plus the `pass` body as
   `empty-body` against the declared return type, plus the
   `bpy.types.Object` forward-ref as `unresolved-reference`.
   Reverting to v0.8.0's untyped style (matching the sibling
   `get_absolute_matrix(cls, obj)` stub) clears six ty errors at
   the cost of zero runtime semantics — the abstract stubs only
   serve as registry markers, concrete `tool.Surveyor.*` carries
   the real signatures.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Gorgious56
2026-05-27 14:38:37 +02:00
parent 89b7eff03e
commit 786d3c8a89
5 changed files with 34 additions and 42 deletions
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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.
+4 -24
View File
@@ -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,
+26
View File
@@ -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":
+1 -15
View File
@@ -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]: