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,41 @@
# 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/>.
"""Create relationships between features (e.g. openings) and physical elements
A feature is a special element (created using
:func:`ifcopenshell.api.root.create_entity`) that may then be used to create
geometric changes in other elements (such as walls and slabs). Most commonly, a
feature would be an opening void. These voids may then be filled with doors,
trapdoors, skylights, and so on.
"""
from .. import wrap_usecases
from .add_feature import add_feature
from .add_filling import add_filling
from .remove_feature import remove_feature
from .remove_filling import remove_filling
wrap_usecases(__path__, __name__)
__all__ = [
"add_feature",
"add_filling",
"remove_feature",
"remove_filling",
]
@@ -0,0 +1,149 @@
# 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.aggregate
import ifcopenshell.api.geometry
import ifcopenshell.api.owner
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.placement
def add_feature(
file: ifcopenshell.file, feature: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
"""Create a projecting, voiding, or surface feature in an element
There are three main types of features: those that add, remove, or
influence geometry of a parent object.
The most common of these is an opening. For example, it is often necessary
to cut out openings in elements like walls and slabs to make space to
insert doors, windows, and other services that go through these
penetrations.
Whereas it is possible to simply draw the wall as a rectangle with a
hole in it for the opening, often these openings have specific meanings.
For example, an opening might be filled with a window, and so when the
window moves, the opening should move with it. Alternatively, the
opening itself might have fire or acoustic requirements, such that any
service or equipment passing through that space must also comply with
those requirements. For these types of semantic openings, you should
have a distinct opening element which voids your regular element. For
example, your wall will still be a rectangular prism with no hole in it,
and a separate opening element will have a box representing the extents
of the opening for a window. The opening element will automatically
perform a geometric boolean operation to cut out the wall's geometry.
Whenever you have an opening in you project, you should determine
whether or not the opening is semantic (i.e. should be represented by a
distinct opening object) or non-semantic (i.e. should simply be
booleaned or be part of the shape of the object).
:param feature: The IfcFeatureElement to affect the element.
:param element: The IfcElement to add the feature to.
:return: The new IfcRelVoidsElement relationship
Example:
.. code:: python
# A bit of preparation, let's create some geometric contexts since
# we want to create some geometry for our wall and opening.
model3d = ifcopenshell.api.context.add_context(model, context_type="Model")
body = ifcopenshell.api.context.add_context(model,
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
# Create a wall
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
# Let's use the "3D Body" representation we created earlier to add a
# new wall-like body geometry, 5 meters long, 3 meters high, and
# 200mm thick
representation = ifcopenshell.api.geometry.add_wall_representation(model,
context=body, length=5, height=3, thickness=0.2)
ifcopenshell.api.geometry.assign_representation(model,
product=wall, representation=representation)
# Place our wall at the origin
ifcopenshell.api.geometry.edit_object_placement(model, product=wall)
# Create an opening, such as for a service penetration with fire and
# acoustic requirements.
opening = ifcopenshell.api.root.create_entity(model, ifc_class="IfcOpeningElement")
# Let's create an opening representation of a 950mm x 2100mm door.
# Notice how the thickness is greater than the wall thickness, this
# helps resolve floating point resolution errors in 3D.
representation = ifcopenshell.api.geometry.add_wall_representation(model,
context=body, length=.95, height=2.1, thickness=0.4)
ifcopenshell.api.geometry.assign_representation(model,
product=opening, representation=representation)
# Let's shift our door 1 meter along the wall and 100mm along the
# wall, to create a nice overlap for the opening boolean.
matrix = np.identity(4)
matrix[:,3] = [1, -.1, 0, 0]
ifcopenshell.api.geometry.edit_object_placement(model, product=opening, matrix=matrix)
# The opening will now void the wall.
ifcopenshell.api.feature.add_feature(model, feature=opening, element=wall)
"""
if feature.is_a("IfcFeatureElementSubtraction"):
rels = feature.VoidsElements
ifc_class = "IfcRelVoidsElement"
elif feature.is_a("IfcFeatureElementAddition"):
rels = feature.ProjectsElements
ifc_class = "IfcRelProjectsElement"
elif feature.is_a("IfcSurfaceFeature"):
if file.schema == "IFC4":
return ifcopenshell.api.aggregate.assign_object(file, [feature], element)
rels = feature.AdheresToElement
ifc_class = "IfcRelAdheresToElement"
if rels:
if rels[0][4] == element:
return rels[0]
elif ifc_class == "IfcRelAdheresToElement" and len(rels[0].RelatedSurfaceFeatures) != 1:
rels[0].RelatedSurfaceFeatures = list(set(rels[0].RelatedSurfaceFeatures) - {feature})
else:
history = rels[0].OwnerHistory
file.remove(rels[0])
if history:
ifcopenshell.util.element.remove_deep2(file, history)
rel = file.create_entity(
ifc_class,
ifcopenshell.guid.new(),
ifcopenshell.api.owner.create_owner_history(file),
None,
None,
element,
[feature] if ifc_class == "IfcRelAdheresToElement" else feature,
)
if (placement := feature.ObjectPlacement) and placement.is_a("IfcLocalPlacement"):
ifcopenshell.api.geometry.edit_object_placement(
file,
product=feature,
matrix=ifcopenshell.util.placement.get_local_placement(placement),
is_si=False,
)
return rel
@@ -0,0 +1,117 @@
# 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.guid
import ifcopenshell.util.element
def add_filling(
file: ifcopenshell.file, opening: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
"""Fill an opening with an element
Physical elements may have openings in them. For example, a wall might
have an opening for a door. That opening is then filled by the door.
This indicates that when the door moves, the opening will move with it.
Or if the door is removed, then the opening may remain and need to be
filled.
:param opening: The IfcOpeningElement to fill with the element.
:param element: The IfcElement to be inserted into the opening.
:return: The new IfcRelFillsElement relationship
Example:
.. code:: python
# A bit of preparation, let's create some geometric contexts since
# we want to create some geometry for our wall and opening.
model3d = ifcopenshell.api.context.add_context(model, context_type="Model")
body = ifcopenshell.api.context.add_context(model,
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
# Create a wall
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
# Let's use the "3D Body" representation we created earlier to add a
# new wall-like body geometry, 5 meters long, 3 meters high, and
# 200mm thick
representation = ifcopenshell.api.geometry.add_wall_representation(model,
context=body, length=5, height=3, thickness=0.2)
ifcopenshell.api.geometry.assign_representation(model,
product=wall, representation=representation)
# Place our wall at the origin
ifcopenshell.api.geometry.edit_object_placement(model, product=wall)
# Create an opening, such as for a service penetration with fire and
# acoustic requirements.
opening = ifcopenshell.api.root.create_entity(model, ifc_class="IfcOpeningElement")
# Let's create an opening representation of a 950mm x 2100mm door.
# Notice how the thickness is greater than the wall thickness, this
# helps resolve floating point resolution errors in 3D.
representation = ifcopenshell.api.geometry.add_wall_representation(model,
context=body, length=.95, height=2.1, thickness=0.4)
ifcopenshell.api.geometry.assign_representation(model,
product=opening, representation=representation)
# Let's shift our door 1 meter along the wall and 100mm along the
# wall, to create a nice overlap for the opening boolean.
matrix = np.identity(4)
matrix[:,3] = [1, -.1, 0, 0]
ifcopenshell.api.geometry.edit_object_placement(model, product=opening, matrix=matrix)
# The opening will now void the wall.
ifcopenshell.api.feature.add_feature(model, feature=opening, element=wall)
# Create a door
door = ifcopenshell.api.root.create_entity(model, ifc_class="IfcDoor")
# Let's create a door representation of a 950mm x 2100mm door.
representation = ifcopenshell.api.geometry.add_wall_representation(model,
context=body, length=.95, height=2.1, thickness=0.05)
ifcopenshell.api.geometry.assign_representation(model,
product=door, representation=representation)
# Let's shift our door 1 meter along the wall and 100mm along the
# wall, which lines up with our opening.
matrix = np.identity(4)
matrix[:,3] = [1, .05, 0, 0]
ifcopenshell.api.geometry.edit_object_placement(model, product=door, matrix=matrix)
# The door will now fill the opening.
ifcopenshell.api.feature.add_filling(model, opening=opening, element=door)
"""
fills_voids = element.FillsVoids
if fills_voids:
if fills_voids[0].RelatingOpeningElement == opening:
return fills_voids[0]
history = fills_voids[0].OwnerHistory
file.remove(fills_voids[0])
if history:
ifcopenshell.util.element.remove_deep2(file, history)
return file.create_entity(
"IfcRelFillsElement",
GlobalId=ifcopenshell.guid.new(),
RelatingOpeningElement=opening,
RelatedBuildingElement=element,
)
@@ -0,0 +1,68 @@
# 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.api.aggregate
import ifcopenshell.api.root
import ifcopenshell.util.element
def remove_feature(file: ifcopenshell.file, feature: ifcopenshell.entity_instance) -> None:
"""Permanently delete a feature element and its void or projection relationship.
The feature entity (e.g. IfcOpeningElement) is removed from the model
along with its IfcRelVoidsElement or IfcRelProjectsElement relationship.
The host element (wall, slab, etc.) is unaffected. Any fillings (windows,
doors) that occupied the opening become orphaned and must be separately
deleted via root.remove_product.
:param feature: The IfcFeatureElement to remove.
Example:
.. code:: python
# Create an orphaned opening. Note that an orphaned opening is
# invalid, as an opening can only exist when voiding another
# element.
feature = ifcopenshell.api.root.create_entity(model, ifc_class="IfcOpeningElement")
# Remove it. This brings us back to a valid model.
ifcopenshell.api.feature.remove_feature(model, feature=feature)
"""
if feature.is_a("IfcFeatureElementSubtraction"):
rels = feature.VoidsElements
elif feature.is_a("IfcFeatureElementAddition"):
rels = feature.ProjectsElements
elif feature.is_a("IfcSurfaceFeature"):
if file.schema == "IFC4":
ifcopenshell.api.aggregate.unassign_object(file, products=[feature])
rels = []
else:
rels = feature.ProjectsElements
for rel in rels:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
if feature.is_a("IfcOpeningElement"):
for rel in feature.HasFillings:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
ifcopenshell.api.root.remove_product(file, product=feature)
@@ -0,0 +1,59 @@
# 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.util.element
def remove_filling(file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> None:
"""Remove a filling relationship
If an element is filling an opening, this removes the relationship such
that the opening and element both still exist, but the element no longer
fills the opening.
:param element: The element filling an opening.
:return: None
Example:
.. code:: python
# Create a wall
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
# Create an opening, such as for a service penetration with fire and
# acoustic requirements.
opening = ifcopenshell.api.root.create_entity(model, ifc_class="IfcOpeningElement")
# Create a door
door = ifcopenshell.api.root.create_entity(model, ifc_class="IfcDoor")
# The door will now fill the opening.
ifcopenshell.api.feature.add_filling(model, opening=opening, element=door)
# Not anymore!
ifcopenshell.api.feature.remove_filling(model, element=door)
"""
for rel in file.by_type("IfcRelFillsElement"):
if rel.RelatedBuildingElement == element:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
break