See #5919. API now has support for features, not just openings. Void module is renamed to feature.

This commit is contained in:
Dion Moult
2025-01-28 14:18:17 +11:00
parent 5300f4c78f
commit 03bfd828e2
21 changed files with 326 additions and 132 deletions
@@ -26,7 +26,7 @@ import numpy as np
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.api.geometry import ifcopenshell.api.geometry
import ifcopenshell.api.void import ifcopenshell.api.feature
import ifcopenshell.geom import ifcopenshell.geom
import ifcopenshell.util.shape import ifcopenshell.util.shape
import ifcopenshell.util.element import ifcopenshell.util.element
@@ -69,7 +69,7 @@ class FilledOpeningGenerator:
assert filling and element assert filling and element
if filling.FillsVoids: if filling.FillsVoids:
ifcopenshell.api.run( ifcopenshell.api.run(
"void.remove_opening", tool.Ifc.get(), opening=filling.FillsVoids[0].RelatingOpeningElement "feature.remove_feature", tool.Ifc.get(), feature=filling.FillsVoids[0].RelatingOpeningElement
) )
if target is None: if target is None:
@@ -156,8 +156,8 @@ class FilledOpeningGenerator:
"geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation "geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation
) )
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=opening, element=element) ifcopenshell.api.run("feature.add_feature", tool.Ifc.get(), feature=opening, element=element)
ifcopenshell.api.run("void.add_filling", tool.Ifc.get(), opening=opening, element=filling) ifcopenshell.api.run("feature.add_filling", tool.Ifc.get(), opening=opening, element=filling)
voided_objs = [voided_obj] voided_objs = [voided_obj]
# Openings affect all subelements of an aggregate # Openings affect all subelements of an aggregate
@@ -826,7 +826,7 @@ class CloneOpening(Operator, tool.Ifc.Operator):
new_opening = ifcopenshell.api.run("root.create_entity", tool.Ifc.get(), ifc_class="IfcOpeningElement") new_opening = ifcopenshell.api.run("root.create_entity", tool.Ifc.get(), ifc_class="IfcOpeningElement")
new_opening.Representation = opening_representation new_opening.Representation = opening_representation
ifcopenshell.api.void.add_opening(ifc_file, opening=new_opening, element=voided_element) ifcopenshell.api.feature.add_feature(ifc_file, feature=new_opening, element=voided_element)
new_opening.ObjectPlacement = opening_placement new_opening.ObjectPlacement = opening_placement
# Update affected representations. # Update affected representations.
+2 -2
View File
@@ -984,7 +984,7 @@ class DumbWallJoiner:
_, opening_position = mathutils.geometry.intersect_point_line(opening_location.to_2d(), *axis1["reference"]) _, opening_position = mathutils.geometry.intersect_point_line(opening_location.to_2d(), *axis1["reference"])
if opening_position > cut_percentage: if opening_position > cut_percentage:
# The opening should be removed from element1. # The opening should be removed from element1.
ifcopenshell.api.run("void.remove_opening", tool.Ifc.get(), opening=opening) ifcopenshell.api.run("feature.remove_feature", tool.Ifc.get(), feature=opening)
# Now let's check element2. # Now let's check element2.
for opening in [ for opening in [
@@ -996,7 +996,7 @@ class DumbWallJoiner:
_, opening_position = mathutils.geometry.intersect_point_line(opening_location.to_2d(), *axis1["reference"]) _, opening_position = mathutils.geometry.intersect_point_line(opening_location.to_2d(), *axis1["reference"])
if opening_position < cut_percentage: if opening_position < cut_percentage:
# The opening should be removed from element2. # The opening should be removed from element2.
ifcopenshell.api.run("void.remove_opening", tool.Ifc.get(), opening=opening) ifcopenshell.api.run("feature.remove_feature", tool.Ifc.get(), feature=opening)
# During the duplication process, filled voids are not copied. So we # During the duplication process, filled voids are not copied. So we
# only need to check fillings on the original element1. # only need to check fillings on the original element1.
@@ -87,7 +87,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
self.report({"INFO"}, f"An {element1.is_a()} is not allowed to have an opening.") self.report({"INFO"}, f"An {element1.is_a()} is not allowed to have an opening.")
continue continue
# Sync placement before void.add_opening. # Sync placement before feature.add_feature.
if tool.Ifc.is_moved(obj1): if tool.Ifc.is_moved(obj1):
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj1) bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj1)
@@ -109,7 +109,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
should_add_representation=True, should_add_representation=True,
context=body_context, context=body_context,
) )
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=element2, element=element1) ifcopenshell.api.run("feature.add_feature", tool.Ifc.get(), feature=element2, element=element1)
if tool.Ifc.is_moved(obj2): if tool.Ifc.is_moved(obj2):
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj2) bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj2)
@@ -178,7 +178,7 @@ class RemoveOpening(bpy.types.Operator, tool.Ifc.Operator):
opening_obj.name = "/".join(opening_obj.name.split("/")[1:]) opening_obj.name = "/".join(opening_obj.name.split("/")[1:])
tool.Ifc.unlink(element=opening) tool.Ifc.unlink(element=opening)
ifcopenshell.api.run("void.remove_opening", tool.Ifc.get(), opening=opening) ifcopenshell.api.run("feature.remove_feature", tool.Ifc.get(), feature=opening)
decomposed_building_elements = {element} decomposed_building_elements = {element}
decomposed_building_elements.update(tool.Aggregate.get_parts_recursively(element)) decomposed_building_elements.update(tool.Aggregate.get_parts_recursively(element))
@@ -220,7 +220,7 @@ class AddFilling(bpy.types.Operator, tool.Ifc.Operator):
if not element_id or not opening_id or element_id == opening_id: if not element_id or not opening_id or element_id == opening_id:
return {"FINISHED"} return {"FINISHED"}
ifcopenshell.api.run( ifcopenshell.api.run(
"void.add_filling", self.file, opening=self.file.by_id(opening_id), element=self.file.by_id(element_id) "feature.add_filling", self.file, opening=self.file.by_id(opening_id), element=self.file.by_id(element_id)
) )
return {"FINISHED"} return {"FINISHED"}
@@ -233,10 +233,9 @@ class RemoveFilling(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
filling = tool.Ifc.get().by_id(self.filling) filling = tool.Ifc.get().by_id(self.filling)
filling_obj = tool.Ifc.get_object(filling)
for rel in filling.FillsVoids: for rel in filling.FillsVoids:
bpy.ops.bim.remove_opening(opening_id=rel.RelatingOpeningElement.id()) bpy.ops.bim.remove_opening(opening_id=rel.RelatingOpeningElement.id())
ifcopenshell.api.run("void.remove_filling", tool.Ifc.get(), element=filling) ifcopenshell.api.run("feature.remove_filling", tool.Ifc.get(), element=filling)
return {"FINISHED"} return {"FINISHED"}
+2 -2
View File
@@ -40,13 +40,13 @@ class Feature(bonsai.core.tool.Feature):
for feature_obj in feature_objs: for feature_obj in feature_objs:
feature_element = tool.Ifc.get_entity(feature_obj) feature_element = tool.Ifc.get_entity(feature_obj)
# Sync placement before void.add_opening. # Sync placement before feature.add_feature.
if tool.Ifc.is_moved(featured_obj): if tool.Ifc.is_moved(featured_obj):
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=featured_obj) bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=featured_obj)
element_had_openings = tool.Geometry.has_openings(featured_element) element_had_openings = tool.Geometry.has_openings(featured_element)
body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body") body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body")
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=feature_element, element=featured_element) ifcopenshell.api.run("feature.add_feature", tool.Ifc.get(), feature=feature_element, element=featured_element)
if tool.Ifc.is_moved(feature_obj): if tool.Ifc.is_moved(feature_obj):
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=feature_obj) bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=feature_obj)
+2 -2
View File
@@ -290,8 +290,8 @@ class Root(bonsai.core.tool.Root):
product=opening, product=opening,
representation=mapped_representation, representation=mapped_representation,
) )
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=opening, element=element) ifcopenshell.api.run("feature.add_feature", tool.Ifc.get(), feature=opening, element=element)
ifcopenshell.api.run("void.add_filling", tool.Ifc.get(), opening=opening, element=filling) ifcopenshell.api.run("feature.add_filling", tool.Ifc.get(), opening=opening, element=filling)
voided_objs = [voided_obj] voided_objs = [voided_obj]
# Openings affect all subelements of an aggregate # Openings affect all subelements of an aggregate
+2 -2
View File
@@ -74,8 +74,8 @@ class TestGetDecompositionRelationships(NewFile):
element = ifc.createIfcWall() element = ifc.createIfcWall()
opening = ifc.createIfcOpeningElement() opening = ifc.createIfcOpeningElement()
fill = ifc.createIfcWindow() fill = ifc.createIfcWindow()
ifcopenshell.api.run("void.add_opening", ifc, opening=opening, element=element) ifcopenshell.api.run("feature.add_feature", ifc, feature=opening, element=element)
ifcopenshell.api.run("void.add_filling", ifc, opening=opening, element=fill) ifcopenshell.api.run("feature.add_filling", ifc, opening=opening, element=fill)
obj = bpy.data.objects.new("Object", None) obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(fill, obj) tool.Ifc.link(fill, obj)
@@ -16,25 +16,26 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
"""Create void relationships between openings and physical elements """Create relationships between features (e.g. openings) and physical elements
An opening is a special element (created using A feature is a special element (created using
:func:`ifcopenshell.api.root.create_entity`) that may then be used to create :func:`ifcopenshell.api.root.create_entity`) that may then be used to create
voids in other elements (such as walls and slabs). These voids may then be geometric changes in other elements (such as walls and slabs). Most commonly, a
filled with doors, trapdoors, skylights, and so on. feature would be an opening void. These voids may then be filled with doors,
trapdoors, skylights, and so on.
""" """
from .. import wrap_usecases from .. import wrap_usecases
from .add_feature import add_feature
from .add_filling import add_filling from .add_filling import add_filling
from .add_opening import add_opening from .remove_feature import remove_feature
from .remove_filling import remove_filling from .remove_filling import remove_filling
from .remove_opening import remove_opening
wrap_usecases(__path__, __name__) wrap_usecases(__path__, __name__)
__all__ = [ __all__ = [
"add_feature",
"add_filling", "add_filling",
"add_opening", "remove_feature",
"remove_filling", "remove_filling",
"remove_opening",
] ]
@@ -0,0 +1,152 @@
# 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.api.geometry
import ifcopenshell.api.aggregate
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.
:type feature: ifcopenshell.entity_instance
:param element: The IfcElement to add the feature to.
:type element: ifcopenshell.entity_instance
:return: The new IfcRelVoidsElement relationship
:rtype: ifcopenshell.entity_instance
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
@@ -82,7 +82,7 @@ def add_filling(
ifcopenshell.api.geometry.edit_object_placement(model, product=opening, matrix=matrix) ifcopenshell.api.geometry.edit_object_placement(model, product=opening, matrix=matrix)
# The opening will now void the wall. # The opening will now void the wall.
ifcopenshell.api.void.add_opening(model, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(model, feature=opening, element=wall)
# Create a door # Create a door
door = ifcopenshell.api.root.create_entity(model, ifc_class="IfcDoor") door = ifcopenshell.api.root.create_entity(model, ifc_class="IfcDoor")
@@ -100,7 +100,7 @@ def add_filling(
ifcopenshell.api.geometry.edit_object_placement(model, product=door, matrix=matrix) ifcopenshell.api.geometry.edit_object_placement(model, product=door, matrix=matrix)
# The door will now fill the opening. # The door will now fill the opening.
ifcopenshell.api.void.add_filling(model, opening=opening, element=door) ifcopenshell.api.feature.add_filling(model, opening=opening, element=door)
""" """
settings = {"opening": opening, "element": element} settings = {"opening": opening, "element": element}
@@ -17,44 +17,50 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.api.root import ifcopenshell.api.root
import ifcopenshell.api.aggregate
import ifcopenshell.util.element import ifcopenshell.util.element
def remove_opening(file: ifcopenshell.file, opening: ifcopenshell.entity_instance) -> None: def remove_feature(file: ifcopenshell.file, feature: ifcopenshell.entity_instance) -> None:
"""Remove an opening """Remove a feature
Fillings are retained as orphans. Voided elements remain. Openings Fillings are retained as orphans. Featured elements remain. Features
cannot exist by themselves, so not only is the opening relationship cannot exist by themselves, so not only is the relationship removed, the
removed, the opening is also removed. feature is also removed.
:param opening: The IfcOpeningElement to remove. :param feature: The IfcFeatureElement to remove.
:type opening: ifcopenshell.entity_instance
:return: None
:rtype: None
Example: Example:
.. code:: python .. code:: python
# Create an oprhaned opening. Note that an orphaned opening is # Create an orphaned opening. Note that an orphaned opening is
# invalid, as an opening can only exist when voiding another # invalid, as an opening can only exist when voiding another
# element. # element.
opening = ifcopenshell.api.root.create_entity(model, ifc_class="IfcOpeningElement") feature = ifcopenshell.api.root.create_entity(model, ifc_class="IfcOpeningElement")
# Remove it. This brings us back to a valid model. # Remove it. This brings us back to a valid model.
ifcopenshell.api.void.remove_opening(model, opening=opening) ifcopenshell.api.feature.remove_feature(model, feature=feature)
""" """
settings = {"opening": opening} if feature.is_a("IfcFeatureElementSubtraction"):
rels = feature.VoidsElements
for rel in settings["opening"].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 history = rel.OwnerHistory
file.remove(rel) file.remove(rel)
if history: if history:
ifcopenshell.util.element.remove_deep2(file, history) ifcopenshell.util.element.remove_deep2(file, history)
if settings["opening"].is_a("IfcOpeningElement"): if feature.is_a("IfcOpeningElement"):
for rel in settings["opening"].HasFillings: for rel in feature.HasFillings:
history = rel.OwnerHistory history = rel.OwnerHistory
file.remove(rel) file.remove(rel)
if history: if history:
ifcopenshell.util.element.remove_deep2(file, history) ifcopenshell.util.element.remove_deep2(file, history)
ifcopenshell.api.root.remove_product(file, product=settings["opening"]) ifcopenshell.api.root.remove_product(file, product=feature)
@@ -47,10 +47,10 @@ def remove_filling(file: ifcopenshell.file, element: ifcopenshell.entity_instanc
door = ifcopenshell.api.root.create_entity(model, ifc_class="IfcDoor") door = ifcopenshell.api.root.create_entity(model, ifc_class="IfcDoor")
# The door will now fill the opening. # The door will now fill the opening.
ifcopenshell.api.void.add_filling(model, opening=opening, element=door) ifcopenshell.api.feature.add_filling(model, opening=opening, element=door)
# Not anymore! # Not anymore!
ifcopenshell.api.void.remove_filling(model, element=door) ifcopenshell.api.feature.remove_filling(model, element=door)
""" """
settings = {"element": element} settings = {"element": element}
@@ -18,7 +18,7 @@
import ifcopenshell.api.type import ifcopenshell.api.type
import ifcopenshell.api.grid import ifcopenshell.api.grid
import ifcopenshell.api.void import ifcopenshell.api.feature
import ifcopenshell.api.root import ifcopenshell.api.root
import ifcopenshell.api.pset import ifcopenshell.api.pset
import ifcopenshell.api.boundary import ifcopenshell.api.boundary
@@ -94,7 +94,7 @@ def remove_product(file: ifcopenshell.file, product: ifcopenshell.entity_instanc
) )
ifcopenshell.api.geometry.remove_representation(file, **{"representation": representation}) ifcopenshell.api.geometry.remove_representation(file, **{"representation": representation})
for opening in getattr(settings["product"], "HasOpenings", []) or []: for opening in getattr(settings["product"], "HasOpenings", []) or []:
ifcopenshell.api.void.remove_opening(file, opening=opening.RelatedOpeningElement) ifcopenshell.api.feature.remove_feature(file, feature=opening.RelatedOpeningElement)
if settings["product"].is_a("IfcGrid"): if settings["product"].is_a("IfcGrid"):
for axis in settings["product"].UAxes + settings["product"].VAxes + (settings["product"].WAxes or ()): for axis in settings["product"].UAxes + settings["product"].VAxes + (settings["product"].WAxes or ()):
@@ -19,19 +19,24 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.api.owner import ifcopenshell.api.owner
import ifcopenshell.api.geometry import ifcopenshell.api.geometry
import ifcopenshell.api.aggregate
import ifcopenshell.guid import ifcopenshell.guid
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.placement import ifcopenshell.util.placement
def add_opening( def add_feature(
file: ifcopenshell.file, opening: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance file: ifcopenshell.file, feature: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance: ) -> ifcopenshell.entity_instance:
"""Create an opening in an element """Create a projecting, voiding, or surface feature in an element
It is often necessary to cut out openings in elements like walls and There are three main types of features: those that add, remove, or
slabs to make space to insert doors, windows, and other services that go influence geometry of a parent object.
through these penetrations.
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 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. hole in it for the opening, often these openings have specific meanings.
@@ -51,9 +56,9 @@ def add_opening(
distinct opening object) or non-semantic (i.e. should simply be distinct opening object) or non-semantic (i.e. should simply be
booleaned or be part of the shape of the object). booleaned or be part of the shape of the object).
:param opening: The IfcOpeningElement to cut out the element. :param feature: The IfcFeatureElement to affect the element.
:type opening: ifcopenshell.entity_instance :type feature: ifcopenshell.entity_instance
:param element: The IfcElement to insert the opening into. :param element: The IfcElement to add the feature to.
:type element: ifcopenshell.entity_instance :type element: ifcopenshell.entity_instance
:return: The new IfcRelVoidsElement relationship :return: The new IfcRelVoidsElement relationship
:rtype: ifcopenshell.entity_instance :rtype: ifcopenshell.entity_instance
@@ -101,36 +106,46 @@ def add_opening(
ifcopenshell.api.geometry.edit_object_placement(model, product=opening, matrix=matrix) ifcopenshell.api.geometry.edit_object_placement(model, product=opening, matrix=matrix)
# The opening will now void the wall. # The opening will now void the wall.
ifcopenshell.api.void.add_opening(model, opening=opening, element=wall) ifcopenshell.api.void.add_feature(model, feature=opening, element=wall)
""" """
settings = {"opening": opening, "element": element} 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"
voids_elements = settings["opening"].VoidsElements if rels:
if rels[0][4] == element:
if voids_elements: return rels[0]
if voids_elements[0].RelatingBuildingElement == settings["element"]: elif ifc_class == "IfcRelAdheresToElement" and len(rels[0].RelatedSurfaceFeatures) != 1:
return voids_elements[0] rels[0].RelatedSurfaceFeatures = list(set(rels[0].RelatedSurfaceFeatures) - {feature})
history = voids_elements[0].OwnerHistory else:
file.remove(voids_elements[0]) history = rels[0].OwnerHistory
if history: file.remove(rels[0])
ifcopenshell.util.element.remove_deep2(file, history) if history:
ifcopenshell.util.element.remove_deep2(file, history)
rel = file.create_entity( rel = file.create_entity(
"IfcRelVoidsElement", ifc_class,
**{ ifcopenshell.guid.new(),
"GlobalId": ifcopenshell.guid.new(), ifcopenshell.api.owner.create_owner_history(file),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file), None,
"RelatingBuildingElement": settings["element"], None,
"RelatedOpeningElement": settings["opening"], element,
} [feature] if ifc_class == "IfcRelAdheresToElement" else feature,
) )
placement = getattr(settings["opening"], "ObjectPlacement", None) if (placement := feature.ObjectPlacement) and placement.is_a("IfcLocalPlacement"):
if placement and placement.is_a("IfcLocalPlacement"):
ifcopenshell.api.geometry.edit_object_placement( ifcopenshell.api.geometry.edit_object_placement(
file, file,
product=settings["opening"], product=feature,
matrix=ifcopenshell.util.placement.get_local_placement(settings["opening"].ObjectPlacement), matrix=ifcopenshell.util.placement.get_local_placement(placement),
is_si=False, is_si=False,
) )
@@ -18,24 +18,44 @@
import numpy import numpy
import test.bootstrap import test.bootstrap
import ifcopenshell.api.void import ifcopenshell.api.feature
import ifcopenshell.api.root import ifcopenshell.api.root
import ifcopenshell.api.unit import ifcopenshell.api.unit
import ifcopenshell.api.geometry import ifcopenshell.api.geometry
class TestAddOpening(test.bootstrap.IFC4): class TestAddFeature(test.bootstrap.IFC4X3):
def test_adding_a_surface_feature(self):
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
feature = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSurfaceFeature")
ifcopenshell.api.feature.add_feature(self.file, feature=feature, element=wall)
assert wall.HasSurfaceFeatures[0].RelatedSurfaceFeatures == (feature,)
class TestAddFeature(test.bootstrap.IFC4):
def test_adding_an_opening(self): def test_adding_an_opening(self):
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
assert wall.HasOpenings[0].RelatedOpeningElement == opening assert wall.HasOpenings[0].RelatedOpeningElement == opening
def test_adding_a_projection(self):
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
projection = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProjectionElement")
ifcopenshell.api.feature.add_feature(self.file, feature=projection, element=wall)
assert wall.HasProjections[0].RelatedFeatureElement == projection
def test_adding_a_surface_feature(self):
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
feature = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSurfaceFeature")
ifcopenshell.api.feature.add_feature(self.file, feature=feature, element=wall)
assert wall.IsDecomposedBy[0].RelatedObjects == (feature,)
def test_adding_an_opening_twice(self): def test_adding_an_opening_twice(self):
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
assert wall.HasOpenings[0].RelatedOpeningElement == opening assert wall.HasOpenings[0].RelatedOpeningElement == opening
assert len(wall.HasOpenings) == 1 assert len(wall.HasOpenings) == 1
@@ -43,8 +63,8 @@ class TestAddOpening(test.bootstrap.IFC4):
slab = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSlab") slab = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSlab")
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=slab) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=slab)
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
assert not slab.HasOpenings assert not slab.HasOpenings
assert wall.HasOpenings[0].RelatedOpeningElement == opening assert wall.HasOpenings[0].RelatedOpeningElement == opening
@@ -63,7 +83,7 @@ class TestAddOpening(test.bootstrap.IFC4):
) )
ifcopenshell.api.geometry.edit_object_placement(self.file, product=wall, matrix=matrix1.copy(), is_si=False) ifcopenshell.api.geometry.edit_object_placement(self.file, product=wall, matrix=matrix1.copy(), is_si=False)
ifcopenshell.api.geometry.edit_object_placement(self.file, product=opening, matrix=matrix1.copy(), is_si=False) ifcopenshell.api.geometry.edit_object_placement(self.file, product=opening, matrix=matrix1.copy(), is_si=False)
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
assert opening.ObjectPlacement.PlacementRelTo.PlacesObject[0] == wall assert opening.ObjectPlacement.PlacementRelTo.PlacesObject[0] == wall
assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(opening.ObjectPlacement), matrix1) assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(opening.ObjectPlacement), matrix1)
@@ -74,9 +94,10 @@ class TestAddOpening(test.bootstrap.IFC4):
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
placement = self.file.createIfcGridPlacement() placement = self.file.createIfcGridPlacement()
opening.ObjectPlacement = placement opening.ObjectPlacement = placement
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
assert opening.ObjectPlacement == placement assert opening.ObjectPlacement == placement
class TestAddOpeningIFC2X3(test.bootstrap.IFC2X3, TestAddOpening): class TestAddFeatureIFC2X3(test.bootstrap.IFC2X3, TestAddFeature):
pass def test_adding_a_surface_feature(self):
pass
@@ -17,7 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import test.bootstrap import test.bootstrap
import ifcopenshell.api.void import ifcopenshell.api.feature
import ifcopenshell.api.root import ifcopenshell.api.root
@@ -25,14 +25,14 @@ class TestAddFilling(test.bootstrap.IFC4):
def test_adding_a_filling(self): def test_adding_a_filling(self):
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor")
ifcopenshell.api.void.add_filling(self.file, opening=opening, element=door) ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=door)
assert door.FillsVoids[0].RelatingOpeningElement == opening assert door.FillsVoids[0].RelatingOpeningElement == opening
def test_adding_a_filling_twice(self): def test_adding_a_filling_twice(self):
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor")
ifcopenshell.api.void.add_filling(self.file, opening=opening, element=door) ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=door)
ifcopenshell.api.void.add_filling(self.file, opening=opening, element=door) ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=door)
assert door.FillsVoids[0].RelatingOpeningElement == opening assert door.FillsVoids[0].RelatingOpeningElement == opening
assert len(opening.HasFillings) == 1 assert len(opening.HasFillings) == 1
@@ -40,8 +40,8 @@ class TestAddFilling(test.bootstrap.IFC4):
door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor")
opening1 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening1 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
opening2 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening2 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.add_filling(self.file, opening=opening1, element=door) ifcopenshell.api.feature.add_filling(self.file, opening=opening1, element=door)
ifcopenshell.api.void.add_filling(self.file, opening=opening2, element=door) ifcopenshell.api.feature.add_filling(self.file, opening=opening2, element=door)
assert not opening1.HasFillings assert not opening1.HasFillings
assert opening2.HasFillings[0].RelatedBuildingElement == door assert opening2.HasFillings[0].RelatedBuildingElement == door
@@ -17,21 +17,21 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import test.bootstrap import test.bootstrap
import ifcopenshell.api.void import ifcopenshell.api.feature
import ifcopenshell.api.root import ifcopenshell.api.root
class TestRemoveOpening(test.bootstrap.IFC4): class TestRemoveFeature(test.bootstrap.IFC4):
def test_removing_a_simple_opening(self): def test_removing_a_simple_feature(self):
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") feature = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.remove_opening(self.file, opening=opening) ifcopenshell.api.feature.remove_feature(self.file, feature=feature)
assert len(list(self.file)) == 0 assert len(list(self.file)) == 0
def test_removing_an_opening_voiding_a_wall(self): def test_removing_an_opening_voiding_a_wall(self):
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
ifcopenshell.api.void.remove_opening(self.file, opening=opening) ifcopenshell.api.feature.remove_feature(self.file, feature=opening)
assert len(self.file.by_type("IfcOpeningElement")) == 0 assert len(self.file.by_type("IfcOpeningElement")) == 0
assert len(self.file.by_type("IfcRelVoidsElement")) == 0 assert len(self.file.by_type("IfcRelVoidsElement")) == 0
assert wall assert wall
@@ -40,9 +40,9 @@ class TestRemoveOpening(test.bootstrap.IFC4):
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor")
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
ifcopenshell.api.void.add_filling(self.file, opening=opening, element=door) ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=door)
ifcopenshell.api.void.remove_opening(self.file, opening=opening) ifcopenshell.api.feature.remove_feature(self.file, feature=opening)
assert len(self.file.by_type("IfcOpeningElement")) == 0 assert len(self.file.by_type("IfcOpeningElement")) == 0
assert len(self.file.by_type("IfcRelVoidsElement")) == 0 assert len(self.file.by_type("IfcRelVoidsElement")) == 0
assert len(self.file.by_type("IfcRelFillsElement")) == 0 assert len(self.file.by_type("IfcRelFillsElement")) == 0
@@ -50,5 +50,5 @@ class TestRemoveOpening(test.bootstrap.IFC4):
assert door assert door
class TestRemoveOpeningIFC2X3(test.bootstrap.IFC2X3, TestRemoveOpening): class TestRemoveFeatureIFC2X3(test.bootstrap.IFC2X3, TestRemoveFeature):
pass pass
@@ -19,7 +19,7 @@
import numpy import numpy
import pytest import pytest
import test.bootstrap import test.bootstrap
import ifcopenshell.api.void import ifcopenshell.api.feature
import ifcopenshell.api.root import ifcopenshell.api.root
import ifcopenshell.api.unit import ifcopenshell.api.unit
import ifcopenshell.api.system import ifcopenshell.api.system
@@ -303,7 +303,7 @@ class TestEditObjectPlacement(test.bootstrap.IFC4):
(0.0, 0.0, 0.0, 1.0), (0.0, 0.0, 0.0, 1.0),
) )
) )
ifcopenshell.api.void.add_opening(self.file, opening=subelement, element=element) ifcopenshell.api.feature.add_feature(self.file, feature=subelement, element=element)
ifcopenshell.api.geometry.edit_object_placement(self.file, product=element, matrix=matrix.copy(), is_si=False) ifcopenshell.api.geometry.edit_object_placement(self.file, product=element, matrix=matrix.copy(), is_si=False)
ifcopenshell.api.geometry.edit_object_placement( ifcopenshell.api.geometry.edit_object_placement(
self.file, product=subelement, matrix=submatrix.copy(), is_si=False self.file, product=subelement, matrix=submatrix.copy(), is_si=False
@@ -337,8 +337,8 @@ class TestEditObjectPlacement(test.bootstrap.IFC4):
(0.0, 0.0, 0.0, 1.0), (0.0, 0.0, 0.0, 1.0),
) )
) )
ifcopenshell.api.void.add_opening(self.file, opening=element, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=element, element=wall)
ifcopenshell.api.void.add_filling(self.file, element=subelement, opening=element) ifcopenshell.api.feature.add_filling(self.file, element=subelement, opening=element)
ifcopenshell.api.geometry.edit_object_placement(self.file, product=site, matrix=numpy.eye(4), is_si=False) ifcopenshell.api.geometry.edit_object_placement(self.file, product=site, matrix=numpy.eye(4), is_si=False)
ifcopenshell.api.geometry.edit_object_placement(self.file, product=wall, matrix=numpy.eye(4), is_si=False) ifcopenshell.api.geometry.edit_object_placement(self.file, product=wall, matrix=numpy.eye(4), is_si=False)
ifcopenshell.api.geometry.edit_object_placement(self.file, product=element, matrix=matrix.copy(), is_si=False) ifcopenshell.api.geometry.edit_object_placement(self.file, product=element, matrix=matrix.copy(), is_si=False)
@@ -564,8 +564,8 @@ class TestEditObjectPlacement(test.bootstrap.IFC4):
shifted_submatrix = numpy.eye(4) shifted_submatrix = numpy.eye(4)
shifted_submatrix[:3, 3] = (1, 3, 5) shifted_submatrix[:3, 3] = (1, 3, 5)
ifcopenshell.api.void.add_opening(self.file, opening=subelement, element=element) ifcopenshell.api.feature.add_feature(self.file, feature=subelement, element=element)
ifcopenshell.api.void.add_filling(self.file, opening=subelement, element=subsubelement) ifcopenshell.api.feature.add_filling(self.file, opening=subelement, element=subsubelement)
previous_placement_id = ifcopenshell.api.geometry.edit_object_placement( previous_placement_id = ifcopenshell.api.geometry.edit_object_placement(
self.file, product=element, matrix=matrix.copy(), is_si=False self.file, product=element, matrix=matrix.copy(), is_si=False
).id() ).id()
@@ -21,7 +21,7 @@ import test.bootstrap
import ifcopenshell.api.unit import ifcopenshell.api.unit
import ifcopenshell.api.type import ifcopenshell.api.type
import ifcopenshell.api.root import ifcopenshell.api.root
import ifcopenshell.api.void import ifcopenshell.api.feature
import ifcopenshell.api.pset import ifcopenshell.api.pset
import ifcopenshell.api.group import ifcopenshell.api.group
import ifcopenshell.api.system import ifcopenshell.api.system
@@ -134,7 +134,7 @@ class TestCopyClass(test.bootstrap.IFC4):
# IfcOpeningElement opening # IfcOpeningElement opening
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
new = ifcopenshell.api.root.copy_class(self.file, product=wall) new = ifcopenshell.api.root.copy_class(self.file, product=wall)
assert wall.HasOpenings[0] != new.HasOpenings[0] assert wall.HasOpenings[0] != new.HasOpenings[0]
assert wall.HasOpenings[0].RelatedOpeningElement == opening assert wall.HasOpenings[0].RelatedOpeningElement == opening
@@ -144,7 +144,7 @@ class TestCopyClass(test.bootstrap.IFC4):
# IfcVoidingFeature opening # IfcVoidingFeature opening
plate = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcPlate") plate = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcPlate")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcVoidingFeature") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcVoidingFeature")
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=plate) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=plate)
new = ifcopenshell.api.root.copy_class(self.file, product=plate) new = ifcopenshell.api.root.copy_class(self.file, product=plate)
assert plate.HasOpenings[0] != new.HasOpenings[0] assert plate.HasOpenings[0] != new.HasOpenings[0]
assert plate.HasOpenings[0].RelatedOpeningElement == opening assert plate.HasOpenings[0].RelatedOpeningElement == opening
@@ -155,15 +155,15 @@ class TestCopyClass(test.bootstrap.IFC4):
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
window = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWindow") window = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWindow")
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
ifcopenshell.api.void.add_filling(self.file, opening=opening, element=window) ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=window)
new = ifcopenshell.api.root.copy_class(self.file, product=wall) new = ifcopenshell.api.root.copy_class(self.file, product=wall)
assert not new.HasOpenings assert not new.HasOpenings
def test_copying_an_opening_voiding_an_element(self): def test_copying_an_opening_voiding_an_element(self):
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
new = ifcopenshell.api.root.copy_class(self.file, product=opening) new = ifcopenshell.api.root.copy_class(self.file, product=opening)
assert opening.VoidsElements[0] != new.VoidsElements[0] assert opening.VoidsElements[0] != new.VoidsElements[0]
assert new.VoidsElements[0].RelatingBuildingElement == wall assert new.VoidsElements[0].RelatingBuildingElement == wall
@@ -171,14 +171,14 @@ class TestCopyClass(test.bootstrap.IFC4):
def test_copying_an_opening_with_a_filling(self): def test_copying_an_opening_with_a_filling(self):
door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.add_filling(self.file, opening=opening, element=door) ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=door)
new = ifcopenshell.api.root.copy_class(self.file, product=opening) new = ifcopenshell.api.root.copy_class(self.file, product=opening)
assert not new.HasFillings assert not new.HasFillings
def test_copying_a_filling(self): def test_copying_a_filling(self):
door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.add_filling(self.file, opening=opening, element=door) ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=door)
new = ifcopenshell.api.root.copy_class(self.file, product=door) new = ifcopenshell.api.root.copy_class(self.file, product=door)
assert not new.FillsVoids assert not new.FillsVoids
@@ -20,7 +20,7 @@ import test.bootstrap
import ifcopenshell.api.unit import ifcopenshell.api.unit
import ifcopenshell.api.nest import ifcopenshell.api.nest
import ifcopenshell.api.root import ifcopenshell.api.root
import ifcopenshell.api.void import ifcopenshell.api.feature
import ifcopenshell.api.grid import ifcopenshell.api.grid
import ifcopenshell.api.type import ifcopenshell.api.type
import ifcopenshell.api.pset import ifcopenshell.api.pset
@@ -159,7 +159,7 @@ class TestRemoveProduct(test.bootstrap.IFC4):
def test_removing_all_openings_of_an_element(self): def test_removing_all_openings_of_an_element(self):
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=element) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=element)
ifcopenshell.api.root.remove_product(self.file, product=element) ifcopenshell.api.root.remove_product(self.file, product=element)
assert len(list(self.file)) == 0 assert len(list(self.file)) == 0
assert len(self.file.by_type("IfcWall")) == 0 assert len(self.file.by_type("IfcWall")) == 0
@@ -180,7 +180,7 @@ class TestRemoveProduct(test.bootstrap.IFC4):
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
total_entities = len(list(self.file)) total_entities = len(list(self.file))
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=element) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=element)
ifcopenshell.api.root.remove_product(self.file, product=opening) ifcopenshell.api.root.remove_product(self.file, product=opening)
assert len(list(self.file)) == total_entities assert len(list(self.file)) == total_entities
assert len(self.file.by_type("IfcOpeningElement")) == 0 assert len(self.file.by_type("IfcOpeningElement")) == 0
@@ -191,8 +191,8 @@ class TestRemoveProduct(test.bootstrap.IFC4):
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
filling = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") filling = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor")
total_entities = len(list(self.file)) total_entities = len(list(self.file))
ifcopenshell.api.void.add_opening(self.file, opening=opening, element=element) ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=element)
ifcopenshell.api.void.add_filling(self.file, opening=opening, element=filling) ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=filling)
ifcopenshell.api.root.remove_product(self.file, product=filling) ifcopenshell.api.root.remove_product(self.file, product=filling)
assert len(list(self.file)) == total_entities assert len(list(self.file)) == total_entities
assert len(self.file.by_type("IfcDoor")) == 0 assert len(self.file.by_type("IfcDoor")) == 0
@@ -19,7 +19,7 @@
import ifcopenshell.api.profile import ifcopenshell.api.profile
import pytest import pytest
import test.bootstrap import test.bootstrap
import ifcopenshell.api.void import ifcopenshell.api.feature
import ifcopenshell.api.pset import ifcopenshell.api.pset
import ifcopenshell.api.root import ifcopenshell.api.root
import ifcopenshell.api.type import ifcopenshell.api.type
@@ -841,8 +841,8 @@ class TestGetDecompositionIFC4(test.bootstrap.IFC4):
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
subelement = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") subelement = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
subsubelement = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWindow") subsubelement = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWindow")
ifcopenshell.api.void.add_opening(self.file, element=element, opening=subelement) ifcopenshell.api.feature.add_feature(self.file, element=element, feature=subelement)
ifcopenshell.api.void.add_filling(self.file, element=subsubelement, opening=subelement) ifcopenshell.api.feature.add_filling(self.file, element=subsubelement, opening=subelement)
results = subject.get_decomposition(element) results = subject.get_decomposition(element)
assert subelement in results assert subelement in results
assert subsubelement in results assert subsubelement in results