Merge remote-tracking branch 'origin/v0.8.0' into datamodel-v1.0

This commit is contained in:
Thomas Krijnen
2026-04-18 20:15:28 +02:00
1655 changed files with 105534 additions and 46934 deletions
@@ -18,14 +18,15 @@
import numpy
import pytest
import test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.unit
import ifcopenshell.api.spatial
import ifcopenshell.api.geometry
import ifcopenshell.api.aggregate
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import ifcopenshell.api.spatial
import ifcopenshell.api.unit
import ifcopenshell.util.element
import ifcopenshell.util.placement
import test.bootstrap
class TestAssignObject(test.bootstrap.IFC4):
@@ -16,10 +16,10 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.aggregate
import ifcopenshell.api.root
import ifcopenshell.util.element
import test.bootstrap
class TestUnassignObject(test.bootstrap.IFC4):
@@ -16,11 +16,10 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
from ifcopenshell.api.alignment._add_segment_to_layout import _add_segment_to_layout
@@ -16,7 +16,7 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -16,7 +16,7 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -16,7 +16,7 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -16,12 +16,12 @@
# 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.util
import ifcopenshell.util.unit
import pytest
import math
import ifcopenshell.api.alignment
import ifcopenshell.api.unit
import ifcopenshell.util
import ifcopenshell.util.unit
def test_create_as_offset_curve():
@@ -16,7 +16,7 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.unit
@@ -16,7 +16,7 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -16,11 +16,11 @@
# 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 pytest
import math
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
import math
def _test_horizontal() -> ifcopenshell.file:
@@ -16,9 +16,8 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -17,6 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -16,7 +16,7 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -16,7 +16,6 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
@@ -16,7 +16,7 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -17,9 +17,11 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
from ifcopenshell.api.alignment._map_alignment_cant_segment import _map_alignment_cant_segment
from ifcopenshell.api.alignment._map_alignment_cant_segment import (
_map_alignment_cant_segment,
)
def _BlossCurve_100_0_300_1000_1_Meter(file):
@@ -20,9 +20,12 @@
# for horizontal alignment.
import pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.unit
from ifcopenshell.api.alignment._map_alignment_horizontal_segment import _map_alignment_horizontal_segment
from ifcopenshell.api.alignment._map_alignment_horizontal_segment import (
_map_alignment_horizontal_segment,
)
def _BlossCurve_100_0_300_1000_1_Meter(file):
@@ -20,8 +20,11 @@
# for vertical alignment.
import pytest
import ifcopenshell.api.alignment
from ifcopenshell.api.alignment._map_alignment_vertical_segment import _map_alignment_vertical_segment
from ifcopenshell.api.alignment._map_alignment_vertical_segment import (
_map_alignment_vertical_segment,
)
def _CircularArc_100_0_10_0_0_0_0_5_1_Meter(file):
@@ -16,7 +16,7 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -17,7 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
from pytest import fixture
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -16,10 +16,12 @@
# 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 pytest
from ifcopenshell.api.alignment._update_curve_segment_transition_code import _update_curve_segment_transition_code
import ifcopenshell.api.context
import ifcopenshell.api.unit
from ifcopenshell.api.alignment._update_curve_segment_transition_code import (
_update_curve_segment_transition_code,
)
def _test1():
@@ -17,6 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -16,7 +16,7 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.unit
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.boundary
import ifcopenshell.api.root
import test.bootstrap
class TestCopyBoundary(test.bootstrap.IFC4):
@@ -0,0 +1,115 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2026 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/>.
# This file was generated with the assistance of an AI coding tool.
import ifcopenshell.api.boundary
import ifcopenshell.api.root
import test.bootstrap
class TestEditAttributes(test.bootstrap.IFC4):
def setup_boundary(self):
space = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSpace")
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
boundary = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcRelSpaceBoundary")
return boundary, space, wall
def test_sets_relating_space_and_building_element(self):
boundary, space, wall = self.setup_boundary()
ifcopenshell.api.boundary.edit_attributes(
self.file, entity=boundary, relating_space=space, related_building_element=wall
)
assert boundary.RelatingSpace == space
assert boundary.RelatedBuildingElement == wall
def test_defaults_enums_to_notdefined(self):
boundary, space, wall = self.setup_boundary()
ifcopenshell.api.boundary.edit_attributes(
self.file, entity=boundary, relating_space=space, related_building_element=wall
)
assert boundary.PhysicalOrVirtualBoundary == "NOTDEFINED"
assert boundary.InternalOrExternalBoundary == "NOTDEFINED"
def test_sets_physical_or_virtual(self):
boundary, space, wall = self.setup_boundary()
ifcopenshell.api.boundary.edit_attributes(
self.file,
entity=boundary,
relating_space=space,
related_building_element=wall,
physical_or_virtual="PHYSICAL",
)
assert boundary.PhysicalOrVirtualBoundary == "PHYSICAL"
def test_sets_internal_or_external(self):
boundary, space, wall = self.setup_boundary()
ifcopenshell.api.boundary.edit_attributes(
self.file,
entity=boundary,
relating_space=space,
related_building_element=wall,
internal_or_external="EXTERNAL",
)
assert boundary.InternalOrExternalBoundary == "EXTERNAL"
def test_sets_all_enum_variants(self):
boundary, space, wall = self.setup_boundary()
for value in ("PHYSICAL", "VIRTUAL", "NOTDEFINED"):
ifcopenshell.api.boundary.edit_attributes(
self.file,
entity=boundary,
relating_space=space,
related_building_element=wall,
physical_or_virtual=value,
)
assert boundary.PhysicalOrVirtualBoundary == value
for value in ("INTERNAL", "EXTERNAL", "EXTERNAL_EARTH", "EXTERNAL_WATER", "EXTERNAL_FIRE", "NOTDEFINED"):
ifcopenshell.api.boundary.edit_attributes(
self.file,
entity=boundary,
relating_space=space,
related_building_element=wall,
internal_or_external=value,
)
assert boundary.InternalOrExternalBoundary == value
class TestEditAttributesIFC2X3(test.bootstrap.IFC2X3, TestEditAttributes):
def test_sets_all_enum_variants(self):
boundary, space, wall = self.setup_boundary()
for value in ("PHYSICAL", "VIRTUAL", "NOTDEFINED"):
ifcopenshell.api.boundary.edit_attributes(
self.file,
entity=boundary,
relating_space=space,
related_building_element=wall,
physical_or_virtual=value,
)
assert boundary.PhysicalOrVirtualBoundary == value
# IFC2X3 only has INTERNAL, EXTERNAL, NOTDEFINED
for value in ("INTERNAL", "EXTERNAL", "NOTDEFINED"):
ifcopenshell.api.boundary.edit_attributes(
self.file,
entity=boundary,
relating_space=space,
related_building_element=wall,
internal_or_external=value,
)
assert boundary.InternalOrExternalBoundary == value
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.boundary
import ifcopenshell.api.root
import test.bootstrap
class TestRemoveBoundary(test.bootstrap.IFC4):
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.classification
import ifcopenshell.api.root
import test.bootstrap
class TestAddClassification(test.bootstrap.IFC4):
@@ -17,10 +17,11 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.classification
import ifcopenshell.api.root
import ifcopenshell.util.classification
import test.bootstrap
class TestAddReference(test.bootstrap.IFC4):
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.classification
import ifcopenshell.api.root
import test.bootstrap
class TestRemoveClassification(test.bootstrap.IFC4):
@@ -17,10 +17,11 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.classification
import ifcopenshell.api.root
import ifcopenshell.util.classification
import test.bootstrap
class TestRemoveReference(test.bootstrap.IFC4):
@@ -17,10 +17,10 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import ifcopenshell.api.aggregate
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.cogo
import ifcopenshell.api.context
def test_add_survey_point():
@@ -17,10 +17,10 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import ifcopenshell.api.aggregate
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.cogo
import ifcopenshell.api.context
def test_assign_survey_point():
@@ -16,9 +16,10 @@
# 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 pytest
import ifcopenshell.api.cogo
import math
def test_bearing2dd():
@@ -17,10 +17,10 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import ifcopenshell.api.aggregate
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.cogo
import ifcopenshell.api.context
def test_edit_survey_point():
@@ -16,10 +16,10 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.constraint
import ifcopenshell.api.root
import ifcopenshell.util.constraint
import test.bootstrap
class TestAssignConstraint(test.bootstrap.IFC4):
@@ -16,10 +16,10 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.constraint
import ifcopenshell.api.root
import ifcopenshell.util.constraint
import test.bootstrap
class TestUnassignConstraint(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.context
import test.bootstrap
class TestAddContext(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.context
import test.bootstrap
class TestEditContext(test.bootstrap.IFC4):
@@ -16,10 +16,10 @@
# 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 test.bootstrap
import ifcopenshell.api.context
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import test.bootstrap
class TestRemoveContext(test.bootstrap.IFC4):
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.cost
import ifcopenshell.api.control
import ifcopenshell.api.cost
import test.bootstrap
class TestAssignControl(test.bootstrap.IFC4):
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.cost
import ifcopenshell.api.control
import ifcopenshell.api.cost
import test.bootstrap
class TestUnassignControl(test.bootstrap.IFC4):
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.cost
import ifcopenshell.util.element
import test.bootstrap
class TestAddCostItem(test.bootstrap.IFC4):
@@ -16,10 +16,10 @@
# 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 test.bootstrap
import ifcopenshell.api.control
import ifcopenshell.api.cost
import ifcopenshell.api.root
import ifcopenshell.api.control
import test.bootstrap
class TestAddCostItemQuantity(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.cost
import test.bootstrap
class TestAddCostSchedule(test.bootstrap.IFC4):
@@ -0,0 +1,70 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.api.cost
import ifcopenshell.api.unit
import test.bootstrap
class TestEditCostValue(test.bootstrap.IFC4):
def test_editing_applied_value(self):
schedule = ifcopenshell.api.cost.add_cost_schedule(self.file)
item = ifcopenshell.api.cost.add_cost_item(self.file, cost_schedule=schedule)
value = ifcopenshell.api.cost.add_cost_value(self.file, parent=item)
ifcopenshell.api.cost.edit_cost_value(self.file, cost_value=value, attributes={"AppliedValue": 42.0})
assert value.AppliedValue.wrappedValue == 42.0
def test_editing_unit_basis_removes_old_deeply(self):
schedule = ifcopenshell.api.cost.add_cost_schedule(self.file)
item = ifcopenshell.api.cost.add_cost_item(self.file, cost_schedule=schedule)
value = ifcopenshell.api.cost.add_cost_value(self.file, parent=item)
unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT")
ifcopenshell.api.cost.edit_cost_value(
self.file,
cost_value=value,
attributes={"UnitBasis": {"ValueComponent": 1.0, "UnitComponent": unit}},
)
old_basis = value.UnitBasis
assert old_basis is not None
old_basis_id = old_basis.id()
# Now change to a new unit basis — the old one should be deeply removed.
ifcopenshell.api.cost.edit_cost_value(
self.file,
cost_value=value,
attributes={"UnitBasis": {"ValueComponent": 2.0, "UnitComponent": unit}},
)
assert value.UnitBasis is not None
assert value.UnitBasis.id() != old_basis_id
def test_clearing_unit_basis(self):
schedule = ifcopenshell.api.cost.add_cost_schedule(self.file)
item = ifcopenshell.api.cost.add_cost_item(self.file, cost_schedule=schedule)
value = ifcopenshell.api.cost.add_cost_value(self.file, parent=item)
unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT")
ifcopenshell.api.cost.edit_cost_value(
self.file,
cost_value=value,
attributes={"UnitBasis": {"ValueComponent": 1.0, "UnitComponent": unit}},
)
assert value.UnitBasis is not None
ifcopenshell.api.cost.edit_cost_value(self.file, cost_value=value, attributes={"UnitBasis": None})
assert value.UnitBasis is None
class TestEditCostValueIFC4X3(test.bootstrap.IFC4X3, TestEditCostValue):
pass
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.cost
import test.bootstrap
class TestRemoveCostItem(test.bootstrap.IFC4):
@@ -16,9 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api
import ifcopenshell.api.cost
import test.bootstrap
class TestRemoveCostSchedule(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.document
import test.bootstrap
class TestAddInformation(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.document
import test.bootstrap
class TestAddReference(test.bootstrap.IFC4):
@@ -16,10 +16,10 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.document
import ifcopenshell.api.root
import ifcopenshell.util.element
import test.bootstrap
class TestAssignDocument(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.document
import test.bootstrap
class TestRemoveInformation(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.document
import test.bootstrap
class TestRemoveReference(test.bootstrap.IFC4):
@@ -16,10 +16,10 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.document
import ifcopenshell.api.root
import ifcopenshell.util.element
import test.bootstrap
class TestUnassignDocument(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.drawing
import test.bootstrap
class TestAssignProduct(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.drawing
import test.bootstrap
class TestEditTextLiteral(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.drawing
import test.bootstrap
class TestUnassignProduct(test.bootstrap.IFC4):
@@ -17,12 +17,13 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import numpy
import test.bootstrap
import ifcopenshell.api.feature
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import ifcopenshell.api.unit
import ifcopenshell.api.geometry
import ifcopenshell.util.placement
import test.bootstrap
class TestAddFeature(test.bootstrap.IFC4X3):
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.feature
import ifcopenshell.api.root
import test.bootstrap
class TestAddFilling(test.bootstrap.IFC4):
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.feature
import ifcopenshell.api.root
import test.bootstrap
class TestRemoveFeature(test.bootstrap.IFC4):
@@ -16,11 +16,11 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.context
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import ifcopenshell.util.shape_builder
import test.bootstrap
class TestAddBoolean(test.bootstrap.IFC4):
@@ -42,7 +42,7 @@ class TestAddBoolean(test.bootstrap.IFC4):
assert boolean.FirstOperand == first
assert boolean.SecondOperand == second
assert boolean.Operator == "DIFFERENCE"
assert set(rep.Items) == {boolean}
assert set(rep.Items) == {boolean, second}
def test_adding_multiple_booleans_from_three_top_level_items(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
@@ -58,13 +58,14 @@ class TestAddBoolean(test.bootstrap.IFC4):
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1, second2])
assert len(booleans) == 2
assert len(rep.Items) == 1
assert rep.Items[0].FirstOperand.is_a("IfcBooleanResult")
assert rep.Items[0].SecondOperand == second2
assert rep.Items[0].Operator == "DIFFERENCE"
assert rep.Items[0].FirstOperand.FirstOperand == first
assert rep.Items[0].FirstOperand.SecondOperand == second1
assert rep.Items[0].FirstOperand.Operator == "DIFFERENCE"
final_boolean = booleans[-1]
assert final_boolean.FirstOperand.is_a("IfcBooleanResult")
assert final_boolean.SecondOperand == second2
assert final_boolean.Operator == "DIFFERENCE"
assert final_boolean.FirstOperand.FirstOperand == first
assert final_boolean.FirstOperand.SecondOperand == second1
assert final_boolean.FirstOperand.Operator == "DIFFERENCE"
assert set(rep.Items) == {final_boolean, second1, second2}
def test_adding_a_boolean_to_an_existing_operand_from_a_top_level_item(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
@@ -78,14 +79,16 @@ class TestAddBoolean(test.bootstrap.IFC4):
second2 = builder.block()
rep = builder.get_representation(body, [first, second1])
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1])
# second1 stays in Items, add second2 as well
rep.Items = list(rep.Items) + [second2]
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second2])
assert len(booleans) == 1
assert len(rep.Items) == 1
assert rep.Items[0].FirstOperand.is_a("IfcBooleanResult")
assert rep.Items[0].SecondOperand == second2
assert rep.Items[0].FirstOperand.FirstOperand == first
assert rep.Items[0].FirstOperand.SecondOperand == second1
final_boolean = booleans[0]
assert final_boolean.FirstOperand.is_a("IfcBooleanResult")
assert final_boolean.SecondOperand == second2
assert final_boolean.FirstOperand.FirstOperand == first
assert final_boolean.FirstOperand.SecondOperand == second1
assert set(rep.Items) == {final_boolean, second1, second2}
def test_adding_a_boolean_to_an_existing_operand_from_another_operand(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
@@ -104,7 +107,7 @@ class TestAddBoolean(test.bootstrap.IFC4):
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first1, [second2])
assert len(booleans) == 1
assert len(rep.Items) == 2
assert len(rep.Items) == 4
assert self.file.get_total_inverses(first1) == 1
result = next(iter(self.file.get_inverse(first1)))
@@ -132,14 +135,15 @@ class TestAddBoolean(test.bootstrap.IFC4):
rep = builder.get_representation(body, [first, second])
ifcopenshell.api.geometry.add_boolean(self.file, first, [second])
ifcopenshell.api.geometry.add_boolean(self.file, first, [second])
assert len(rep.Items) == 1
assert rep.Items[0].FirstOperand == first
assert rep.Items[0].SecondOperand == second
assert set(rep.Items) == {self.file.by_type("IfcBooleanResult")[0], second}
boolean = self.file.by_type("IfcBooleanResult")[0]
assert boolean.FirstOperand == first
assert boolean.SecondOperand == second
ifcopenshell.api.geometry.add_boolean(self.file, second, [second])
ifcopenshell.api.geometry.add_boolean(self.file, second, [first])
assert len(rep.Items) == 1
assert rep.Items[0].FirstOperand == first
assert rep.Items[0].SecondOperand == second
assert set(rep.Items) == {boolean, second}
assert boolean.FirstOperand == first
assert boolean.SecondOperand == second
assert len(self.file.by_type("IfcBooleanResult")) == 1
@@ -16,11 +16,11 @@
# 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 test.bootstrap
import ifcopenshell.api.unit
import ifcopenshell.api.root
import ifcopenshell.api.context
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import ifcopenshell.api.unit
import test.bootstrap
from ifcopenshell.util.shape_builder import ShapeBuilder
@@ -16,11 +16,11 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.context
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import ifcopenshell.util.shape_builder
import test.bootstrap
class TestAddShapeAspect(test.bootstrap.IFC4):
@@ -0,0 +1,82 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2026 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/>.
# This file was generated with the assistance of an AI coding tool.
import ifcopenshell.api.context
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import test.bootstrap
class TestAddTopologyRepresentation(test.bootstrap.IFC4):
def setup_context(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
model = ifcopenshell.api.context.add_context(self.file, context_type="Model")
return ifcopenshell.api.context.add_context(
self.file,
context_type="Model",
context_identifier="Reference",
target_view="GRAPH_VIEW",
parent=model,
)
def test_creates_topology_representation(self):
context = self.setup_context()
face = self.file.create_entity("IfcFaceSurface")
rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=face)
assert rep.is_a("IfcTopologyRepresentation")
assert rep.ContextOfItems == context
assert face in rep.Items
def test_infers_face_representation_type(self):
context = self.setup_context()
face = self.file.create_entity("IfcFaceSurface")
rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=face)
assert rep.RepresentationType == "Face"
def test_infers_edge_representation_type(self):
context = self.setup_context()
edge = self.file.create_entity("IfcEdge")
rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=edge)
assert rep.RepresentationType == "Edge"
def test_defaults_representation_identifier_to_context_identifier(self):
context = self.setup_context()
face = self.file.create_entity("IfcFaceSurface")
rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=face)
assert rep.RepresentationIdentifier == context.ContextIdentifier
def test_custom_representation_identifier(self):
context = self.setup_context()
face = self.file.create_entity("IfcFaceSurface")
rep = ifcopenshell.api.geometry.add_topology_representation(
self.file, context=context, item=face, representation_identifier="Body"
)
assert rep.RepresentationIdentifier == "Body"
def test_custom_representation_type_overrides_inferred(self):
context = self.setup_context()
face = self.file.create_entity("IfcFaceSurface")
rep = ifcopenshell.api.geometry.add_topology_representation(
self.file, context=context, item=face, representation_type="Undefined"
)
assert rep.RepresentationType == "Undefined"
class TestAddTopologyRepresentationIFC2X3(test.bootstrap.IFC2X3, TestAddTopologyRepresentation):
pass
@@ -16,12 +16,11 @@
# 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 test.bootstrap
import ifcopenshell.api
import ifcopenshell.api.material
import ifcopenshell.api.type
import ifcopenshell.api.root
import ifcopenshell.api.geometry
import ifcopenshell.api.material
import ifcopenshell.api.root
import ifcopenshell.api.type
import test.bootstrap
class TestAssignRepresentation(test.bootstrap.IFC4):
@@ -0,0 +1,154 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2026 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 json
import ifcopenshell.api.geometry
import ifcopenshell.util.element
import ifcopenshell.util.shape_builder
import test.bootstrap
class TestClipSolid(test.bootstrap.IFC4):
def make_extrusion(self):
builder = ifcopenshell.util.shape_builder.ShapeBuilder(self.file)
rect = builder.rectangle(size=(1.0, 1.0))
return builder.extrude(rect, magnitude=4.0)
def test_returns_boolean_clipping_result(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid(
self.file,
item=extrusion,
location=[0.0, 0.0, 3.0],
normal=[0.0, 0.0, 1.0],
)
assert result.is_a("IfcBooleanClippingResult")
assert result.Operator == "DIFFERENCE"
def test_first_operand_is_the_item(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid(
self.file,
item=extrusion,
location=[0.0, 0.0, 3.0],
normal=[0.0, 0.0, 1.0],
)
assert result.FirstOperand == extrusion
def test_second_operand_is_half_space_solid(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid(
self.file,
item=extrusion,
location=[0.0, 0.0, 3.0],
normal=[0.0, 0.0, 1.0],
)
assert result.SecondOperand.is_a("IfcHalfSpaceSolid")
def test_clip_plane_location_matches(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid(
self.file,
item=extrusion,
location=[0.0, 0.0, 3.0],
normal=[0.0, 0.0, 1.0],
)
plane = result.SecondOperand.BaseSurface
coords = plane.Position.Location.Coordinates
assert list(coords) == [0.0, 0.0, 3.0]
def test_chaining_two_clips(self):
extrusion = self.make_extrusion()
first_clip = ifcopenshell.api.geometry.clip_solid(
self.file,
item=extrusion,
location=[0.0, 0.0, 3.0],
normal=[0.0, 0.0, 1.0],
)
second_clip = ifcopenshell.api.geometry.clip_solid(
self.file,
item=first_clip,
location=[0.0, 0.0, 1.0],
normal=[0.0, 0.0, -1.0],
)
assert second_clip.is_a("IfcBooleanClippingResult")
assert second_clip.FirstOperand == first_clip
assert first_clip.FirstOperand == extrusion
def test_angled_clip_plane(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid(
self.file,
item=extrusion,
location=[0.0, 0.0, 3.26],
normal=[0.419, 0.0, 0.908],
)
assert result.is_a("IfcBooleanClippingResult")
assert result.SecondOperand.is_a("IfcHalfSpaceSolid")
def test_element_registers_result_in_bbim_boolean(self):
extrusion = self.make_extrusion()
wall = self.file.createIfcWall()
result = ifcopenshell.api.geometry.clip_solid(
self.file,
item=extrusion,
location=[0.0, 0.0, 3.0],
normal=[0.0, 0.0, 1.0],
element=wall,
)
pset = ifcopenshell.util.element.get_pset(wall, "BBIM_Boolean")
assert pset is not None
assert result.id() in json.loads(pset["Data"])
def test_element_appends_to_existing_bbim_boolean(self):
extrusion = self.make_extrusion()
wall = self.file.createIfcWall()
first = ifcopenshell.api.geometry.clip_solid(
self.file,
item=extrusion,
location=[0.0, 0.0, 3.0],
normal=[0.0, 0.0, 1.0],
element=wall,
)
second = ifcopenshell.api.geometry.clip_solid(
self.file,
item=first,
location=[0.0, 0.0, 1.0],
normal=[0.0, 0.0, -1.0],
element=wall,
)
pset = ifcopenshell.util.element.get_pset(wall, "BBIM_Boolean")
ids = json.loads(pset["Data"])
assert first.id() in ids
assert second.id() in ids
def test_no_element_does_not_create_pset(self):
extrusion = self.make_extrusion()
wall = self.file.createIfcWall()
ifcopenshell.api.geometry.clip_solid(
self.file,
item=extrusion,
location=[0.0, 0.0, 3.0],
normal=[0.0, 0.0, 1.0],
)
assert ifcopenshell.util.element.get_pset(wall, "BBIM_Boolean") is None
class TestClipSolidIFC2X3(test.bootstrap.IFC2X3, TestClipSolid):
pass
@@ -0,0 +1,179 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2026 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 json
import ifcopenshell.api.geometry
import ifcopenshell.util.element
import ifcopenshell.util.shape_builder
import test.bootstrap
class TestClipSolidBounded(test.bootstrap.IFC4):
def make_extrusion(self):
builder = ifcopenshell.util.shape_builder.ShapeBuilder(self.file)
rect = builder.rectangle(size=(4.0, 1.0))
return builder.extrude(rect, magnitude=3.0)
def test_returns_boolean_clipping_result(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid_bounded(
self.file,
item=extrusion,
location=[2.5, 0.0, 2.0],
normal=[0.6, 0.0, 0.8],
boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]],
)
assert result.is_a("IfcBooleanClippingResult")
assert result.Operator == "DIFFERENCE"
def test_first_operand_is_the_item(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid_bounded(
self.file,
item=extrusion,
location=[2.5, 0.0, 2.0],
normal=[0.6, 0.0, 0.8],
boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]],
)
assert result.FirstOperand == extrusion
def test_second_operand_is_polygonal_bounded_half_space(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid_bounded(
self.file,
item=extrusion,
location=[2.5, 0.0, 2.0],
normal=[0.6, 0.0, 0.8],
boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]],
)
assert result.SecondOperand.is_a("IfcPolygonalBoundedHalfSpace")
def test_agreement_flag_is_false(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid_bounded(
self.file,
item=extrusion,
location=[2.5, 0.0, 2.0],
normal=[0.6, 0.0, 0.8],
boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]],
)
assert result.SecondOperand.AgreementFlag is False
def test_clip_plane_location_matches(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid_bounded(
self.file,
item=extrusion,
location=[2.5, 0.0, 2.0],
normal=[0.6, 0.0, 0.8],
boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]],
)
plane = result.SecondOperand.BaseSurface
coords = plane.Position.Location.Coordinates
assert list(coords) == [2.5, 0.0, 2.0]
def test_boundary_is_closed_polyline(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid_bounded(
self.file,
item=extrusion,
location=[2.5, 0.0, 2.0],
normal=[0.6, 0.0, 0.8],
boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]],
)
boundary = result.SecondOperand.PolygonalBoundary
assert boundary.is_a("IfcPolyline")
pts = [list(p.Coordinates) for p in boundary.Points]
assert pts[0] == pts[-1], "polygon should be closed"
assert len(pts) == 5 # 4 unique + closing repeat
def test_boundary_position_defaults_to_origin(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid_bounded(
self.file,
item=extrusion,
location=[2.5, 0.0, 2.0],
normal=[0.6, 0.0, 0.8],
boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]],
)
pos = result.SecondOperand.Position
assert list(pos.Location.Coordinates) == [0.0, 0.0, 0.0]
def test_custom_boundary_position(self):
extrusion = self.make_extrusion()
result = ifcopenshell.api.geometry.clip_solid_bounded(
self.file,
item=extrusion,
location=[2.5, 0.0, 2.0],
normal=[0.6, 0.0, 0.8],
boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]],
boundary_position=[1.0, 2.0, 3.0],
)
pos = result.SecondOperand.Position
assert list(pos.Location.Coordinates) == [1.0, 2.0, 3.0]
def test_chaining_with_clip_solid(self):
extrusion = self.make_extrusion()
first_clip = ifcopenshell.api.geometry.clip_solid(
self.file,
item=extrusion,
location=[0.0, 0.0, 3.0],
normal=[0.0, 0.0, 1.0],
)
result = ifcopenshell.api.geometry.clip_solid_bounded(
self.file,
item=first_clip,
location=[2.5, 0.0, 2.0],
normal=[0.6, 0.0, 0.8],
boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]],
)
assert result.is_a("IfcBooleanClippingResult")
assert result.FirstOperand == first_clip
assert first_clip.FirstOperand == extrusion
def test_element_registers_result_in_bbim_boolean(self):
extrusion = self.make_extrusion()
wall = self.file.createIfcWall()
result = ifcopenshell.api.geometry.clip_solid_bounded(
self.file,
item=extrusion,
location=[2.5, 0.0, 2.0],
normal=[0.6, 0.0, 0.8],
boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]],
element=wall,
)
pset = ifcopenshell.util.element.get_pset(wall, "BBIM_Boolean")
assert pset is not None
assert result.id() in json.loads(pset["Data"])
def test_no_element_does_not_create_pset(self):
extrusion = self.make_extrusion()
wall = self.file.createIfcWall()
ifcopenshell.api.geometry.clip_solid_bounded(
self.file,
item=extrusion,
location=[2.5, 0.0, 2.0],
normal=[0.6, 0.0, 0.8],
boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]],
)
assert ifcopenshell.util.element.get_pset(wall, "BBIM_Boolean") is None
class TestClipSolidBoundedIFC2X3(test.bootstrap.IFC2X3, TestClipSolidBounded):
pass
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import test.bootstrap
class TestConnectElement(test.bootstrap.IFC4):
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import test.bootstrap
class TestConnectPath(test.bootstrap.IFC4):
@@ -33,6 +33,18 @@ class TestConnectPath(test.bootstrap.IFC4):
assert rel.RelatedConnectionType == "ATEND"
assert rel.Description == "MITRE"
def test_storing_connection_geometry(self):
wall1 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
wall2 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
geometry = self.file.create_entity("IfcConnectionPointGeometry")
rel = ifcopenshell.api.geometry.connect_path(
self.file,
relating_element=wall1,
related_element=wall2,
connection_geometry=geometry,
)
assert rel.ConnectionGeometry == geometry
def test_doing_nothing_if_the_element_is_already_connected(self):
wall1 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
wall2 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
@@ -0,0 +1,133 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2026 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import ifcopenshell.util.representation
import test.bootstrap
class TestCopyRepresentation(test.bootstrap.IFC4):
def _body_context(self):
body = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW")
if body is None:
model = self.file.createIfcGeometricRepresentationContext(
ContextType="Model",
CoordinateSpaceDimension=3,
Precision=1e-5,
WorldCoordinateSystem=self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
),
)
body = self.file.createIfcGeometricRepresentationSubContext(
ContextIdentifier="Body",
ContextType="Model",
TargetView="MODEL_VIEW",
ParentContext=model,
)
return body
def _add_body_rep(self, element):
body = self._body_context()
rep = ifcopenshell.api.geometry.add_wall_representation(
self.file, context=body, length=5.0, height=3.0, thickness=0.2
)
ifcopenshell.api.geometry.assign_representation(self.file, product=element, representation=rep)
return rep
def test_copy_to_empty_target(self):
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
self._add_body_rep(wall_a)
result = ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b)
assert result is not None
assert result.is_a("IfcShapeRepresentation")
target_rep = ifcopenshell.util.representation.get_representation(wall_b, "Model", "Body")
assert target_rep is not None
assert target_rep == result
def test_source_rep_entities_are_distinct(self):
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
source_rep = self._add_body_rep(wall_a)
new_rep = ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b)
assert new_rep.id() != source_rep.id()
assert new_rep.Items[0].id() != source_rep.Items[0].id()
def test_context_is_shared_not_copied(self):
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
source_rep = self._add_body_rep(wall_a)
new_rep = ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b)
assert new_rep.ContextOfItems.id() == source_rep.ContextOfItems.id()
def test_replaces_existing_target_rep(self):
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
self._add_body_rep(wall_a)
old_rep = self._add_body_rep(wall_b)
old_rep_id = old_rep.id()
ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b)
try:
self.file.by_id(old_rep_id)
assert False, "old representation still exists"
except RuntimeError:
pass # entity was removed, as expected
def test_source_unchanged_after_copy(self):
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
source_rep = self._add_body_rep(wall_a)
source_rep_id = source_rep.id()
ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b)
assert self.file.by_id(source_rep_id) is not None # source must still exist
assert ifcopenshell.util.representation.get_representation(wall_a, "Model", "Body") is not None
def test_returns_none_when_no_matching_rep(self):
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
result = ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b)
assert result is None
def test_custom_context_identifier(self):
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
self._add_body_rep(wall_a)
# "Axis" doesn't exist on wall_a, so should return None
result = ifcopenshell.api.geometry.copy_representation(
self.file, source=wall_a, target=wall_b, context_identifier="Axis"
)
assert result is None
class TestCopyRepresentationIFC2X3(test.bootstrap.IFC2X3, TestCopyRepresentation):
pass
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import test.bootstrap
class TestDisconnectElement(test.bootstrap.IFC4):
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import test.bootstrap
class TestDisconnectPath(test.bootstrap.IFC4):
@@ -18,16 +18,17 @@
import numpy
import pytest
import test.bootstrap
import ifcopenshell.api.feature
import ifcopenshell.api.root
import ifcopenshell.api.unit
import ifcopenshell.api.system
import ifcopenshell.api.spatial
import ifcopenshell.api.geometry
import ifcopenshell.api.aggregate
import ifcopenshell.api.feature
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import ifcopenshell.api.spatial
import ifcopenshell.api.system
import ifcopenshell.api.unit
import ifcopenshell.guid
import ifcopenshell.util.placement
import test.bootstrap
class TestEditObjectPlacement(test.bootstrap.IFC4):
@@ -16,11 +16,11 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.context
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import ifcopenshell.util.shape_builder
import test.bootstrap
class TestRemoveBoolean(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.geometry
import test.bootstrap
class TestRemoveRepresentation(test.bootstrap.IFC4):
@@ -16,9 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.geometry
import ifcopenshell.util.placement
import test.bootstrap
class TestUnassignRepresentation(test.bootstrap.IFC4):
@@ -16,11 +16,11 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.context
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import ifcopenshell.util.shape_builder
import test.bootstrap
class TestValidateType(test.bootstrap.IFC4):
@@ -76,7 +76,7 @@ class TestValidateType(test.bootstrap.IFC4):
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1])
assert len(booleans) == 1
assert len(rep.Items) == 3
assert len(rep.Items) == 4
assert ifcopenshell.api.geometry.validate_type(self.file, rep) is True
assert len(rep.Items) == 1
assert rep.RepresentationType == "CSG"
@@ -96,9 +96,9 @@ class TestValidateType(test.bootstrap.IFC4):
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1])
assert len(booleans) == 1
assert len(rep.Items) == 2
assert len(rep.Items) == 3 # boolean replaced first, but second1 stays in Items
assert ifcopenshell.api.geometry.validate_type(self.file, rep) is False
assert len(rep.Items) == 2
assert len(rep.Items) == 2 # validate_type unioned second1 into the boolean
assert rep.RepresentationType is None
@@ -16,11 +16,11 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.context
import ifcopenshell.api.georeference
import ifcopenshell.api.root
import ifcopenshell.util.element
import test.bootstrap
class TestAddGeoreferencing(test.bootstrap.IFC4):
@@ -51,6 +51,38 @@ class TestAddGeoreferencing(test.bootstrap.IFC4):
assert len(self.file.by_type("IfcMapConversion")) == 1
assert len(self.file.by_type("IfcProjectedCRS")) == 1
def test_recovering_from_orphan_projected_crs(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
ifcopenshell.api.context.add_context(self.file, "Model")
self.file.create_entity("IfcProjectedCRS", Name="EPSG:1234")
assert len(self.file.by_type("IfcProjectedCRS")) == 1
assert len(self.file.by_type("IfcCoordinateOperation")) == 0
ifcopenshell.api.georeference.add_georeferencing(self.file)
assert len(self.file.by_type("IfcMapConversion")) == 1
assert len(self.file.by_type("IfcProjectedCRS")) == 1
def test_recovering_from_orphan_coordinate_operation(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
context = ifcopenshell.api.context.add_context(self.file, "Model")
self.file.create_entity(
"IfcMapConversion",
SourceCRS=context,
TargetCRS=self.file.create_entity("IfcProjectedCRS", Name="EPSG:1234"),
)
ifcopenshell.api.georeference.remove_georeferencing(self.file)
# Simulate orphan by re-adding just a conversion without CRS
self.file.create_entity(
"IfcMapConversion",
SourceCRS=context,
TargetCRS=self.file.create_entity("IfcProjectedCRS", Name="EPSG:1234"),
)
self.file.remove(self.file.by_type("IfcProjectedCRS")[0])
assert len(self.file.by_type("IfcProjectedCRS")) == 0
assert len(self.file.by_type("IfcCoordinateOperation")) == 1
ifcopenshell.api.georeference.add_georeferencing(self.file)
assert len(self.file.by_type("IfcMapConversion")) == 1
assert len(self.file.by_type("IfcProjectedCRS")) == 1
class TestAddGeoreferencingIFC2X3(test.bootstrap.IFC2X3):
def test_adding_georeferencing(self):
@@ -16,11 +16,11 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.context
import ifcopenshell.api.georeference
import ifcopenshell.api.root
import ifcopenshell.util.element
import test.bootstrap
class TestEditGeoreferencing(test.bootstrap.IFC4):
@@ -17,11 +17,12 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import numpy as np
import test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.context
import ifcopenshell.api.georeference
import ifcopenshell.api.root
import ifcopenshell.util.geolocation
import test.bootstrap
class TestEditTrueNorth(test.bootstrap.IFC4):
@@ -17,11 +17,12 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import numpy as np
import test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.context
import ifcopenshell.api.georeference
import ifcopenshell.api.root
import ifcopenshell.util.geolocation
import test.bootstrap
class TestEditWCS(test.bootstrap.IFC4):
@@ -16,11 +16,11 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.context
import ifcopenshell.api.georeference
import ifcopenshell.api.root
import ifcopenshell.util.element
import test.bootstrap
class TestRemoveGeoreferencing(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.grid
import test.bootstrap
class TestCreateGridAxis(test.bootstrap.IFC4):
@@ -0,0 +1,59 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.api.grid
import test.bootstrap
class TestRemoveGridAxis(test.bootstrap.IFC4):
def test_removing_an_axis_removes_its_curve(self):
grid = self.file.createIfcGrid()
axis = ifcopenshell.api.grid.create_grid_axis(
self.file, axis_tag="A", same_sense=True, uvw_axes="UAxes", grid=grid
)
axis.AxisCurve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))])
axis2 = ifcopenshell.api.grid.create_grid_axis(
self.file, axis_tag="B", same_sense=True, uvw_axes="UAxes", grid=grid
)
axis2.AxisCurve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint((1.0, 0.0, 0.0))])
ifcopenshell.api.grid.remove_grid_axis(self.file, axis=axis2)
assert grid.UAxes == (axis,)
assert len(self.file.by_type("IfcGridAxis")) == 1
# The curve should be removed since it was only used by the removed axis.
assert len(self.file.by_type("IfcPolyline")) == 1
def test_removing_an_axis_preserves_shared_curve(self):
grid = self.file.createIfcGrid()
shared_curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))])
axis = ifcopenshell.api.grid.create_grid_axis(
self.file, axis_tag="A", same_sense=True, uvw_axes="UAxes", grid=grid
)
axis.AxisCurve = shared_curve
axis2 = ifcopenshell.api.grid.create_grid_axis(
self.file, axis_tag="B", same_sense=True, uvw_axes="UAxes", grid=grid
)
axis2.AxisCurve = shared_curve
ifcopenshell.api.grid.remove_grid_axis(self.file, axis=axis2)
assert grid.UAxes == (axis,)
# The shared curve should be preserved since it's still used by axis.
assert shared_curve in self.file
assert axis.AxisCurve == shared_curve
class TestRemoveGridAxisIFC2X3(test.bootstrap.IFC2X3, TestRemoveGridAxis):
pass
@@ -16,9 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.group
import ifcopenshell.util.element
import test.bootstrap
class TestAddGroup(test.bootstrap.IFC4):
@@ -16,10 +16,10 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.group
import ifcopenshell.api.root
import ifcopenshell.util.element
import test.bootstrap
class TestAssignGroup(test.bootstrap.IFC4):
@@ -16,10 +16,10 @@
# 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 test.bootstrap
import ifcopenshell.api.group
import ifcopenshell.api.pset
import ifcopenshell.api.root
import ifcopenshell.api.group
import test.bootstrap
class TestRemoveGroup(test.bootstrap.IFC4):
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.group
import ifcopenshell.api.root
import test.bootstrap
class TestAssignGroup(test.bootstrap.IFC4):
@@ -16,10 +16,10 @@
# 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 test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.group
import ifcopenshell.api.root
import ifcopenshell.util.element
import test.bootstrap
class TestUpdateGroupProductsIFC2X3(test.bootstrap.IFC2X3):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.layer
import test.bootstrap
class TestAddLayer(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.layer
import test.bootstrap
class TestAddLayerWithStyle(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.layer
import test.bootstrap
class TestAssignLayer(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.layer
import test.bootstrap
class TestUnassignLayer(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.library
import test.bootstrap
class TestAddLibrary(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.library
import test.bootstrap
class TestAddReference(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.library
import test.bootstrap
class TestAssignReference(test.bootstrap.IFC4):
@@ -16,10 +16,11 @@
# 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 test.bootstrap
import datetime
import ifcopenshell.api.library
import ifcopenshell.util.date
import test.bootstrap
class TestEditLibrary(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.library
import test.bootstrap
class TestEditReference(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.library
import test.bootstrap
class TestRemoveLibrary(test.bootstrap.IFC4):
@@ -16,8 +16,8 @@
# 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 test.bootstrap
import ifcopenshell.api.library
import test.bootstrap
class TestRemoveReference(test.bootstrap.IFC4):
@@ -16,9 +16,9 @@
# 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 test.bootstrap
import ifcopenshell.api.library
import ifcopenshell.util.element
import test.bootstrap
class TestUnassignReference(test.bootstrap.IFC4):

Some files were not shown because too many files have changed in this diff Show More