First Commit

This commit is contained in:
2026-05-31 10:17:09 +07:00
commit 17a9c69379
4547 changed files with 1170384 additions and 0 deletions
@@ -0,0 +1,56 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
"""Manage distribution systems and port connectivity
Service distribution systems (mechanical, electrical, hydraulic, fire,
logistical, etc) consist of connected distribution segments, fittings,
terminals, control equipment, and more. This module handles port connectivity
and relationships describing distribution flow.
"""
from .. import wrap_usecases
from .add_port import add_port
from .add_system import add_system
from .assign_flow_control import assign_flow_control
from .assign_port import assign_port
from .assign_system import assign_system
from .connect_port import connect_port
from .disconnect_port import disconnect_port
from .edit_system import edit_system
from .remove_system import remove_system
from .unassign_flow_control import unassign_flow_control
from .unassign_port import unassign_port
from .unassign_system import unassign_system
wrap_usecases(__path__, __name__)
__all__ = [
"add_port",
"add_system",
"assign_flow_control",
"assign_port",
"assign_system",
"connect_port",
"disconnect_port",
"edit_system",
"remove_system",
"unassign_flow_control",
"unassign_port",
"unassign_system",
]
@@ -0,0 +1,59 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from typing import Optional
import ifcopenshell
import ifcopenshell.api.root
import ifcopenshell.api.system
def add_port(
file: ifcopenshell.file, element: Optional[ifcopenshell.entity_instance] = None
) -> ifcopenshell.entity_instance:
"""Adds a new distribution port to an element
A distribution port represents a connection point on an element, where
a distribution element may be connected to another distribution element.
For example, a duct segment will typically have two ports, one at either
end, because you can attach another segment or fitting to either end of
the duct segment.
This will both add a distribution port and automatically assign it to a
distribution element.
:param element: The IfcDistributionElement you want to add a
distribution port to.
:return: The newly created IfcDistributionPort
Example:
.. code:: python
# Create a duct
duct = ifcopenshell.api.root.create_entity(model,
ifc_class="IfcDuctSegment", predefined_type="RIGIDSEGMENT")
# Create 2 ports, one for either end.
port1 = ifcopenshell.api.system.add_port(model, element=duct)
port2 = ifcopenshell.api.system.add_port(model, element=duct)
"""
port = ifcopenshell.api.root.create_entity(file, ifc_class="IfcDistributionPort")
if element:
ifcopenshell.api.system.assign_port(file, element=element, port=port)
return port
@@ -0,0 +1,58 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.owner
import ifcopenshell.guid
def add_system(file: ifcopenshell.file, ifc_class: str = "IfcDistributionSystem") -> ifcopenshell.entity_instance:
"""Add a new distribution system
A distribution system is a group of distribution elements, like ducts,
pipes, pumps, filters, fans, and so on that distribute a medium (air,
liquid, or electricity) throughout a facility. Systems may be
hierarchical, with larger systems composed of smaller subsystems.
:param ifc_class: The type of system, chosen from IfcDistributionSystem
for mechanical, electrical, communications, plumbing, fire, or
security systems. Alternatively you may choose IfcBuildingSystem for
specialised building facade systems or similar. For IFC2X3, choose
IfcSystem.
:return: The newly created IfcSystem.
Example:
.. code:: python
# A completely empty distribution system
system = ifcopenshell.api.system.add_system(model)
"""
ifc_class = ifc_class
# workaround for failing default argument in ifc2x3
if file.schema == "IFC2X3" and ifc_class == "IfcDistributionSystem":
ifc_class = "IfcSystem"
return file.create_entity(
ifc_class,
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"Name": "Unnamed",
}
)
@@ -0,0 +1,80 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from typing import Union
import ifcopenshell
import ifcopenshell.api.owner
import ifcopenshell.guid
def assign_flow_control(
file: ifcopenshell.file,
relating_flow_element: ifcopenshell.entity_instance,
related_flow_control: ifcopenshell.entity_instance,
) -> Union[ifcopenshell.entity_instance, None]:
"""Assigns to the flow element control element that either sense or control
some aspect of the flow element.
Note that control can be assigned only to the one flow element.
:param related_flow_control: IfcDistributionControlElement
which may be used to impart control on the flow element
:param relating_flow_element: The IfcDistributionFlowElement that is being controlled / sensed
:return: Matching or newly created IfcRelFlowControlElements. If control
is already assigned to some other element method will return None.
Example:
.. code:: python
flow_element = model.createIfcFlowSegment()
flow_control = model.createIfcController()
relation = ifcopenshell.api.system.assign_flow_control(
model, related_flow_control=flow_control, relating_flow_element=flow_element
)
"""
if related_flow_control.AssignedToFlowElement:
# only 1 control per 1 flow element is possible
assignment = related_flow_control.AssignedToFlowElement[0]
if assignment.RelatingFlowElement == relating_flow_element:
return assignment
# return None if this control is already assigned to another flow element
return
if relating_flow_element.HasControlElements:
assignment = relating_flow_element.HasControlElements[0]
if related_flow_control in assignment.RelatedControlElements:
return assignment
related_flow_controls = set(assignment.RelatedControlElements)
related_flow_controls.add(related_flow_control)
assignment.RelatedControlElements = list(related_flow_controls)
ifcopenshell.api.owner.update_owner_history(file, element=assignment)
return assignment
assignment = file.create_entity(
"IfcRelFlowControlElements",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedControlElements": [related_flow_control],
"RelatingFlowElement": relating_flow_element,
},
)
return assignment
@@ -0,0 +1,122 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.geometry
import ifcopenshell.api.owner
import ifcopenshell.guid
import ifcopenshell.util.placement
def assign_port(
file: ifcopenshell.file, element: ifcopenshell.entity_instance, port: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
"""Assigns a port to an element
If you have an orphaned port, you may assign it to a distribution
element using this function. Ports should typically not be orphaned, but
it may be useful when patching up models.
:param element: The IfcDistributionElement to assign the port to.
:param port: The IfcDistributionPort you want to assign.
:return: The IfcRelNests relationship, or the
IfcRelConnectsPortToElement for IFC2X3.
Example:
.. code:: python
# Create a duct
duct = ifcopenshell.api.root.create_entity(model,
ifc_class="IfcDuctSegment", predefined_type="RIGIDSEGMENT")
# Create 2 ports, one for either end.
port1 = ifcopenshell.api.system.add_port(model, element=duct)
port2 = ifcopenshell.api.system.add_port(model, element=duct)
# Unassign one port for some weird reason.
ifcopenshell.api.system.unassign_port(model, element=duct, port=port1)
# Reassign it back
ifcopenshell.api.system.assign_port(model, element=duct, port=port1)
"""
usecase = Usecase()
usecase.file = file
return usecase.execute(element, port)
class Usecase:
file: ifcopenshell.file
def execute(
self, element: ifcopenshell.entity_instance, port: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
self.element = element
self.port = port
if self.file.schema == "IFC2X3":
return self.execute_ifc2x3()
rels = self.element.IsNestedBy or []
for rel in rels:
if self.port in rel.RelatedObjects:
return rel
if rels:
rel = rels[0]
related_objects = set(rel.RelatedObjects) or set()
related_objects.add(self.port)
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.owner.update_owner_history(self.file, element=rel)
else:
rel = self.file.create_entity(
"IfcRelNests",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.owner.create_owner_history(self.file),
RelatedObjects=[self.port],
RelatingObject=self.element,
)
self.update_port_placement()
return rel
def execute_ifc2x3(self) -> ifcopenshell.entity_instance:
for rel in self.element.HasPorts or []:
if rel.RelatingPort == self.port:
return rel
rel = self.file.create_entity(
"IfcRelConnectsPortToElement",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.owner.create_owner_history(self.file),
RelatingPort=self.port,
RelatedElement=self.element,
)
self.update_port_placement()
return rel
def update_port_placement(self) -> None:
placement = getattr(self.port, "ObjectPlacement", None)
if placement and placement.is_a("IfcLocalPlacement"):
ifcopenshell.api.geometry.edit_object_placement(
self.file,
product=self.port,
matrix=ifcopenshell.util.placement.get_local_placement(self.port.ObjectPlacement),
is_si=False,
)
@@ -0,0 +1,57 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from typing import Union
import ifcopenshell
import ifcopenshell.api.group
import ifcopenshell.util.system
def assign_system(
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
system: ifcopenshell.entity_instance,
) -> Union[ifcopenshell.entity_instance, None]:
"""Assigns distribution elements to a system
Note that it is not necessary to assign distribution ports to a system.
:param products: The list of IfcDistributionElements to assign to the system.
:param system: The IfcSystem you want to assign the element to.
:return: The IfcRelAssignsToGroup relationship
or `None` if `products` was empty list.
Example:
.. code:: python
# A completely empty distribution system
system = ifcopenshell.api.system.add_system(model)
# Create a duct
duct = ifcopenshell.api.root.create_entity(model,
ifc_class="IfcDuctSegment", predefined_type="RIGIDSEGMENT")
# This duct is part of the system
ifcopenshell.api.system.assign_system(model, products=[duct], system=system)
"""
if not all(ifcopenshell.util.system.is_assignable(failed_product := product, system) for product in products):
raise TypeError(f"You cannot assign an {failed_product.is_a()} to an {system.is_a()}")
return ifcopenshell.api.group.assign_group(file, products=products, group=system)
@@ -0,0 +1,218 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from typing import Any, Optional
import ifcopenshell
import ifcopenshell.api.owner
import ifcopenshell.guid
import ifcopenshell.util.element
def connect_port(
file: ifcopenshell.file,
port1: ifcopenshell.entity_instance,
port2: ifcopenshell.entity_instance,
direction: str = "NOTDEFINED",
element: Optional[ifcopenshell.entity_instance] = None,
) -> None:
"""Connects two ports together
A distribution element (e.g. a duct) may be connected to another
distribution element (e.g. a fitting) by connecting a port at one of the
duct to a port at the same end of the fitting.
Ports may only have one connection, so you cannot have multiple things
connected to the same port. Nor can you have incompatible port
connections, such as an electrical port connected to an airflow port.
Port connectivity may be explicit or implicit. Explicit connections are
where the port connectivity is described for every single distribution
element in detail. For example, a duct segment would have port
connections to a duct fitting, which would have port connections to
another duct segment, all the way from a fan to an air terminal exactly
as constructed on site. Implicit connections only consider the key
distribution control elements (e.g. the fan and the terminal) and ignore
all of the details of the duct segments and fittings in between.
Generally, explicit connectivity is preferred for later detailed design,
and implicit connectivity is preferred for early phase design.
:param port1: The port of the first distribution element to connect.
:param port2: The port of the second distribution element to connect.
:param direction: The directionality of distribution flow through the
port connection. NOTDEFINED means that the direction has not yet
been determined. This is useful during preliminary system design.
SOURCE means that the flow is from the first element to the second
element. SINK means that the flow is from the second element to the
first element. SOURCEANDSINK means that flow is bi-directional
between the first and second element. SOURCEANDSINK is a relatively
rare scenario.
:param element: Optionally set an element through which the port
connectivity is made, such as a segment or fitting. This is only to
be used for implicit port connectivity where the segments and
fittings are less important.
Example:
.. code:: python
# A completely empty distribution system
system = ifcopenshell.api.system.add_system(model)
# Create a duct and a 90 degree bend fitting
duct = ifcopenshell.api.root.create_entity(model,
ifc_class="IfcDuctSegment", predefined_type="RIGIDSEGMENT")
fitting = ifcopenshell.api.root.create_entity(model,
ifc_class="IfcDuctFitting", predefined_type="BEND")
# The duct and fitting is part of the system
ifcopenshell.api.system.assign_system(model, products=[duct], system=system)
ifcopenshell.api.system.assign_system(model, products=[fitting], system=system)
# Create 2 ports, one for either end of both the duct and fitting.
duct_port1 = ifcopenshell.api.system.add_port(model, element=duct)
duct_port2 = ifcopenshell.api.system.add_port(model, element=duct)
fitting_port1 = ifcopenshell.api.system.add_port(model, element=fitting)
fitting_port2 = ifcopenshell.api.system.add_port(model, element=fitting)
# Connect the duct and fitting together. At this point, we have not
# yet determined the direction of the flow, so we leave direction as
# NOTDEFINED.
ifcopenshell.api.system.connect_port(model, port1=duct_port2, port2=fitting_port1)
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {
"port1": port1,
"port2": port2,
"direction": direction,
"element": element,
}
return usecase.execute()
class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self):
# Note: there are a number of ambiguities with port connectivity. We
# assume system topology is represented by a directed graph. In other
# words, SOURCEANDSINK and NOTDEFINED implies a two way connection, with
# two IfcRelConnectsPorts. SOURCE or SINK by itself implies a one way
# connection. NOTDEFINED semantically implies that although you may
# traverse the graph either direction, the direction has not been
# determined yet by the engineer. None is not allowed as a direction as
# we assume None means that no connection is made.
if self.settings["port1"] == self.settings["port2"]:
return
self.purge_existing_connections_to_other_ports()
if self.settings["direction"] == "SOURCE":
self.settings["port1"].FlowDirection = "SOURCE"
self.settings["port2"].FlowDirection = "SINK"
elif self.settings["direction"] == "SINK":
self.settings["port1"].FlowDirection = "SINK"
self.settings["port2"].FlowDirection = "SOURCE"
else:
self.settings["port1"].FlowDirection = self.settings["direction"]
self.settings["port2"].FlowDirection = self.settings["direction"]
if self.settings["direction"] in ["SOURCE", "SOURCEANDSINK", "NOTDEFINED"]:
self.set_connected_to()
else:
self.purge_connected_to()
if self.settings["direction"] in ["SINK", "SOURCEANDSINK", "NOTDEFINED"]:
self.set_connected_from()
else:
self.purge_connected_from()
self.set_realising_element()
def purge_existing_connections_to_other_ports(self):
for rel in self.settings["port1"].ConnectedTo or []:
if rel.RelatedPort != self.settings["port2"]:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
for rel in self.settings["port1"].ConnectedFrom or []:
if rel.RelatingPort != self.settings["port2"]:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
for rel in self.settings["port2"].ConnectedTo or []:
if rel.RelatedPort != self.settings["port1"]:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
for rel in self.settings["port2"].ConnectedFrom or []:
if rel.RelatingPort != self.settings["port1"]:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
def set_connected_to(self):
if self.settings["port1"].ConnectedTo:
return
self.file.create_entity(
"IfcRelConnectsPorts",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.owner.create_owner_history(self.file),
RelatingPort=self.settings["port1"],
RelatedPort=self.settings["port2"],
)
def set_connected_from(self):
if self.settings["port1"].ConnectedFrom:
return
self.file.create_entity(
"IfcRelConnectsPorts",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.owner.create_owner_history(self.file),
RelatingPort=self.settings["port2"],
RelatedPort=self.settings["port1"],
)
def purge_connected_to(self):
for rel in self.settings["port1"].ConnectedTo or []:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
def purge_connected_from(self):
for rel in self.settings["port1"].ConnectedFrom or []:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
def set_realising_element(self):
for rel in self.settings["port1"].ConnectedTo or []:
rel.RealizingElement = self.settings["element"]
for rel in self.settings["port1"].ConnectedFrom or []:
rel.RealizingElement = self.settings["element"]
@@ -0,0 +1,73 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.element
def disconnect_port(file: ifcopenshell.file, port: ifcopenshell.entity_instance) -> None:
"""Disconnects a port from any other port
A port may only be connected to one other port, so the other port is not
needed to be specified.
:param port: The IfcDistributionPort to disconnect.
:return: None
Example:
.. code:: python
# A completely empty distribution system
system = ifcopenshell.api.system.add_system(model)
# Create a duct and a 90 degree bend fitting
duct = ifcopenshell.api.root.create_entity(model,
ifc_class="IfcDuctSegment", predefined_type="RIGIDSEGMENT")
fitting = ifcopenshell.api.root.create_entity(model,
ifc_class="IfcDuctFitting", predefined_type="BEND")
# The duct and fitting is part of the system
ifcopenshell.api.system.assign_system(model, products=[duct], system=system)
ifcopenshell.api.system.assign_system(model, products=[fitting], system=system)
# Create 2 ports, one for either end of both the duct and fitting.
duct_port1 = ifcopenshell.api.system.add_port(model, element=duct)
duct_port2 = ifcopenshell.api.system.add_port(model, element=duct)
fitting_port1 = ifcopenshell.api.system.add_port(model, element=fitting)
fitting_port2 = ifcopenshell.api.system.add_port(model, element=fitting)
# Connect the duct and fitting together. At this point, we have not
# yet determined the direction of the flow, so we leave direction as
# NOTDEFINED.
ifcopenshell.api.system.connect_port(model, port1=duct_port2, port2=fitting_port1)
# Disconnect the port. note we could've equally disconnected
# fitting_port1 instead of duct_port2
ifcopenshell.api.system.disconnect_port(model, port=duct_port2)
"""
rels = port.ConnectedTo or ()
rels += port.ConnectedFrom or ()
for rel in rels:
rel.RelatingPort.FlowDirection = None
rel.RelatedPort.FlowDirection = None
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -0,0 +1,44 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from typing import Any
import ifcopenshell
def edit_system(file: ifcopenshell.file, system: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
"""Edits the attributes of an IfcSystem
For more information about the attributes and data types of an
IfcSystem, consult the IFC documentation.
:param system: The IfcSystem entity you want to edit
:param attributes: a dictionary of attribute names and values.
:return: None
Example:
.. code:: python
# A completely empty distribution system
system = ifcopenshell.api.system.add_system(model)
# Change the name of the system to "HW" for Hot Water
ifcopenshell.api.system.edit_system(model, system=system, attributes={"Name": "HW"})
"""
for name, value in attributes.items():
setattr(system, name, value)
@@ -0,0 +1,67 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.pset
import ifcopenshell.util.element
def remove_system(file: ifcopenshell.file, system: ifcopenshell.entity_instance) -> None:
"""Removes a distribution system
All the distribution elements within the system are retained.
:param system: The IfcSystem to remove.
:return: None
Example:
.. code:: python
# A completely empty distribution system
system = ifcopenshell.api.system.add_system(model)
# Delete it.
ifcopenshell.api.system.remove_system(model, system=system)
"""
for inverse_id in [i.id() for i in file.get_inverse(system)]:
try:
inverse = file.by_id(inverse_id)
except:
continue
if inverse.is_a("IfcRelDefinesByProperties"):
ifcopenshell.api.pset.remove_pset(
file,
product=system,
pset=inverse.RelatingPropertyDefinition,
)
elif inverse.is_a("IfcRelAssignsToGroup"):
if inverse.RelatingGroup == system:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
history = system.OwnerHistory
file.remove(system)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -0,0 +1,67 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.owner
import ifcopenshell.util.element
def unassign_flow_control(
file: ifcopenshell.file,
relating_flow_element: ifcopenshell.entity_instance,
related_flow_control: ifcopenshell.entity_instance,
) -> None:
"""Unassigns flow control element from the flow element.
:param related_flow_control: IfcDistributionControlElement controling the
flow element
:param relating_flow_element: The IfcDistributionFlowElement that is being controlled
:return: None
Example:
.. code:: python
# assign control to the flow element
flow_element = file.createIfcFlowSegment()
flow_control = file.createIfcController()
relation = ifcopenshell.api.system.assign_flow_control(
file, relating_control=flow_control, related_object=flow_element
)
# und unassign it
ifcopenshell.api.system.unassign_flow_control(file,
relating_control=flow_control, related_object=flow_element
)
"""
if not related_flow_control.AssignedToFlowElement:
return
assignment = related_flow_control.AssignedToFlowElement[0]
if assignment.RelatingFlowElement != relating_flow_element:
return
if len(assignment.RelatedControlElements) == 1:
history = assignment.OwnerHistory
file.remove(assignment)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
return
related_flow_controls = list(assignment.RelatedControlElements)
related_flow_controls.remove(related_flow_control)
assignment.RelatedControlElements = related_flow_controls
ifcopenshell.api.owner.update_owner_history(file, element=assignment)
@@ -0,0 +1,86 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.owner
import ifcopenshell.util.element
def unassign_port(
file: ifcopenshell.file, element: ifcopenshell.entity_instance, port: ifcopenshell.entity_instance
) -> None:
"""Unassigns a port to an element
Ports are typically always assigned to a distribution element, but in
some edge cases you may want to unassign the port to create an orphaned
port for cleaning or patchin purposes.
:param element: The IfcDistributionElement to unassign the port from.
:param port: The IfcDistributionPort you want to unassign.
:return: None
Example:
.. code:: python
# Create a duct
duct = ifcopenshell.api.root.create_entity(model,
ifc_class="IfcDuctSegment", predefined_type="RIGIDSEGMENT")
# Create 2 ports, one for either end.
port1 = ifcopenshell.api.system.add_port(model, element=duct)
port2 = ifcopenshell.api.system.add_port(model, element=duct)
# Unassign one port for some weird reason.
ifcopenshell.api.system.unassign_port(model, element=duct, port=port1)
"""
usecase = Usecase()
usecase.file = file
return usecase.execute(element, port)
class Usecase:
file: ifcopenshell.file
def execute(self, element: ifcopenshell.entity_instance, port: ifcopenshell.entity_instance) -> None:
if self.file.schema == "IFC2X3":
self.element = element
self.port = port
return self.execute_ifc2x3()
for rel in element.IsNestedBy or []:
if port in rel.RelatedObjects:
if len(rel.RelatedObjects) == 1:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
return
related_objects = set(rel.RelatedObjects) or set()
related_objects.remove(port)
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.owner.update_owner_history(self.file, element=rel)
def execute_ifc2x3(self) -> None:
for rel in self.element.HasPorts or []:
if rel.RelatingPort == self.port:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
return
@@ -0,0 +1,51 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.group
def unassign_system(
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
system: ifcopenshell.entity_instance,
) -> None:
"""Unassigns list of products from a system
:param products: The list of IfcDistributionElements to unassign from the system.
:param system: The IfcSystem you want to unassign the element from.
:return: None
Example:
.. code:: python
# A completely empty distribution system
system = ifcopenshell.api.system.add_system(model)
# Create a duct
duct = ifcopenshell.api.root.create_entity(model,
ifc_class="IfcDuctSegment", predefined_type="RIGIDSEGMENT")
# This duct is part of the system
ifcopenshell.api.system.assign_system(model, products=[duct], system=system)
# Not anymore!
ifcopenshell.api.system.unassign_system(model, products=[duct], system=system)
"""
ifcopenshell.api.group.unassign_group(file, products=products, group=system)