mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 07:49:59 +00:00
Fix MEP pair-disconnect crash and bend re-edit pen icon
Three user-facing fixes for the MEP-system gizmo surface: 1. MEP pair-disconnect no longer crashes Blender. The MEPSystemPathDecorator cached entity_instance references in _cached_walk; deleting a bridging fitting via the gizmo left a freed SWIG handle in the list, and the next _build_geometry pass segfaulted on .is_a. The cache now stores STEP integer ids and re-resolves via ifc_file.by_id on each draw, plus folds tool.Parametric.get_geom_generation into the cache key — ifcopenshell.api mutations invalidate before the next frame regardless of how the deletion was routed. 2. Bend re-edit pen icon stays reachable. The bend creation path tessellates the swept-disk body (upstream geometry-kernel workaround), so tool.System.has_parametric_body correctly returns False for a freshly-committed bend. _active_is_bend_fitting and GizmoMEPActions.is_eligible_object now fall back to the type's BBIM_Fitting pset — the same source bim.enable_bend_preview_from_bend reads parameters from — keeping the pen icon eligible. 3. MEP pair / per-port unjoin icons unified through bim.disconnect_elements. The MEP gizmo group's three unjoin icons (pair, start, end) now share the wall-disconnect surface: same VIEW3D_GT_wall_link_toggle icon, same bim.disconnect_elements operator. tool.Connection.find_rels learned a new "mep-pair-fitting" kind that returns the bridging fitting as the disconnect target; core.connection.disconnect_rel grew the matching dispatch arm. The old MEPUnjoinAtPort and MEPUnjoinPair operators are removed. Also registered wall.GizmoPairDisconnect (previously declared but never in the classes tuple, so dead code) for the wall+slab pair-disconnect surface, and extracted MEP port-topology helpers (find_bridging_fitting, is_disconnectable_fitting, neighbours_at_ports) onto tool.System so the canonical walk has a single home. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -23,6 +23,7 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.api.system
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.system
|
||||
import ifcopenshell.util.unit
|
||||
import numpy as np
|
||||
@@ -39,6 +40,132 @@ class TestImplementsTool(NewFile):
|
||||
assert isinstance(subject(), bonsai.core.tool.System)
|
||||
|
||||
|
||||
class TestHasParametricBody(NewFile):
|
||||
"""The MEP-action gizmo predicates gate on ``has_parametric_body``;
|
||||
fittings whose swept body lives on the type via ``IfcMappedItem`` must
|
||||
return True so the pen-icon and lock-icon rows show on the occurrence."""
|
||||
|
||||
def _build_bend_occurrence_with_mapped_body(self):
|
||||
bpy.ops.bim.create_project()
|
||||
ifc_file = tool.Ifc.get()
|
||||
body_ctx = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
placement = ifc_file.create_entity(
|
||||
"IfcAxis2Placement3D",
|
||||
Location=ifc_file.create_entity("IfcCartesianPoint", Coordinates=(0.0, 0.0, 0.0)),
|
||||
)
|
||||
line = ifc_file.create_entity(
|
||||
"IfcLine",
|
||||
Pnt=ifc_file.create_entity("IfcCartesianPoint", Coordinates=(0.0, 0.0, 0.0)),
|
||||
Dir=ifc_file.create_entity(
|
||||
"IfcVector",
|
||||
Orientation=ifc_file.create_entity("IfcDirection", DirectionRatios=(1.0, 0.0, 0.0)),
|
||||
Magnitude=1.0,
|
||||
),
|
||||
)
|
||||
trimmed = ifc_file.create_entity(
|
||||
"IfcTrimmedCurve",
|
||||
BasisCurve=line,
|
||||
Trim1=(ifc_file.create_entity("IfcParameterValue", wrappedValue=0.0),),
|
||||
Trim2=(ifc_file.create_entity("IfcParameterValue", wrappedValue=1.0),),
|
||||
SenseAgreement=True,
|
||||
MasterRepresentation="PARAMETER",
|
||||
)
|
||||
swept = ifc_file.create_entity("IfcSweptDiskSolid", Directrix=trimmed, Radius=0.05)
|
||||
type_body = ifc_file.create_entity(
|
||||
"IfcShapeRepresentation",
|
||||
ContextOfItems=body_ctx,
|
||||
RepresentationIdentifier="Body",
|
||||
RepresentationType="AdvancedSweptSolid",
|
||||
Items=(swept,),
|
||||
)
|
||||
rep_map = ifc_file.create_entity(
|
||||
"IfcRepresentationMap", MappingOrigin=placement, MappedRepresentation=type_body
|
||||
)
|
||||
fitting_type = ifc_file.create_entity(
|
||||
"IfcPipeFittingType",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
Name="BendType",
|
||||
PredefinedType="BEND",
|
||||
RepresentationMaps=(rep_map,),
|
||||
)
|
||||
mapped_item = ifc_file.create_entity(
|
||||
"IfcMappedItem",
|
||||
MappingSource=rep_map,
|
||||
MappingTarget=ifc_file.create_entity(
|
||||
"IfcCartesianTransformationOperator3D",
|
||||
LocalOrigin=ifc_file.create_entity("IfcCartesianPoint", Coordinates=(0.0, 0.0, 0.0)),
|
||||
),
|
||||
)
|
||||
occurrence_body = ifc_file.create_entity(
|
||||
"IfcShapeRepresentation",
|
||||
ContextOfItems=body_ctx,
|
||||
RepresentationIdentifier="Body",
|
||||
RepresentationType="MappedRepresentation",
|
||||
Items=(mapped_item,),
|
||||
)
|
||||
fitting = ifc_file.create_entity(
|
||||
"IfcPipeFitting",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
Name="Bend",
|
||||
PredefinedType="BEND",
|
||||
Representation=ifc_file.create_entity("IfcProductDefinitionShape", Representations=(occurrence_body,)),
|
||||
)
|
||||
ifc_file.create_entity(
|
||||
"IfcRelDefinesByType",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
RelatedObjects=(fitting,),
|
||||
RelatingType=fitting_type,
|
||||
)
|
||||
return fitting
|
||||
|
||||
def test_returns_true_for_swept_disk_via_mapped_item(self):
|
||||
"""``traverse()`` follows the
|
||||
``IfcMappedItem.MappingSource.MappedRepresentation`` chain so the
|
||||
``IfcSweptDiskSolid`` on the type's body is reachable from the
|
||||
occurrence's body representation. Bend fittings produced by the
|
||||
bend-preview commit path use this exact representation shape."""
|
||||
fitting = self._build_bend_occurrence_with_mapped_body()
|
||||
assert subject.has_parametric_body(fitting) is True
|
||||
|
||||
def test_returns_false_for_tessellated_body(self):
|
||||
"""The bend creation path replaces the swept-disk body with an
|
||||
``IfcTriangulatedFaceSet`` as an upstream geometry-kernel
|
||||
workaround. The traverse finds no extruded / swept solid, so the
|
||||
predicate returns False — pinning the constraint that drives the
|
||||
``BBIM_Fitting`` pset fallback in the bend-icon visibility
|
||||
predicate."""
|
||||
bpy.ops.bim.create_project()
|
||||
ifc_file = tool.Ifc.get()
|
||||
body_ctx = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
coords = ifc_file.create_entity(
|
||||
"IfcCartesianPointList3D",
|
||||
CoordList=((0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)),
|
||||
)
|
||||
tessellation = ifc_file.create_entity(
|
||||
"IfcTriangulatedFaceSet",
|
||||
Coordinates=coords,
|
||||
CoordIndex=((1, 2, 3),),
|
||||
)
|
||||
body = ifc_file.create_entity(
|
||||
"IfcShapeRepresentation",
|
||||
ContextOfItems=body_ctx,
|
||||
RepresentationIdentifier="Body",
|
||||
RepresentationType="Tessellation",
|
||||
Items=(tessellation,),
|
||||
)
|
||||
fitting = ifc_file.create_entity(
|
||||
"IfcPipeFitting",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
Name="TessellatedBend",
|
||||
PredefinedType="BEND",
|
||||
Representation=ifc_file.create_entity("IfcProductDefinitionShape", Representations=(body,)),
|
||||
)
|
||||
|
||||
assert subject.has_parametric_body(fitting) is False
|
||||
|
||||
|
||||
class TestAddPorts(NewFile):
|
||||
def setup_mep_segment(self):
|
||||
bpy.ops.bim.create_project()
|
||||
|
||||
Reference in New Issue
Block a user