mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Highlight partner wall on link-toggle hover
Hovering a wall-junction link-toggle icon today only swaps the icon shape — the user doesn't see which wall the click will disconnect from until after they click. ATPATH (T-junction) configurations especially make the partner ambiguous when multiple connections sit close together. On hover, paint a wireframe bbox around the partner wall using the same shader, constants and color the array module already established for its layer-children highlight (POLYLINE_UNIFORM_COLOR, decorator_color_special, line width 1.8, alpha 0.8). The line-width / alpha constants in decorator.py are renamed from _ARRAY_LAYER_BBOX_LINE_* to _BBOX_HIGHLIGHT_LINE_* and shared between draw_array_layer_children_bbox and the new draw_wall_partner_bbox so the two highlights stay in lockstep. The trigger lives in a new GizmoWallLinkToggle subclass in wall.py which keeps the base gizmos.GizmoLinkToggle generic (per the generic-naming convention for shared widgets). The subclass's draw() calls super().draw(context) then on self.is_highlight outlines its partner_obj via the shared decorator helper. Same trigger pattern as GizmoArrayLayerIndicator. Blender's Gizmo API exposes target_set_operator but no symmetric getter, so the partner reference can't be read back from the bound operator handle. Instead GizmoWallUnjoinSingle.position_gizmos mirrors the resolved partner_obj onto each visible icon every frame next to the existing other_wall_guid write — the icon's draw() reads from its own __slots__-declared attribute. A forward-compat AST test pins the contract: GizmoWallLinkToggle.draw must reference is_highlight and call draw_wall_partner_bbox. Catches the regression where someone tidies the draw() override into super() or replaces the shared helper with an ad-hoc draw call. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -108,6 +108,7 @@ classes = (
|
||||
wall.GizmoWallFilletPreview,
|
||||
wall.GizmoWallFilletReedit,
|
||||
wall.GizmoWallJoinIntersection,
|
||||
wall.GizmoWallLinkToggle,
|
||||
wall.GizmoWallUnjoinSingle,
|
||||
wall.JoinWallsIntersection,
|
||||
wall.MergeWall,
|
||||
|
||||
@@ -2233,8 +2233,8 @@ def draw_polyline_segments(
|
||||
gpu.state.blend_set("NONE")
|
||||
|
||||
|
||||
_ARRAY_LAYER_BBOX_LINE_WIDTH = 1.8
|
||||
_ARRAY_LAYER_BBOX_LINE_ALPHA = 0.8
|
||||
_BBOX_HIGHLIGHT_LINE_WIDTH = 1.8
|
||||
_BBOX_HIGHLIGHT_LINE_ALPHA = 0.8
|
||||
_ARRAY_LAYER_BBOX_MAX_CHILDREN = 200
|
||||
|
||||
|
||||
@@ -2283,8 +2283,31 @@ def draw_array_layer_children_bbox(
|
||||
context,
|
||||
segments,
|
||||
color,
|
||||
_ARRAY_LAYER_BBOX_LINE_ALPHA,
|
||||
_ARRAY_LAYER_BBOX_LINE_WIDTH,
|
||||
_BBOX_HIGHLIGHT_LINE_ALPHA,
|
||||
_BBOX_HIGHLIGHT_LINE_WIDTH,
|
||||
)
|
||||
|
||||
|
||||
def draw_wall_partner_bbox(
|
||||
context: bpy.types.Context,
|
||||
partner_obj: bpy.types.Object,
|
||||
) -> None:
|
||||
"""Paint a wireframe bbox around ``partner_obj`` in the same 3D pass.
|
||||
Called inline from gizmo ``draw()`` methods so the highlight tracks the
|
||||
hover cursor one-for-one — no POST_VIEW handler, no timing lag.
|
||||
|
||||
Silently no-ops if the object has no bounding box (e.g. Empties)."""
|
||||
segments = bbox_world_edges(partner_obj)
|
||||
if not segments:
|
||||
return
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
color = prefs.decorator_color_special[:3]
|
||||
draw_polyline_segments(
|
||||
context,
|
||||
segments,
|
||||
color,
|
||||
_BBOX_HIGHLIGHT_LINE_ALPHA,
|
||||
_BBOX_HIGHLIGHT_LINE_WIDTH,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -3546,6 +3546,36 @@ class GizmoWallJoinIntersection(bpy.types.GizmoGroup, _WallGeomCachedBillboardin
|
||||
self.merge_icon.hide = True
|
||||
|
||||
|
||||
class GizmoWallLinkToggle(gizmo.GizmoLinkToggle, bpy.types.Gizmo):
|
||||
"""Link-toggle glyph with a partner-wall highlight on hover. The owning
|
||||
gizmo group writes the partner Blender object onto each icon every frame
|
||||
via ``partner_obj``; on ``is_highlight`` the partner's bbox is outlined
|
||||
inline so the user sees which wall the click will disconnect from
|
||||
before committing.
|
||||
|
||||
The partner reference is stashed on the gizmo instance rather than
|
||||
read back from the bound operator handle because Blender's Gizmo API
|
||||
exposes ``target_set_operator`` for binding but no symmetric getter."""
|
||||
|
||||
bl_idname = "VIEW3D_GT_wall_link_toggle"
|
||||
__slots__ = ("partner_obj",)
|
||||
|
||||
def setup(self) -> None:
|
||||
super().setup()
|
||||
self.partner_obj = None
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
super().draw(context)
|
||||
if not self.is_highlight:
|
||||
return
|
||||
partner = self.partner_obj
|
||||
if partner is None:
|
||||
return
|
||||
from bonsai.bim.module.model.decorator import draw_wall_partner_bbox
|
||||
|
||||
draw_wall_partner_bbox(context, partner)
|
||||
|
||||
|
||||
class GizmoWallUnjoinSingle(bpy.types.GizmoGroup, _WallGeomCachedBillboardingMixin):
|
||||
"""Activates when exactly one LAYER2 wall is selected. Surfaces an unjoin icon at
|
||||
every join location inferred from the wall's IfcRelConnectsPathElements inverse
|
||||
@@ -3600,7 +3630,7 @@ class GizmoWallUnjoinSingle(bpy.types.GizmoGroup, _WallGeomCachedBillboardingMix
|
||||
self.unjoin_op_props = []
|
||||
for _ in range(self.POOL_SIZE):
|
||||
icon = self.setup_icon_gizmo(
|
||||
"VIEW3D_GT_link_toggle", default_color, highlight_color, "bim.unjoin_wall_path_connection"
|
||||
"VIEW3D_GT_wall_link_toggle", default_color, highlight_color, "bim.unjoin_wall_path_connection"
|
||||
)
|
||||
icon.hide = True
|
||||
self.unjoin_icons.append(icon)
|
||||
@@ -3652,6 +3682,11 @@ class GizmoWallUnjoinSingle(bpy.types.GizmoGroup, _WallGeomCachedBillboardingMix
|
||||
# save/reload, and any sit-in-the-undo-stack interlude between dispatch
|
||||
# and execute.
|
||||
self.unjoin_op_props[slot_idx].other_wall_guid = other_elem.GlobalId
|
||||
# Mirror the partner reference onto the icon itself so its draw()
|
||||
# can outline the partner on hover without a Gizmo-side getter on
|
||||
# the bound operator (the API exposes target_set_operator with
|
||||
# no symmetric reader).
|
||||
icon.partner_obj = other_obj
|
||||
|
||||
|
||||
class GizmoWallFilletPreview(bpy.types.GizmoGroup):
|
||||
|
||||
@@ -28,6 +28,7 @@ rule is."""
|
||||
|
||||
import ast
|
||||
import inspect
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -59,3 +60,33 @@ def test_iter_path_connections_uses_path_connectable_predicate():
|
||||
"that strict predicate drops fillet-corner walls. Use "
|
||||
"is_path_connectable_wall instead."
|
||||
)
|
||||
|
||||
|
||||
def test_gizmo_wall_link_toggle_invokes_partner_bbox_helper():
|
||||
"""The wall subclass must call draw_wall_partner_bbox when its hover
|
||||
state is active. Without this contract the partner-wall highlight
|
||||
silently regresses if someone "tidies" the draw() override away."""
|
||||
from bonsai.bim.module.model import wall as wall_module
|
||||
|
||||
source = textwrap.dedent(inspect.getsource(wall_module.GizmoWallLinkToggle.draw))
|
||||
tree = ast.parse(source)
|
||||
attr_names = {node.attr for node in ast.walk(tree) if isinstance(node, ast.Attribute)}
|
||||
call_names: set[str] = set()
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, ast.Call):
|
||||
continue
|
||||
if isinstance(node.func, ast.Attribute):
|
||||
call_names.add(node.func.attr)
|
||||
elif isinstance(node.func, ast.Name):
|
||||
call_names.add(node.func.id)
|
||||
|
||||
assert "is_highlight" in attr_names, (
|
||||
"GizmoWallLinkToggle.draw must gate its highlight call on self.is_highlight — "
|
||||
"without it the partner outline would draw every frame, not just on hover."
|
||||
)
|
||||
assert "draw_wall_partner_bbox" in call_names, (
|
||||
"GizmoWallLinkToggle.draw must call draw_wall_partner_bbox to render the "
|
||||
"partner outline. The shared composite in decorator.py is the canonical "
|
||||
"trigger for this feature; replacing it with an ad-hoc draw call would "
|
||||
"drift from the array-children bbox styling."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user