mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-12 14:33:28 +00:00
Fix fillet preview crash + surface openings on fillet walls
Three wall-gizmo fixes: * GizmoWallFilletPreview crashed on every draw_prepare after the DRY-colors refactor moved decoration lookups onto self.get_decoration_colors() — that method lives on BillboardingGizmoGroupMixin / BaseParametricGizmoGroup, but GizmoWallFilletPreview inherited only from bpy.types.GizmoGroup. setup() AttributeError'd silently, leaving radius_dim and friends unset. Add the mixin to the bases; rename _position_gizmos to position_gizmos so the mixin's refresh/draw_prepare dispatch lands correctly and drop the now-redundant overrides. * GizmoWallAddOpening's poll gated on the strict is_wall predicate, which rejects fillet-corner walls (no LAYER2 usage by IFC spec). Switch to is_path_connectable_wall on both the active and the partner-exclusion checks so the add-opening icon surfaces over curved corners — matching every other wall-state gizmo's host gate. * Show / hide openings was only available on LAYER2 walls because GizmoWallEdition's parametric edit pipeline (which carries the toggle) refuses fillet bodies. Add GizmoWallFilletToggleOpenings, a dedicated single-icon group that polls on is_fillet_corner_wall and reuses bim.toggle_wall_openings — the body stays untouched. Forward-compat AST guards in test_wall_gizmos_forward_compat.py pin both invariants: every wall GizmoGroup that calls self.get_decoration_colors() must inherit a mixin that provides it, and GizmoWallAddOpening.poll must keep using the looser predicate. Generated with the assistance of an AI coding tool.
This commit is contained in:
committed by
Thomas Krijnen
parent
28491b290a
commit
e1ab5047b0
@@ -41,7 +41,7 @@ def test_iter_path_connections_uses_path_connectable_predicate():
|
||||
poll. Strict ``is_wall`` rejects fillet-corner walls (which have no
|
||||
LAYER2 usage by IFC spec), so a regression to ``is_wall`` would silently
|
||||
drop fillet partners from the connection list — visible to the user as
|
||||
"the corner looks unconnected from the adjacent wall's selection.\""""
|
||||
"the corner looks unconnected from the adjacent wall's selection.\" """
|
||||
from bonsai.bim.module.model.wall import _iter_path_connections
|
||||
|
||||
source = inspect.getsource(_iter_path_connections)
|
||||
@@ -90,3 +90,72 @@ def test_gizmo_wall_link_toggle_invokes_partner_bbox_helper():
|
||||
"trigger for this feature; replacing it with an ad-hoc draw call would "
|
||||
"drift from the array-children bbox styling."
|
||||
)
|
||||
|
||||
|
||||
def test_every_wall_gizmo_group_resolves_get_decoration_colors():
|
||||
"""Any wall ``GizmoGroup`` whose ``setup()`` reads decoration colours via
|
||||
``self.get_decoration_colors()`` must inherit from a mixin that supplies
|
||||
it (``gizmo.BaseParametricGizmoGroup`` or ``gizmo.BillboardingGizmoGroupMixin``).
|
||||
Without the mixin the call AttributeErrors inside ``setup()``, Blender
|
||||
logs the failure and skips the rest of ``setup()``, and every later
|
||||
``draw_prepare()`` blows up on whichever attribute the truncated setup
|
||||
failed to assign — a silent, runtime-only regression that no other test
|
||||
catches."""
|
||||
import bpy
|
||||
|
||||
from bonsai.bim.module.model import wall as wall_module
|
||||
|
||||
offenders: list[str] = []
|
||||
for name in dir(wall_module):
|
||||
cls = getattr(wall_module, name)
|
||||
if not inspect.isclass(cls):
|
||||
continue
|
||||
if inspect.getmodule(cls) is not wall_module:
|
||||
continue
|
||||
if not issubclass(cls, bpy.types.GizmoGroup):
|
||||
continue
|
||||
setup = cls.__dict__.get("setup")
|
||||
if setup is None:
|
||||
continue
|
||||
try:
|
||||
src = inspect.getsource(setup)
|
||||
except (OSError, TypeError):
|
||||
continue
|
||||
if "self.get_decoration_colors()" not in src:
|
||||
continue
|
||||
if not hasattr(cls, "get_decoration_colors"):
|
||||
offenders.append(cls.__name__)
|
||||
|
||||
assert not offenders, (
|
||||
f"GizmoGroup subclasses {offenders} call self.get_decoration_colors() in "
|
||||
"setup() but inherit from no class that provides it. Add "
|
||||
"gizmo.BillboardingGizmoGroupMixin (or gizmo.BaseParametricGizmoGroup) to "
|
||||
"the class bases — both define get_decoration_colors and are the canonical "
|
||||
"wall-gizmo mixins."
|
||||
)
|
||||
|
||||
|
||||
def test_gizmo_wall_add_opening_accepts_fillet_corner_active():
|
||||
"""``GizmoWallAddOpening.poll`` must gate on ``is_path_connectable_wall``,
|
||||
not the strict ``is_wall`` predicate. Fillet-corner walls carry no LAYER2
|
||||
usage by IFC spec, so the strict predicate rejects them and the
|
||||
add-opening icon never surfaces over a curved corner — symmetry with the
|
||||
join / unjoin / extend wall gizmos (all of which already poll on the
|
||||
looser predicate) is required for the user to drop openings into fillet
|
||||
corners at all."""
|
||||
from bonsai.bim.module.model.wall import GizmoWallAddOpening
|
||||
|
||||
source = textwrap.dedent(inspect.getsource(GizmoWallAddOpening.poll))
|
||||
tree = ast.parse(source)
|
||||
attr_names = {node.attr for node in ast.walk(tree) if isinstance(node, ast.Attribute)}
|
||||
|
||||
assert "is_path_connectable_wall" in attr_names, (
|
||||
"GizmoWallAddOpening.poll must gate on tool.Parametric.is_path_connectable_wall "
|
||||
"for both the active element and the partner-exclusion check. The strict "
|
||||
"is_wall predicate hides the add-opening gizmo over every fillet-corner wall."
|
||||
)
|
||||
assert "is_wall" not in attr_names, (
|
||||
"GizmoWallAddOpening.poll must NOT call .is_wall — that strict predicate "
|
||||
"drops fillet-corner walls. Use is_path_connectable_wall instead, matching "
|
||||
"the host gate every other wall-state gizmo group uses."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user