mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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]:
|
||||
|
||||
Reference in New Issue
Block a user