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,43 @@
# 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/>.
"""Nesting is when a component is attached to a host element
Examples include when a faucet is attached using a predrilled hole in a basin,
or when a modular connection occurs through a connection point. This implies
that when a host element moves, the child nested components must move as well.
Note that this API is not meant to be used for connection points on
distribution systems. For that purpose, such as for pipe fittings and
equipment, please see :mod:`ifcopenshell.api.system`.
"""
from .. import wrap_usecases
from .assign_object import assign_object
from .change_nest import change_nest
from .reorder_nesting import reorder_nesting
from .unassign_object import unassign_object
wrap_usecases(__path__, __name__)
__all__ = [
"assign_object",
"change_nest",
"reorder_nesting",
"unassign_object",
]
@@ -0,0 +1,181 @@
# 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.aggregate
import ifcopenshell.api.owner
import ifcopenshell.api.spatial
import ifcopenshell.guid
import ifcopenshell.util.element
def assign_object(
file: ifcopenshell.file,
related_objects: list[ifcopenshell.entity_instance],
relating_object: ifcopenshell.entity_instance,
) -> Union[ifcopenshell.entity_instance, None]:
"""Assigns objects as nested children to a parent host
All physical IFC model elements must be part of a hierarchical tree
called the "spatial decomposition", where large things are made up of
smaller things. This tree always begins at an "IfcProject" and is then
broken down using "decomposition" relationships, of which aggregation is
the first relationship you will use.
Another type of "decomposition" relationship is known as "nesting".
Nesting is used when an child object is physically attached to a parent
host object, through a physical predetermined connection point. The
child object must be specifically designed to attach to a other objects
at specific positions with a particular form factor. Examples include
faucets which must always be attached through a predrilled hole in a
basin. Alternatively, it could be a modular attachment with a
correlating male and female joint that must join at a particular point.
Because there is a strict connection point, when the parent moves, all
nested children must move with the parent. Another example might be a
predrilled hole in a door panel where hardware must fit through.
Nesting relationships are not very commonly used in most design and
construction models. Its main usecase is in modular construction, kit of
parts, or fabrication models.
As a product may only have a single location in the "spatial
decomposition" tree, assigning an nesting relationship will remove any
previous aggregation, containment, or nesting relationships it may have.
IFC placements follow a convention where the placement is relative to
its parent in the spatial hierarchy. If your product has a placement,
its placement will be recalculated to follow this convention.
For physical connections which are part of a distribution system, such
as a plug connecting into a GPO, or a duct connecting to an AHU, or two
pipe segments connecting with a bend, tee, or wye fitting, you should
not nest the two objects directly. Instead, you should nest a connection
port, which determines the type of compatible distribution flow that can
be connected to it. To do this, do not use this function, but instead
use the more specific functions in the ifcopenshell.api.system module.
Note that nesting relationships may also be used by non-physical
elements, such as cost items or tasks. In this context, nesting means
that there is an implied order to the child cost items or tasks (i.e.
task 1 should be shown before task 2). It is not necessary to use this
function for nesting non-physical elements. Instead, it is recommended
to instead just use the relevant API functions, like
ifcopenshell.api.cost.add_cost_item or
ifcopenshell.api.sequence.add_task.
:param related_objects: The list of children of the nesting relationship,
typically IfcElements.
:param relating_object: The host parent of the nesting relationship,
typically an IfcElement.
:return: The IfcRelNests relationship instance
or `None` if `related_objects` was empty list.
Example:
.. code:: python
# Faucets are designed to attach onto a sink through a predrilled hole.
sink = ifcopenshell.api.root.create_entity(model,
ifc_class="IfcSanitaryTerminal", predefined_type="SINK")
faucet = ifcopenshell.api.root.create_entity(model,
ifc_class="IfcValve", predefined_type="FAUCET")
ifcopenshell.api.nest.assign_object(model, related_objects=[faucet], relating_object=sink)
"""
settings = {"related_objects": related_objects, "relating_object": relating_object}
if not settings["related_objects"]:
return
ifc2x3 = file.schema == "IFC2X3"
related_objects_set = set(related_objects)
if ifc2x3:
is_nested_by = next((i for i in relating_object.IsDecomposedBy if i.is_a("IfcRelNests")), None)
else:
is_nested_by = next((i for i in relating_object.IsNestedBy), None)
# NOTE: maintain .RelatedObjects order as it has meaning in IFC
previous_nests_rels: set[ifcopenshell.entity_instance] = set()
objects_without_nests: list[ifcopenshell.entity_instance] = []
objects_with_nests: list[ifcopenshell.entity_instance] = []
# check if there is anything to change
for object in related_objects_set:
if ifc2x3:
object_rel = next((i for i in object.Decomposes if i.is_a("IfcRelNests")), None)
else:
object_rel = next(iter(object.Nests), None)
if object_rel is None:
objects_without_nests.append(object)
continue
# either is_nested_by is None or product is part of different rel
if object_rel != is_nested_by:
previous_nests_rels.add(object_rel)
objects_with_nests.append(object)
# products with already assigned nestings will be skipped
objects_to_change = objects_without_nests + objects_with_nests
# nothing to change
if not objects_to_change:
return is_nested_by
# Can be either only nested, aggregated, or contained at the same time.
possibly_contained = [o for o in objects_without_nests if hasattr(o, "ContainedInStructure")]
ifcopenshell.api.spatial.unassign_container(file, products=possibly_contained)
ifcopenshell.api.aggregate.unassign_object(file, products=objects_without_nests)
# unassign elements from previous nests
for nests in previous_nests_rels:
cur_related_objects = [o for o in nests.RelatedObjects if o not in related_objects_set]
if cur_related_objects:
nests.RelatedObjects = list(cur_related_objects)
ifcopenshell.api.owner.update_owner_history(file, element=nests)
else:
history = nests.OwnerHistory
file.remove(nests)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
# assign elements to a new nesting
if is_nested_by:
cur_related_objects = list(is_nested_by.RelatedObjects)
cur_related_objects_set = set(cur_related_objects)
is_nested_by.RelatedObjects = cur_related_objects + [
o for o in related_objects if o not in cur_related_objects_set
]
ifcopenshell.api.owner.update_owner_history(file, element=is_nested_by)
else:
is_nested_by = file.create_entity(
"IfcRelNests",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": related_objects,
"RelatingObject": relating_object,
}
)
# NOTE: Creating a nesting relationship doesn't localize the object's placement,
# unlike assigning it to an aggregate or a container.
return is_nested_by
@@ -0,0 +1,42 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021-2022 Dion Moult <dion@thinkmoult.com>, Yassine Oualid <yassine@sigmadimensions.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.nest
import ifcopenshell.api.owner
import ifcopenshell.util.element
def change_nest(
file: ifcopenshell.file, item: ifcopenshell.entity_instance, new_parent: ifcopenshell.entity_instance
) -> None:
"""Assigns a cost item to a new parent cost item"""
if not item.Nests:
return
nests = item.Nests[0]
related_objects = list(nests.RelatedObjects)
related_objects.remove(item)
if related_objects:
nests.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, element=nests)
else:
history = nests.OwnerHistory
file.remove(nests)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
ifcopenshell.api.nest.assign_object(file, related_objects=[item], relating_object=new_parent)
@@ -0,0 +1,32 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021-2022 Dion Moult <dion@thinkmoult.com>, Yassine Oualid <yassine@sigmadimensions.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
def reorder_nesting(
file: ifcopenshell.file, item: ifcopenshell.entity_instance, old_index: int = 0, new_index: int = 0
) -> None:
"""Reorders an item in a nesting set"""
if not item.Nests:
return
nesting_set = item.Nests[0]
if not old_index:
old_index = nesting_set.RelatedObjects.index(item)
items = list(getattr(nesting_set, "RelatedObjects") or [])
items.insert(new_index, items.pop(old_index))
setattr(nesting_set, "RelatedObjects", items)
@@ -0,0 +1,72 @@
# 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_object(file: ifcopenshell.file, related_objects: list[ifcopenshell.entity_instance]) -> None:
"""Unassigns related_objects from their nests.
An object (the whole within a decomposition) is Nested by zero or one more smaller objects.
This function will remove this nesting relationship.
If the object is not part of a nesting relationship, nothing will happen.
:param related_objects: The list of children of the nesting relationship,
typically IfcElements.
:return: None
Example:
.. code:: python
task = ifcopenshell.api.root.create_entity(model, ifc_class="IfcTasks")
subtask1 = ifcopenshell.api.root.create_entity(model, ifc_class="IfcTask")
subtask2 = ifcopenshell.api.root.create_entity(model, ifc_class="IfcTask")
ifcopenshell.api.nest.assign_object(model, related_objects=[subtask1], relating_object=task)
ifcopenshell.api.nest.assign_object(model, related_objects=[subtask2], relating_object=task)
# nothing is returned
rel = ifcopenshell.api.nest.unassign_object(model, related_objects=[subtask1])
# nothing is returned, relationship is removed
ifcopenshell.api.nest.unassign_object(model, related_objects=[subtask2])
"""
# NOTE: maintain .RelatedObjects order as it has meaning in IFC
related_objects_set = set(related_objects)
ifc2x3 = file.schema == "IFC2X3"
if ifc2x3:
rels = set(
rel
for object in related_objects_set
if (rel := next((rel for rel in object.Decomposes if rel.is_a("IfcRelNests")), None))
)
else:
rels = set(rel for object in related_objects if (rel := next((rel for rel in object.Nests), None)))
for rel in rels:
cur_related_objects = [o for o in rel.RelatedObjects if o not in related_objects_set]
if cur_related_objects:
rel.RelatedObjects = cur_related_objects
ifcopenshell.api.owner.update_owner_history(file, element=rel)
else:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)