mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Hide MEP gizmos on non-parametric elements
MEP elements imported as tessellation / brep (no IfcExtrudedAreaSolid or IfcSweptDiskSolid in their body representation) can't be parametrically edited — the gizmos offer affordances the geometry kernel has no path to honour. tool.System.has_parametric_body inspects the Model/Body/MODEL_VIEW representation and returns True only when at least one item resolves to one of the two profile-sweep primitives. The gate is wired into: - GizmoMEPActions.is_eligible_object (the action icon group) - _active_is_flow_segment / _active_is_bend_fitting visibility predicates the icon row consults per-icon - GizmoPipeSegmentEdition / GizmoDuctSegmentEdition is_element_type tool.Parametric.is_pipe_segment / is_duct_segment stay IFC-class-only so their truth-table contract test keeps reading a single concern. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -2619,7 +2619,7 @@ class GizmoPipeSegmentEdition(bpy.types.GizmoGroup, _MEPSegmentEditionMixin, giz
|
||||
|
||||
@classmethod
|
||||
def is_element_type(cls, element):
|
||||
return tool.Parametric.is_pipe_segment(element)
|
||||
return tool.Parametric.is_pipe_segment(element) and tool.System.has_parametric_body(element)
|
||||
|
||||
|
||||
class GizmoDuctSegmentEdition(bpy.types.GizmoGroup, _MEPSegmentEditionMixin, gizmo.BaseParametricGizmoGroup):
|
||||
@@ -2646,7 +2646,7 @@ class GizmoDuctSegmentEdition(bpy.types.GizmoGroup, _MEPSegmentEditionMixin, giz
|
||||
|
||||
@classmethod
|
||||
def is_element_type(cls, element):
|
||||
return tool.Parametric.is_duct_segment(element)
|
||||
return tool.Parametric.is_duct_segment(element) and tool.System.has_parametric_body(element)
|
||||
|
||||
|
||||
# --- GizmoMEPActions group + visibility helpers ----------------------------
|
||||
@@ -2658,9 +2658,9 @@ def _selection_size() -> int:
|
||||
|
||||
def _active_is_flow_segment(obj: bpy.types.Object) -> bool:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element is None:
|
||||
if element is None or not element.is_a("IfcFlowSegment"):
|
||||
return False
|
||||
return element.is_a("IfcFlowSegment")
|
||||
return tool.System.has_parametric_body(element)
|
||||
|
||||
|
||||
def _active_mep_has_connected_neighbor(obj: bpy.types.Object) -> bool:
|
||||
@@ -2677,7 +2677,10 @@ def _active_mep_has_connected_neighbor(obj: bpy.types.Object) -> bool:
|
||||
|
||||
|
||||
def _active_is_bend_fitting(obj: bpy.types.Object) -> bool:
|
||||
return _is_bend_fitting(tool.Ifc.get_entity(obj))
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not _is_bend_fitting(element):
|
||||
return False
|
||||
return tool.System.has_parametric_body(element)
|
||||
|
||||
|
||||
class GizmoMEPActions(bpy.types.GizmoGroup, gizmo.BaseIconActionGroup):
|
||||
@@ -2789,9 +2792,9 @@ class GizmoMEPActions(bpy.types.GizmoGroup, gizmo.BaseIconActionGroup):
|
||||
if bend_props is not None and bend_props.is_active:
|
||||
return False
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element is None:
|
||||
if element is None or not tool.System.is_mep_element(element):
|
||||
return False
|
||||
return tool.System.is_mep_element(element)
|
||||
return tool.System.has_parametric_body(element)
|
||||
|
||||
def setup(self, context: bpy.types.Context) -> None:
|
||||
super().setup(context)
|
||||
|
||||
@@ -488,6 +488,24 @@ class System(bonsai.core.tool.System):
|
||||
def is_mep_element(cls, element: ifcopenshell.entity_instance) -> bool:
|
||||
return element.is_a("IfcFlowSegment") or element.is_a("IfcFlowFitting")
|
||||
|
||||
@classmethod
|
||||
def has_parametric_body(cls, element: ifcopenshell.entity_instance) -> bool:
|
||||
"""True when the MEP element's body representation is a profile sweep
|
||||
(``IfcExtrudedAreaSolid`` for segments, ``IfcSweptDiskSolid`` for
|
||||
fittings) — the shape the parametric edit + MEP action gizmos can
|
||||
actually mutate. Tessellation- or brep-imported MEP elements return
|
||||
False so their gizmos hide rather than offer edits the geometry
|
||||
kernel can't honour."""
|
||||
import bonsai.tool as tool
|
||||
|
||||
body = tool.Geometry.get_body_representation(element)
|
||||
if body is None:
|
||||
return False
|
||||
for item in tool.Ifc.get().traverse(body):
|
||||
if item.is_a("IfcExtrudedAreaSolid") or item.is_a("IfcSweptDiskSolid"):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def walk_connected_mep_elements(
|
||||
cls, start_element: ifcopenshell.entity_instance
|
||||
|
||||
@@ -214,7 +214,9 @@ def test_active_is_flow_segment_handles_unbound_object():
|
||||
|
||||
def test_active_is_flow_segment_classifies_segment_vs_fitting():
|
||||
"""Only IfcFlowSegment lights the lock-icon row; IfcFlowFitting (the
|
||||
bend's own class) does not."""
|
||||
bend's own class) does not. The parametric-body gate is mocked True
|
||||
here — its dedicated truth-table is in test_mep_actions_visibility
|
||||
sibling tests."""
|
||||
from bonsai.bim.module.model.mep import _active_is_flow_segment
|
||||
|
||||
segment_elem = Mock()
|
||||
@@ -223,10 +225,11 @@ def test_active_is_flow_segment_classifies_segment_vs_fitting():
|
||||
fitting_elem.is_a = lambda c: c == "IfcFlowFitting"
|
||||
|
||||
plain = Mock()
|
||||
with patch("bonsai.bim.module.model.mep.tool.Ifc.get_entity", return_value=segment_elem):
|
||||
assert _active_is_flow_segment(plain) is True
|
||||
with patch("bonsai.bim.module.model.mep.tool.Ifc.get_entity", return_value=fitting_elem):
|
||||
assert _active_is_flow_segment(plain) is False
|
||||
with patch("bonsai.bim.module.model.mep.tool.System.has_parametric_body", return_value=True):
|
||||
with patch("bonsai.bim.module.model.mep.tool.Ifc.get_entity", return_value=segment_elem):
|
||||
assert _active_is_flow_segment(plain) is True
|
||||
with patch("bonsai.bim.module.model.mep.tool.Ifc.get_entity", return_value=fitting_elem):
|
||||
assert _active_is_flow_segment(plain) is False
|
||||
|
||||
|
||||
def test_active_mep_has_connected_neighbor_returns_false_on_no_entity():
|
||||
|
||||
@@ -170,11 +170,12 @@ def test_gizmo_group_class_wiring(gizmo_cls_name, bl_idname, is_element_predicat
|
||||
|
||||
cls = getattr(mep, gizmo_cls_name)
|
||||
assert cls.bl_idname == bl_idname
|
||||
# The element_type predicate must delegate to the matching tool.Parametric.is_*.
|
||||
predicate = getattr(tool.Parametric, is_element_predicate)
|
||||
fake_element = Mock()
|
||||
fake_element.is_a.return_value = True
|
||||
with patch.object(tool.Parametric, is_element_predicate, side_effect=predicate) as p:
|
||||
with patch.object(tool.Parametric, is_element_predicate, side_effect=predicate) as p, patch.object(
|
||||
tool.System, "has_parametric_body", return_value=True
|
||||
):
|
||||
cls.is_element_type(fake_element)
|
||||
assert p.called, f"{gizmo_cls_name}.is_element_type did not delegate to Parametric.{is_element_predicate}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user