mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
See #5919. API now has support for features, not just openings. Void module is renamed to feature.
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+9
-8
@@ -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
|
||||||
+2
-2
@@ -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}
|
||||||
|
|
||||||
+24
-18
@@ -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)
|
||||||
+2
-2
@@ -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 ()):
|
||||||
|
|||||||
+46
-31
@@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
+32
-11
@@ -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
|
||||||
+6
-6
@@ -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
|
||||||
|
|
||||||
+11
-11
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user