Merge pull request #7543 from falken10vdl/MEP-ports

Simplified handling of Ports in MEP
Addresses https://community.osarch.org/discussion/comment/27740#Comment_27740
This commit is contained in:
falken10vdl
2026-01-22 09:34:52 +01:00
committed by GitHub
11 changed files with 317 additions and 89 deletions
@@ -21,11 +21,13 @@ from . import ui, prop, operator, decorator
classes = (
operator.AddPort,
operator.AddRelatedPortConnection,
operator.AddSystem,
operator.AddZone,
operator.AssignSystem,
operator.AssignUnassignFlowControl,
operator.ConnectPort,
operator.CycleFlowDirection,
operator.DisableEditingSystem,
operator.DisableEditingZone,
operator.DisableSystemEditingUI,
+100 -4
View File
@@ -198,7 +198,16 @@ class ShowPorts(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
# Ifc.Operator - as operator will sync object's position with IFC.
core.show_ports(tool.Ifc, tool.System, tool.Spatial, element=tool.Ifc.get_entity(context.active_object))
element = tool.Ifc.get_entity(context.active_object)
core.show_ports(tool.Ifc, tool.System, tool.Spatial, element=element)
for port in tool.System.get_ports(element):
connected_port = tool.System.get_connected_port(port)
if connected_port:
connected_port_obj = tool.Ifc.get_object(connected_port)
if not connected_port_obj:
parent_element = tool.System.get_port_relating_element(connected_port)
core.show_ports(tool.Ifc, tool.System, tool.Spatial, element=parent_element)
class HidePorts(bpy.types.Operator, tool.Ifc.Operator):
@@ -217,7 +226,7 @@ class HidePorts(bpy.types.Operator, tool.Ifc.Operator):
class AddPort(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_port"
bl_description = "Add port at current cursor position"
bl_description = "Add USERDEFINED port at current cursor position"
bl_label = "Add Port"
bl_options = {"REGISTER", "UNDO"}
@@ -246,7 +255,41 @@ class ConnectPort(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj1 = context.active_object
obj2 = context.selected_objects[0] if context.selected_objects[1] == obj1 else context.selected_objects[1]
core.connect_port(tool.Ifc, port1=tool.Ifc.get_entity(obj1), port2=tool.Ifc.get_entity(obj2))
direction = tool.Ifc.get_entity(obj1).FlowDirection or "NOTDEFINED"
core.connect_port(
tool.Ifc, port1=tool.Ifc.get_entity(obj1), port2=tool.Ifc.get_entity(obj2), direction=direction
)
class AddRelatedPortConnection(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_related_port_connection"
bl_label = "Connect Port"
bl_options = {"REGISTER", "UNDO"}
relating_port_id: bpy.props.IntProperty()
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
props = tool.System.get_system_props()
self.layout.prop(props, "related_port", text="Select Port")
def _execute(self, context):
props = tool.System.get_system_props()
if not props.related_port or props.related_port == "NONE":
return {"CANCELLED"}
port_obj = bpy.data.objects.get(props.related_port)
related_port = tool.Ifc.get_entity(port_obj)
relating_port = tool.Ifc.get().by_id(self.relating_port_id)
direction = relating_port.FlowDirection or "NOTDEFINED"
core.connect_port(tool.Ifc, port1=relating_port, port2=related_port, direction=direction)
PortData.is_loaded = False
return {"FINISHED"}
class DisconnectPort(bpy.types.Operator, tool.Ifc.Operator):
@@ -256,6 +299,9 @@ class DisconnectPort(bpy.types.Operator, tool.Ifc.Operator):
element_id: bpy.props.IntProperty(default=0, options={"SKIP_SAVE"})
def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)
def _execute(self, context):
if self.element_id != 0:
element = tool.Ifc.get().by_id(self.element_id)
@@ -310,7 +356,8 @@ class MEPConnectElements(bpy.types.Operator, tool.Ifc.Operator):
ports_distance[(port1, port2)] = distance
closest_ports = min(ports_distance, key=lambda x: ports_distance[x])
core.connect_port(tool.Ifc, *closest_ports)
direction = closest_ports[0].FlowDirection or "NOTDEFINED"
core.connect_port(tool.Ifc, *closest_ports, direction=direction)
bpy.ops.bim.regenerate_distribution_element()
return {"FINISHED"}
@@ -379,6 +426,55 @@ class SetFlowDirection(bpy.types.Operator, tool.Ifc.Operator):
return {"CANCELLED"}
class CycleFlowDirection(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.cycle_flow_direction"
bl_label = "Cycle Flow Direction"
bl_options = {"REGISTER", "UNDO"}
port_id: bpy.props.IntProperty()
@classmethod
def description(cls, context, operator):
port = tool.Ifc.get().by_id(operator.port_id)
if port and port.is_a("IfcDistributionPort"):
current_direction = port.FlowDirection or "NOTDEFINED"
return f"Current flow direction: {current_direction}. Click to cycle: SOURCE → SINK → SOURCEANDSINK → NOTDEFINED"
return "Cycle through flow directions: SOURCE → SINK → SOURCEANDSINK → NOTDEFINED → SOURCE..."
def _execute(self, context):
port = tool.Ifc.get().by_id(self.port_id)
if not port or not port.is_a("IfcDistributionPort"):
return {"CANCELLED"}
current_direction = port.FlowDirection or "NOTDEFINED"
flow_cycle_map = {
"SOURCE": "SINK",
"SINK": "SOURCEANDSINK",
"SOURCEANDSINK": "NOTDEFINED",
"NOTDEFINED": "SOURCE",
}
next_direction = flow_cycle_map.get(current_direction, "SOURCE")
tool.Ifc.run("attribute.edit_attributes", product=port, attributes={"FlowDirection": next_direction})
connected_port = tool.System.get_connected_port(port)
if connected_port:
connected_direction_map = {
"SOURCE": "SINK",
"SINK": "SOURCE",
"SOURCEANDSINK": "SOURCEANDSINK",
"NOTDEFINED": "NOTDEFINED",
}
connected_direction = connected_direction_map.get(next_direction, "NOTDEFINED")
tool.Ifc.run(
"attribute.edit_attributes", product=connected_port, attributes={"FlowDirection": connected_direction}
)
PortData.is_loaded = False
return {"FINISHED"}
class LoadZones(bpy.types.Operator):
bl_idname = "bim.load_zones"
bl_label = "Load Zones"
@@ -92,6 +92,28 @@ def toggle_decorations(self: "BIMSystemProperties", context: bpy.types.Context)
decorator.SystemDecorator.uninstall()
def get_available_ports_for_connection(
self: "BIMSystemProperties", context: bpy.types.Context
) -> list[tuple[str, str, str]]:
items = []
active_object_ports = set(tool.System.get_ports(tool.Ifc.get_entity(context.active_object)))
ifc_file = tool.Ifc.get()
for ifc_port in ifc_file.by_type("IfcDistributionPort"):
port = tool.Ifc.get_object(ifc_port)
if not port:
continue
if tool.System.get_connected_port(ifc_port) is not None or ifc_port in active_object_ports:
continue
port_object = tool.Ifc.get_object(tool.System.get_port_relating_element(ifc_port))
suggestion = f"{port_object.name} > {port.name}"
items.append((port.name, suggestion, ""))
return items if items else [("NONE", "Ports are hidden or not available", "")]
class BIMSystemProperties(PropertyGroup):
system_attributes: CollectionProperty(name="System Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
@@ -107,6 +129,11 @@ class BIMSystemProperties(PropertyGroup):
should_draw_decorations: BoolProperty(
name="Should Draw Decorations", description="Toggle system decorations", update=toggle_decorations
)
related_port: EnumProperty(
name="Connect To Port",
description="Select a port to connect to",
items=get_available_ports_for_connection,
)
if TYPE_CHECKING:
system_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
@@ -119,6 +146,7 @@ class BIMSystemProperties(PropertyGroup):
edited_system_id: int
system_class: str
should_draw_decorations: bool
related_port: str
@property
def active_system_ui_item(self) -> Union[System, None]:
+119 -68
View File
@@ -30,10 +30,10 @@ if TYPE_CHECKING:
FLOW_DIRECTION_TO_ICON = {
"SOURCE": "FORWARD",
"SINK": "BACK",
"SOURCEANDSINK": "ARROW_LEFTRIGHT",
"NOTDEFINED": "CHECKBOX_DEHLT",
"SOURCE": "FULLSCREEN_ENTER",
"SINK": "FULLSCREEN_EXIT",
"SOURCEANDSINK": "CHECKBOX_DEHLT",
"NOTDEFINED": "QUESTION",
}
@@ -163,44 +163,87 @@ class BIM_PT_ports(Panel):
return
row = self.layout.row(align=True)
row.label(text="Change Flow Direction:")
current_flow_direction = PortData.data["selected_objects_flow_direction"]
for flow_direction in FLOW_DIRECTION_TO_ICON.keys():
row.operator(
"bim.set_flow_direction",
icon=FLOW_DIRECTION_TO_ICON[flow_direction],
depress=flow_direction == current_flow_direction,
text="",
).direction = flow_direction
row.enabled = len(context.selected_objects) == 2
row.label(text=f"Ports located in: {context.active_object.name} and connected Port/Objects:")
row = self.layout.row(align=True)
row.label(text="Ports located on object and connected objects:")
row = self.layout.row(align=True)
cols = [row.column(align=True) for i in range(6)]
cols = [row.column(align=True) for i in range(9)]
cols[3].scale_x = 1.0
cols[6].scale_x = 1.0
cols[8].scale_x = 1.33
for port_data in PortData.data["located_ports_data"]:
flow_direction_icon = FLOW_DIRECTION_TO_ICON[port_data["FlowDirection"] or "NOTDEFINED"]
if port_data["port_obj_name"]:
cols[0].label(text="", icon=flow_direction_icon)
cols[1].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = port_data["id"]
cols[2].label(text=port_data["port_obj_name"])
if port_data["connected_obj_name"]:
cols[0].operator("bim.disconnect_port", text="", icon="UNLINKED").element_id = port_data["id"]
op = cols[1].operator("bim.cycle_flow_direction", text="", icon=flow_direction_icon, emboss=True)
op.port_id = port_data["id"]
else:
cols[0].label(text="", icon=flow_direction_icon)
cols[1].label(text="", icon="HIDE_ON")
cols[2].label(text="Port is hidden")
op = cols[0].operator("bim.add_related_port_connection", text="", icon="PLUGIN")
op.relating_port_id = port_data["id"]
op = cols[1].operator("bim.cycle_flow_direction", text="", icon=flow_direction_icon, emboss=True)
op.port_id = port_data["id"]
if port_data["port_obj_name"]:
cols[2].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = port_data["id"]
cols[3].label(text=port_data["port_obj_name"])
else:
cols[2].label(text="", icon="HIDE_ON")
cols[3].label(text="Port is hidden")
if port_data["connected_obj_name"]:
connected_obj = bpy.data.objects[port_data["connected_obj_name"]]
cols[3].operator("bim.disconnect_port", text="", icon="UNLINKED").element_id = port_data["id"]
ifc_id = tool.Blender.get_ifc_definition_id(connected_obj)
cols[4].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = ifc_id
cols[5].label(text=port_data["connected_obj_name"])
port = tool.Ifc.get().by_id(port_data["id"])
connected_port = tool.System.get_connected_port(port)
if connected_port:
connected_port_obj = tool.Ifc.get_object(connected_port)
if connected_port_obj:
connected_port_flow_dir = FLOW_DIRECTION_TO_ICON[connected_port.FlowDirection or "NOTDEFINED"]
op = cols[4].operator(
"bim.cycle_flow_direction", text="", icon=connected_port_flow_dir, emboss=True
)
op.port_id = connected_port.id()
cols[5].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = (
connected_port.id()
)
cols[6].label(text=connected_port_obj.name)
else:
cols[3].label(text="", icon="UNLINKED")
cols[4].label(text="", icon="BLANK1")
cols[5].label(text="Port is disconnected")
blank4 = cols[4].column(align=True)
blank4.scale_x = 0.1
blank4.label(text="", icon="BLANK1")
blank5 = cols[5].column(align=True)
blank5.scale_x = 0.1
blank5.label(text="", icon="BLANK1")
cols[6].label(text="Port is hidden")
else:
blank4 = cols[4].column(align=True)
blank4.scale_x = 0.1
blank4.label(text="", icon="BLANK1")
blank5 = cols[5].column(align=True)
blank5.scale_x = 0.1
blank5.label(text="", icon="BLANK1")
cols[6].label(text="")
ifc_id = tool.Blender.get_ifc_definition_id(connected_obj)
cols[7].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = ifc_id
cols[8].label(text=port_data["connected_obj_name"])
else:
blank4 = cols[4].column(align=True)
blank4.scale_x = 0.1
blank4.label(text="", icon="BLANK1")
blank5 = cols[5].column(align=True)
blank5.scale_x = 0.1
blank5.label(text="", icon="BLANK1")
cols[6].label(text="Port is disconnected")
blank7 = cols[7].column(align=True)
blank7.scale_x = 0.1
blank7.label(text="", icon="BLANK1")
blank8 = cols[8].column(align=True)
blank8.scale_x = 0.1
blank8.label(text="", icon="BLANK1")
class BIM_PT_port(Panel):
@@ -223,14 +266,8 @@ class BIM_PT_port(Panel):
return True
def draw(self, context):
self.props = tool.System.get_system_props()
layout = self.layout
row = layout.row(align=True)
row.label(text="IfcDistributionPort")
row.operator("bim.connect_port", icon="PLUGIN", text="")
row.operator("bim.disconnect_port", icon="UNLINKED", text="")
row.operator("bim.remove_port", icon="X", text="")
if not PortData.is_loaded:
PortData.load()
@@ -238,42 +275,56 @@ class BIM_PT_port(Panel):
if not PortData.data["is_port"]:
return
element = tool.Ifc.get_entity(context.active_object)
current_flow_direction = str(element.FlowDirection)
row = layout.row(align=True)
row.label(text="Flow Direction:")
row.label(text=current_flow_direction)
# port located on
row = layout.row(align=True)
relating_object_name = PortData.data["port_relating_object_name"]
relating_object = bpy.data.objects[relating_object_name]
row.label(text="Port located on:")
row.label(text=relating_object_name)
ifc_id = tool.Blender.get_ifc_definition_id(relating_object)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = ifc_id
row.label(text=f"IfcDistributionPort located in: {relating_object_name}")
row.operator("bim.remove_port", icon="X", text="")
element = tool.Ifc.get_entity(context.active_object)
# object connected to the port
row = layout.row(align=True)
cols = [row.column(align=True) for i in range(9)]
cols[3].scale_x = 1.0
cols[6].scale_x = 1.0
cols[8].scale_x = 1.33
flow_direction_icon = FLOW_DIRECTION_TO_ICON[element.FlowDirection or "NOTDEFINED"]
connected_port = tool.System.get_connected_port(element)
if connected_port:
cols[0].operator("bim.disconnect_port", text="", icon="UNLINKED")
op = cols[1].operator("bim.cycle_flow_direction", text="", icon=flow_direction_icon, emboss=True)
op.port_id = element.id()
else:
cols[0].operator("bim.connect_port", icon="PLUGIN", text="")
op = cols[1].operator("bim.cycle_flow_direction", text="", icon=flow_direction_icon, emboss=True)
op.port_id = element.id()
cols[2].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = element.id()
cols[3].label(text=context.active_object.name)
if connected_port:
connected_port_flow_dir = FLOW_DIRECTION_TO_ICON[connected_port.FlowDirection or "NOTDEFINED"]
op = cols[4].operator("bim.cycle_flow_direction", text="", icon=connected_port_flow_dir, emboss=True)
op.port_id = connected_port.id()
cols[5].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = connected_port.id()
connected_port_obj = tool.Ifc.get_object(connected_port)
cols[6].label(text=connected_port_obj.name if connected_port_obj else "Hidden Port")
connected_object_name = PortData.data["port_connected_object_name"]
if connected_object_name:
connected_object = bpy.data.objects[connected_object_name]
row.label(text="Port connected to:")
row.label(text=connected_object_name)
ifc_id = tool.Blender.get_ifc_definition_id(connected_object)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = ifc_id
connected_obj = bpy.data.objects[connected_object_name]
ifc_id = tool.Blender.get_ifc_definition_id(connected_obj)
cols[7].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = ifc_id
cols[8].label(text=connected_object_name)
else:
row.label(text="Port is not connected to any element")
row = layout.row(align=True)
row.label(text="Change Flow Direction:")
for flow_direction in FLOW_DIRECTION_TO_ICON.keys():
row.operator(
"bim.set_flow_direction",
icon=FLOW_DIRECTION_TO_ICON[flow_direction],
depress=flow_direction == current_flow_direction,
text="",
).direction = flow_direction
cols[7].label(text="", icon="BLANK1")
cols[8].label(text="")
else:
cols[4].label(text="", icon="BLANK1")
cols[5].label(text="", icon="BLANK1")
cols[6].label(text="Port is disconnected")
cols[7].label(text="", icon="BLANK1")
cols[8].label(text="")
class BIM_PT_flow_controls(Panel):
+9 -5
View File
@@ -123,9 +123,8 @@ def hide_ports(ifc: type[tool.Ifc], system: type[tool.System], element: ifcopens
def add_port(ifc: type[tool.Ifc], system: type[tool.System], element: ifcopenshell.entity_instance) -> None:
system.load_ports(element, system.get_ports(element))
obj = system.create_empty_at_cursor_with_element_orientation(element)
port = system.run_root_assign_class(obj=obj, ifc_class="IfcDistributionPort", should_add_representation=False)
ifc.run("system.assign_port", element=element, port=port)
port = system.create_port_at_cursor(element)
system.load_ports(element, [port])
def remove_port(ifc: type[tool.Ifc], system: type[tool.System], port: ifcopenshell.entity_instance) -> None:
@@ -133,8 +132,13 @@ def remove_port(ifc: type[tool.Ifc], system: type[tool.System], port: ifcopenshe
ifc.run("root.remove_product", product=port)
def connect_port(ifc: type[tool.Ifc], port1: ifcopenshell.entity_instance, port2: ifcopenshell.entity_instance) -> None:
ifc.run("system.connect_port", port1=port1, port2=port2)
def connect_port(
ifc: type[tool.Ifc],
port1: ifcopenshell.entity_instance,
port2: ifcopenshell.entity_instance,
direction: str = "NOTDEFINED",
) -> None:
ifc.run("system.connect_port", port1=port1, port2=port2, direction=direction)
def disconnect_port(ifc: type[tool.Ifc], port: ifcopenshell.entity_instance) -> None:
+1
View File
@@ -1082,6 +1082,7 @@ class Surveyor:
@interface
class System:
def create_empty_at_cursor_with_element_orientation(cls, element): pass
def create_port_at_cursor(cls, system): pass
def delete_element_objects(cls, elements): pass
def disable_editing_system(cls): pass
def disable_system_editing_ui(cls): pass
+32
View File
@@ -100,6 +100,7 @@ class System(bonsai.core.tool.System):
@classmethod
def create_empty_at_cursor_with_element_orientation(cls, element: ifcopenshell.entity_instance) -> bpy.types.Object:
# Is this necessary anymore?
element_obj = tool.Ifc.get_object(element)
obj = bpy.data.objects.new("Port", None)
obj.matrix_world = element_obj.matrix_world
@@ -107,6 +108,26 @@ class System(bonsai.core.tool.System):
bpy.context.scene.collection.objects.link(obj)
return obj
@classmethod
def create_port_at_cursor(cls, element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
ifc_file = tool.Ifc.get()
element_obj = tool.Ifc.get_object(element)
port = ifcopenshell.api.system.add_port(ifc_file, element=element)
port.FlowDirection = "NOTDEFINED"
port.PredefinedType = "USERDEFINED"
systems = ifcopenshell.util.system.get_element_systems(element)
system = systems[0] if systems else None
port.SystemType = getattr(system, "PredefinedType", None) or "USERDEFINED"
matrix = element_obj.matrix_world.copy()
matrix.translation = bpy.context.scene.cursor.matrix.translation
ifcopenshell.api.geometry.edit_object_placement(ifc_file, product=port, matrix=matrix, is_si=True)
return port
@classmethod
def delete_element_objects(cls, elements: list[ifcopenshell.entity_instance]) -> None:
for element in elements:
@@ -192,6 +213,17 @@ class System(bonsai.core.tool.System):
ifc_importer.process_context_filter()
ifc_importer.create_generic_elements(set(ports_to_create))
if element.is_a("IfcTypeProduct"):
target_collection = None
for collection in obj.users_collection:
target_collection = collection
break
if target_collection:
for port_obj in ifc_importer.added_data.values():
if isinstance(port_obj, bpy.types.Object):
tool.Collector.link_collection_object_safe(target_collection, port_obj)
else:
container = ifcopenshell.util.element.get_container(element)
if container:
collection = tool.Blender.get_object_bim_props(tool.Ifc.get_object(container)).collection
+4 -5
View File
@@ -153,11 +153,10 @@ class TestAddPort:
def test_run(self, ifc, system):
system.get_ports("element").should_be_called().will_return(["port"])
system.load_ports("element", ["port"]).should_be_called()
system.create_empty_at_cursor_with_element_orientation("element").should_be_called().will_return("obj")
system.run_root_assign_class(
obj="obj", ifc_class="IfcDistributionPort", should_add_representation=False
).should_be_called().will_return("port")
ifc.run("system.assign_port", element="element", port="port").should_be_called()
#system.create_empty_at_cursor_with_element_orientation("element").should_be_called().will_return("obj")
#system.run_root_assign_class(obj="obj", ifc_class="IfcDistributionPort", should_add_representation=False).should_be_called().will_return("port")
system.create_port_at_cursor("element").should_be_called().will_return("port")
system.load_ports("element", ["port"]).should_be_called()
subject.add_port(ifc, system, element="element")
+13
View File
@@ -103,6 +103,19 @@ class TestCreateEmptyAtCursorWithElementOrientation(NewFile):
obj = subject.create_empty_at_cursor_with_element_orientation(element)
assert obj.matrix_world == bpy.context.scene.cursor.matrix
class TestCreatePortAtCursor(NewFile):
def test_run(self):
assert bpy.context.scene
ifc = ifcopenshell.file()
tool.Ifc().set(ifc)
system = ifcopenshell.api.system.add_system(ifc)
element = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcDuctSegment")
ifcopenshell.api.system.assign_system(ifc, products=[element], system=system)
obj = tool.Ifc.link(element, bpy.data.objects.new("Object", None))
port = subject.create_port_at_cursor(element)
assert port.is_a("IfcDistributionPort")
assert ifcopenshell.util.system.get_ports(element) == [port]
class TestDeleteElementObjects(NewFile):
def test_run(self):
@@ -381,6 +381,7 @@ class Usecase:
def append_type_product(self):
self.whitelisted_inverse_attributes = {
"IfcObjectDefinition": ["HasAssociations"],
"IfcDistributionElementType": ["IsNestedBy"],
self.base_material_class: ["HasExternalReferences", "HasProperties", "HasRepresentation"],
"IfcRepresentationItem": ["StyledByItem", "LayerAssignment"],
"IfcRepresentation": ["LayerAssignments"],
@@ -397,6 +398,7 @@ class Usecase:
"IfcObjectDefinition": ["HasAssociations"],
"IfcObject": ["IsDefinedBy.IfcRelDefinesByProperties"],
"IfcElement": ["HasOpenings"],
"IfcDistributionElement": ["IsNestedBy"],
self.base_material_class: ["HasExternalReferences", "HasProperties", "HasRepresentation"],
"IfcRepresentationItem": [
"StyledByItem",
@@ -569,6 +571,8 @@ class Usecase:
return False
elif element.is_a("IfcRoot") and self.by_guid(element.GlobalId) is not None:
return False
elif element.is_a("IfcDistributionPort"):
return False
elif element.is_a(self.target_class):
return True
elif self.target_class == "IfcProduct" and element.is_a("IfcTypeProduct"):
@@ -65,8 +65,6 @@ def disconnect_port(file: ifcopenshell.file, port: ifcopenshell.entity_instance)
rels += port.ConnectedFrom or ()
for rel in rels:
rel.RelatingPort.FlowDirection = None
rel.RelatedPort.FlowDirection = None
history = rel.OwnerHistory
file.remove(rel)
if history: