diff --git a/src/bonsai/bonsai/bim/module/model/opening.py b/src/bonsai/bonsai/bim/module/model/opening.py index f50f57bb03..5d147fe520 100644 --- a/src/bonsai/bonsai/bim/module/model/opening.py +++ b/src/bonsai/bonsai/bim/module/model/opening.py @@ -26,7 +26,7 @@ import numpy as np import ifcopenshell import ifcopenshell.api import ifcopenshell.api.geometry -import ifcopenshell.api.void +import ifcopenshell.api.feature import ifcopenshell.geom import ifcopenshell.util.shape import ifcopenshell.util.element @@ -69,7 +69,7 @@ class FilledOpeningGenerator: assert filling and element if filling.FillsVoids: 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: @@ -156,8 +156,8 @@ class FilledOpeningGenerator: "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("void.add_filling", tool.Ifc.get(), opening=opening, element=filling) + ifcopenshell.api.run("feature.add_feature", tool.Ifc.get(), feature=opening, element=element) + ifcopenshell.api.run("feature.add_filling", tool.Ifc.get(), opening=opening, element=filling) voided_objs = [voided_obj] # 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.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 # Update affected representations. diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index b848a23f5d..8f776a0b36 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -984,7 +984,7 @@ class DumbWallJoiner: _, opening_position = mathutils.geometry.intersect_point_line(opening_location.to_2d(), *axis1["reference"]) if opening_position > cut_percentage: # 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. for opening in [ @@ -996,7 +996,7 @@ class DumbWallJoiner: _, opening_position = mathutils.geometry.intersect_point_line(opening_location.to_2d(), *axis1["reference"]) if opening_position < cut_percentage: # 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 # only need to check fillings on the original element1. diff --git a/src/bonsai/bonsai/bim/module/void/operator.py b/src/bonsai/bonsai/bim/module/void/operator.py index e592b58810..067dc91332 100644 --- a/src/bonsai/bonsai/bim/module/void/operator.py +++ b/src/bonsai/bonsai/bim/module/void/operator.py @@ -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.") continue - # Sync placement before void.add_opening. + # Sync placement before feature.add_feature. if tool.Ifc.is_moved(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, 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): 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:]) 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.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: return {"FINISHED"} 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"} @@ -233,10 +233,9 @@ class RemoveFilling(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): filling = tool.Ifc.get().by_id(self.filling) - filling_obj = tool.Ifc.get_object(filling) for rel in filling.FillsVoids: 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"} diff --git a/src/bonsai/bonsai/tool/feature.py b/src/bonsai/bonsai/tool/feature.py index d8bcbfb162..e9a4d9f779 100644 --- a/src/bonsai/bonsai/tool/feature.py +++ b/src/bonsai/bonsai/tool/feature.py @@ -40,13 +40,13 @@ class Feature(bonsai.core.tool.Feature): for feature_obj in feature_objs: 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): bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=featured_obj) element_had_openings = tool.Geometry.has_openings(featured_element) 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): bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=feature_obj) diff --git a/src/bonsai/bonsai/tool/root.py b/src/bonsai/bonsai/tool/root.py index 7b64e173af..7d91716e08 100644 --- a/src/bonsai/bonsai/tool/root.py +++ b/src/bonsai/bonsai/tool/root.py @@ -290,8 +290,8 @@ class Root(bonsai.core.tool.Root): product=opening, representation=mapped_representation, ) - ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=opening, element=element) - ifcopenshell.api.run("void.add_filling", tool.Ifc.get(), opening=opening, element=filling) + ifcopenshell.api.run("feature.add_feature", tool.Ifc.get(), feature=opening, element=element) + ifcopenshell.api.run("feature.add_filling", tool.Ifc.get(), opening=opening, element=filling) voided_objs = [voided_obj] # Openings affect all subelements of an aggregate diff --git a/src/bonsai/test/tool/test_root.py b/src/bonsai/test/tool/test_root.py index 79b773d256..0d0e1fdce2 100644 --- a/src/bonsai/test/tool/test_root.py +++ b/src/bonsai/test/tool/test_root.py @@ -74,8 +74,8 @@ class TestGetDecompositionRelationships(NewFile): element = ifc.createIfcWall() opening = ifc.createIfcOpeningElement() fill = ifc.createIfcWindow() - ifcopenshell.api.run("void.add_opening", ifc, opening=opening, element=element) - ifcopenshell.api.run("void.add_filling", ifc, opening=opening, element=fill) + ifcopenshell.api.run("feature.add_feature", ifc, feature=opening, element=element) + ifcopenshell.api.run("feature.add_filling", ifc, opening=opening, element=fill) obj = bpy.data.objects.new("Object", None) tool.Ifc.link(fill, obj) diff --git a/src/ifcopenshell-python/ifcopenshell/api/void/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/feature/__init__.py similarity index 71% rename from src/ifcopenshell-python/ifcopenshell/api/void/__init__.py rename to src/ifcopenshell-python/ifcopenshell/api/feature/__init__.py index 0c5306e46f..41c7c27a6c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/void/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/feature/__init__.py @@ -16,25 +16,26 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . -"""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 -voids in other elements (such as walls and slabs). These voids may then be -filled with doors, trapdoors, skylights, and so on. +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 .add_opening import add_opening +from .remove_feature import remove_feature from .remove_filling import remove_filling -from .remove_opening import remove_opening wrap_usecases(__path__, __name__) __all__ = [ + "add_feature", "add_filling", - "add_opening", + "remove_feature", "remove_filling", - "remove_opening", ] diff --git a/src/ifcopenshell-python/ifcopenshell/api/feature/add_feature.py b/src/ifcopenshell-python/ifcopenshell/api/feature/add_feature.py new file mode 100644 index 0000000000..4eb3693d19 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/feature/add_feature.py @@ -0,0 +1,152 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# 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 . + +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 diff --git a/src/ifcopenshell-python/ifcopenshell/api/void/add_filling.py b/src/ifcopenshell-python/ifcopenshell/api/feature/add_filling.py similarity index 96% rename from src/ifcopenshell-python/ifcopenshell/api/void/add_filling.py rename to src/ifcopenshell-python/ifcopenshell/api/feature/add_filling.py index 0fb7d2e906..7f59d65ec8 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/void/add_filling.py +++ b/src/ifcopenshell-python/ifcopenshell/api/feature/add_filling.py @@ -82,7 +82,7 @@ def add_filling( ifcopenshell.api.geometry.edit_object_placement(model, product=opening, matrix=matrix) # 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 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) # 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} diff --git a/src/ifcopenshell-python/ifcopenshell/api/void/remove_opening.py b/src/ifcopenshell-python/ifcopenshell/api/feature/remove_feature.py similarity index 55% rename from src/ifcopenshell-python/ifcopenshell/api/void/remove_opening.py rename to src/ifcopenshell-python/ifcopenshell/api/feature/remove_feature.py index 21883bcec4..6a1d5e5610 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/void/remove_opening.py +++ b/src/ifcopenshell-python/ifcopenshell/api/feature/remove_feature.py @@ -17,44 +17,50 @@ # along with IfcOpenShell. If not, see . import ifcopenshell.api.root +import ifcopenshell.api.aggregate import ifcopenshell.util.element -def remove_opening(file: ifcopenshell.file, opening: ifcopenshell.entity_instance) -> None: - """Remove an opening +def remove_feature(file: ifcopenshell.file, feature: ifcopenshell.entity_instance) -> None: + """Remove a feature - Fillings are retained as orphans. Voided elements remain. Openings - cannot exist by themselves, so not only is the opening relationship - removed, the opening is also removed. + Fillings are retained as orphans. Featured elements remain. Features + cannot exist by themselves, so not only is the relationship removed, the + feature is also removed. - :param opening: The IfcOpeningElement to remove. - :type opening: ifcopenshell.entity_instance - :return: None - :rtype: None + :param feature: The IfcFeatureElement to remove. Example: .. 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 # 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. - ifcopenshell.api.void.remove_opening(model, opening=opening) + ifcopenshell.api.feature.remove_feature(model, feature=feature) """ - settings = {"opening": opening} - - for rel in settings["opening"].VoidsElements: + 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 settings["opening"].is_a("IfcOpeningElement"): - for rel in settings["opening"].HasFillings: + 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=settings["opening"]) + ifcopenshell.api.root.remove_product(file, product=feature) diff --git a/src/ifcopenshell-python/ifcopenshell/api/void/remove_filling.py b/src/ifcopenshell-python/ifcopenshell/api/feature/remove_filling.py similarity index 93% rename from src/ifcopenshell-python/ifcopenshell/api/void/remove_filling.py rename to src/ifcopenshell-python/ifcopenshell/api/feature/remove_filling.py index af6d28fc6d..f6931b4f9d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/void/remove_filling.py +++ b/src/ifcopenshell-python/ifcopenshell/api/feature/remove_filling.py @@ -47,10 +47,10 @@ def remove_filling(file: ifcopenshell.file, element: ifcopenshell.entity_instanc door = ifcopenshell.api.root.create_entity(model, ifc_class="IfcDoor") # 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! - ifcopenshell.api.void.remove_filling(model, element=door) + ifcopenshell.api.feature.remove_filling(model, element=door) """ settings = {"element": element} diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py index 77fbc4264d..2df1b4eaa5 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py @@ -18,7 +18,7 @@ import ifcopenshell.api.type import ifcopenshell.api.grid -import ifcopenshell.api.void +import ifcopenshell.api.feature import ifcopenshell.api.root import ifcopenshell.api.pset 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}) 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"): for axis in settings["product"].UAxes + settings["product"].VAxes + (settings["product"].WAxes or ()): diff --git a/src/ifcopenshell-python/ifcopenshell/api/void/add_opening.py b/src/ifcopenshell-python/ifcopenshell/api/void/add_feature.py similarity index 69% rename from src/ifcopenshell-python/ifcopenshell/api/void/add_opening.py rename to src/ifcopenshell-python/ifcopenshell/api/void/add_feature.py index 8c739ff0e4..7edc696fae 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/void/add_opening.py +++ b/src/ifcopenshell-python/ifcopenshell/api/void/add_feature.py @@ -19,19 +19,24 @@ 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_opening( - file: ifcopenshell.file, opening: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance +def add_feature( + file: ifcopenshell.file, feature: ifcopenshell.entity_instance, element: 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 - slabs to make space to insert doors, windows, and other services that go - through these penetrations. + 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. @@ -51,9 +56,9 @@ def add_opening( distinct opening object) or non-semantic (i.e. should simply be booleaned or be part of the shape of the object). - :param opening: The IfcOpeningElement to cut out the element. - :type opening: ifcopenshell.entity_instance - :param element: The IfcElement to insert the opening into. + :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 @@ -101,36 +106,46 @@ def add_opening( ifcopenshell.api.geometry.edit_object_placement(model, product=opening, matrix=matrix) # 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 voids_elements: - if voids_elements[0].RelatingBuildingElement == settings["element"]: - return voids_elements[0] - history = voids_elements[0].OwnerHistory - file.remove(voids_elements[0]) - if history: - ifcopenshell.util.element.remove_deep2(file, history) + 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( - "IfcRelVoidsElement", - **{ - "GlobalId": ifcopenshell.guid.new(), - "OwnerHistory": ifcopenshell.api.owner.create_owner_history(file), - "RelatingBuildingElement": settings["element"], - "RelatedOpeningElement": settings["opening"], - } + ifc_class, + ifcopenshell.guid.new(), + ifcopenshell.api.owner.create_owner_history(file), + None, + None, + element, + [feature] if ifc_class == "IfcRelAdheresToElement" else feature, ) - placement = getattr(settings["opening"], "ObjectPlacement", None) - if placement and placement.is_a("IfcLocalPlacement"): + if (placement := feature.ObjectPlacement) and placement.is_a("IfcLocalPlacement"): ifcopenshell.api.geometry.edit_object_placement( file, - product=settings["opening"], - matrix=ifcopenshell.util.placement.get_local_placement(settings["opening"].ObjectPlacement), + product=feature, + matrix=ifcopenshell.util.placement.get_local_placement(placement), is_si=False, ) diff --git a/src/ifcopenshell-python/test/api/void/__init__.py b/src/ifcopenshell-python/test/api/feature/__init__.py similarity index 100% rename from src/ifcopenshell-python/test/api/void/__init__.py rename to src/ifcopenshell-python/test/api/feature/__init__.py diff --git a/src/ifcopenshell-python/test/api/void/test_add_opening.py b/src/ifcopenshell-python/test/api/feature/test_add_feature.py similarity index 63% rename from src/ifcopenshell-python/test/api/void/test_add_opening.py rename to src/ifcopenshell-python/test/api/feature/test_add_feature.py index d1abe2aeea..81ed56ff19 100644 --- a/src/ifcopenshell-python/test/api/void/test_add_opening.py +++ b/src/ifcopenshell-python/test/api/feature/test_add_feature.py @@ -18,24 +18,44 @@ import numpy import test.bootstrap -import ifcopenshell.api.void +import ifcopenshell.api.feature import ifcopenshell.api.root import ifcopenshell.api.unit 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): wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") 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 + 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): wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") - ifcopenshell.api.void.add_opening(self.file, opening=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) + ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall) assert wall.HasOpenings[0].RelatedOpeningElement == opening 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") wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") - ifcopenshell.api.void.add_opening(self.file, opening=opening, element=slab) - ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) + ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=slab) + ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall) assert not slab.HasOpenings 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=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 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") placement = self.file.createIfcGridPlacement() 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 -class TestAddOpeningIFC2X3(test.bootstrap.IFC2X3, TestAddOpening): - pass +class TestAddFeatureIFC2X3(test.bootstrap.IFC2X3, TestAddFeature): + def test_adding_a_surface_feature(self): + pass diff --git a/src/ifcopenshell-python/test/api/void/test_add_filling.py b/src/ifcopenshell-python/test/api/feature/test_add_filling.py similarity index 81% rename from src/ifcopenshell-python/test/api/void/test_add_filling.py rename to src/ifcopenshell-python/test/api/feature/test_add_filling.py index 7e0e2edabe..49259d75ab 100644 --- a/src/ifcopenshell-python/test/api/void/test_add_filling.py +++ b/src/ifcopenshell-python/test/api/feature/test_add_filling.py @@ -17,7 +17,7 @@ # along with IfcOpenShell. If not, see . import test.bootstrap -import ifcopenshell.api.void +import ifcopenshell.api.feature import ifcopenshell.api.root @@ -25,14 +25,14 @@ class TestAddFilling(test.bootstrap.IFC4): def test_adding_a_filling(self): opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") 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 def test_adding_a_filling_twice(self): opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") - ifcopenshell.api.void.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) + ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=door) assert door.FillsVoids[0].RelatingOpeningElement == opening 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") opening1 = 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.void.add_filling(self.file, opening=opening2, element=door) + ifcopenshell.api.feature.add_filling(self.file, opening=opening1, element=door) + ifcopenshell.api.feature.add_filling(self.file, opening=opening2, element=door) assert not opening1.HasFillings assert opening2.HasFillings[0].RelatedBuildingElement == door diff --git a/src/ifcopenshell-python/test/api/void/test_remove_opening.py b/src/ifcopenshell-python/test/api/feature/test_remove_feature.py similarity index 71% rename from src/ifcopenshell-python/test/api/void/test_remove_opening.py rename to src/ifcopenshell-python/test/api/feature/test_remove_feature.py index 02beb80b23..5275639525 100644 --- a/src/ifcopenshell-python/test/api/void/test_remove_opening.py +++ b/src/ifcopenshell-python/test/api/feature/test_remove_feature.py @@ -17,21 +17,21 @@ # along with IfcOpenShell. If not, see . import test.bootstrap -import ifcopenshell.api.void +import ifcopenshell.api.feature import ifcopenshell.api.root -class TestRemoveOpening(test.bootstrap.IFC4): - def test_removing_a_simple_opening(self): - opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") - ifcopenshell.api.void.remove_opening(self.file, opening=opening) +class TestRemoveFeature(test.bootstrap.IFC4): + def test_removing_a_simple_feature(self): + feature = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.feature.remove_feature(self.file, feature=feature) assert len(list(self.file)) == 0 def test_removing_an_opening_voiding_a_wall(self): wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") - ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) - ifcopenshell.api.void.remove_opening(self.file, opening=opening) + ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall) + ifcopenshell.api.feature.remove_feature(self.file, feature=opening) assert len(self.file.by_type("IfcOpeningElement")) == 0 assert len(self.file.by_type("IfcRelVoidsElement")) == 0 assert wall @@ -40,9 +40,9 @@ class TestRemoveOpening(test.bootstrap.IFC4): wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") - ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) - ifcopenshell.api.void.add_filling(self.file, opening=opening, element=door) - ifcopenshell.api.void.remove_opening(self.file, opening=opening) + ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall) + ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=door) + ifcopenshell.api.feature.remove_feature(self.file, feature=opening) assert len(self.file.by_type("IfcOpeningElement")) == 0 assert len(self.file.by_type("IfcRelVoidsElement")) == 0 assert len(self.file.by_type("IfcRelFillsElement")) == 0 @@ -50,5 +50,5 @@ class TestRemoveOpening(test.bootstrap.IFC4): assert door -class TestRemoveOpeningIFC2X3(test.bootstrap.IFC2X3, TestRemoveOpening): +class TestRemoveFeatureIFC2X3(test.bootstrap.IFC2X3, TestRemoveFeature): pass diff --git a/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py b/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py index 7c6bb46b60..70dcbf7285 100644 --- a/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py +++ b/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py @@ -19,7 +19,7 @@ import numpy import pytest import test.bootstrap -import ifcopenshell.api.void +import ifcopenshell.api.feature import ifcopenshell.api.root import ifcopenshell.api.unit import ifcopenshell.api.system @@ -303,7 +303,7 @@ class TestEditObjectPlacement(test.bootstrap.IFC4): (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=subelement, matrix=submatrix.copy(), is_si=False @@ -337,8 +337,8 @@ class TestEditObjectPlacement(test.bootstrap.IFC4): (0.0, 0.0, 0.0, 1.0), ) ) - ifcopenshell.api.void.add_opening(self.file, opening=element, element=wall) - ifcopenshell.api.void.add_filling(self.file, element=subelement, opening=element) + ifcopenshell.api.feature.add_feature(self.file, feature=element, element=wall) + 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=wall, matrix=numpy.eye(4), 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[:3, 3] = (1, 3, 5) - ifcopenshell.api.void.add_opening(self.file, opening=subelement, element=element) - ifcopenshell.api.void.add_filling(self.file, opening=subelement, element=subsubelement) + ifcopenshell.api.feature.add_feature(self.file, feature=subelement, element=element) + ifcopenshell.api.feature.add_filling(self.file, opening=subelement, element=subsubelement) previous_placement_id = ifcopenshell.api.geometry.edit_object_placement( self.file, product=element, matrix=matrix.copy(), is_si=False ).id() diff --git a/src/ifcopenshell-python/test/api/root/test_copy_class.py b/src/ifcopenshell-python/test/api/root/test_copy_class.py index 01175eb762..66ba27ec06 100644 --- a/src/ifcopenshell-python/test/api/root/test_copy_class.py +++ b/src/ifcopenshell-python/test/api/root/test_copy_class.py @@ -21,7 +21,7 @@ import test.bootstrap import ifcopenshell.api.unit import ifcopenshell.api.type import ifcopenshell.api.root -import ifcopenshell.api.void +import ifcopenshell.api.feature import ifcopenshell.api.pset import ifcopenshell.api.group import ifcopenshell.api.system @@ -134,7 +134,7 @@ class TestCopyClass(test.bootstrap.IFC4): # IfcOpeningElement opening wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") 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) assert wall.HasOpenings[0] != new.HasOpenings[0] assert wall.HasOpenings[0].RelatedOpeningElement == opening @@ -144,7 +144,7 @@ class TestCopyClass(test.bootstrap.IFC4): # IfcVoidingFeature opening plate = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcPlate") 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) assert plate.HasOpenings[0] != new.HasOpenings[0] 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") opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") window = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWindow") - ifcopenshell.api.void.add_opening(self.file, opening=opening, element=wall) - ifcopenshell.api.void.add_filling(self.file, opening=opening, element=window) + ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall) + ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=window) new = ifcopenshell.api.root.copy_class(self.file, product=wall) assert not new.HasOpenings def test_copying_an_opening_voiding_an_element(self): wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") 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) assert opening.VoidsElements[0] != new.VoidsElements[0] assert new.VoidsElements[0].RelatingBuildingElement == wall @@ -171,14 +171,14 @@ class TestCopyClass(test.bootstrap.IFC4): def test_copying_an_opening_with_a_filling(self): door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") 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) assert not new.HasFillings def test_copying_a_filling(self): door = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") 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) assert not new.FillsVoids diff --git a/src/ifcopenshell-python/test/api/root/test_remove_product.py b/src/ifcopenshell-python/test/api/root/test_remove_product.py index 7b6cef8295..da9b46dbe5 100644 --- a/src/ifcopenshell-python/test/api/root/test_remove_product.py +++ b/src/ifcopenshell-python/test/api/root/test_remove_product.py @@ -20,7 +20,7 @@ import test.bootstrap import ifcopenshell.api.unit import ifcopenshell.api.nest import ifcopenshell.api.root -import ifcopenshell.api.void +import ifcopenshell.api.feature import ifcopenshell.api.grid import ifcopenshell.api.type import ifcopenshell.api.pset @@ -159,7 +159,7 @@ class TestRemoveProduct(test.bootstrap.IFC4): def test_removing_all_openings_of_an_element(self): element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") 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) assert len(list(self.file)) == 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") total_entities = len(list(self.file)) 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) assert len(list(self.file)) == total_entities 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") filling = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcDoor") total_entities = len(list(self.file)) - ifcopenshell.api.void.add_opening(self.file, opening=opening, element=element) - ifcopenshell.api.void.add_filling(self.file, opening=opening, element=filling) + ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=element) + ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=filling) ifcopenshell.api.root.remove_product(self.file, product=filling) assert len(list(self.file)) == total_entities assert len(self.file.by_type("IfcDoor")) == 0 diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index 6ebba1b82a..1ab002f7b0 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -19,7 +19,7 @@ import ifcopenshell.api.profile import pytest import test.bootstrap -import ifcopenshell.api.void +import ifcopenshell.api.feature import ifcopenshell.api.pset import ifcopenshell.api.root import ifcopenshell.api.type @@ -841,8 +841,8 @@ class TestGetDecompositionIFC4(test.bootstrap.IFC4): element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") subelement = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") subsubelement = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWindow") - ifcopenshell.api.void.add_opening(self.file, element=element, opening=subelement) - ifcopenshell.api.void.add_filling(self.file, element=subsubelement, opening=subelement) + ifcopenshell.api.feature.add_feature(self.file, element=element, feature=subelement) + ifcopenshell.api.feature.add_filling(self.file, element=subsubelement, opening=subelement) results = subject.get_decomposition(element) assert subelement in results assert subsubelement in results