Merge remote-tracking branch 'origin/v0.8.0' into tfk-rocksdb-storage

This commit is contained in:
Thomas Krijnen
2025-09-10 10:06:01 +02:00
181 changed files with 12075 additions and 2648 deletions
@@ -27,23 +27,33 @@ class TestAssignControl(test.bootstrap.IFC4):
control = ifcopenshell.api.cost.add_cost_schedule(self.file)
# simple assignment
relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_object=wall)
relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_objects=[wall])
assert relation
assert len(self.file.by_type("IfcRelAssignsToControl")) == 1
assert relation.RelatingControl == control
assert relation.RelatedObjects == (wall,)
# trying to establish existing relationship
relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_object=wall)
relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_objects=[wall])
assert relation is None
# assigning same control to another object
wall1 = self.file.createIfcWall()
relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_object=wall1)
relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_objects=[wall1])
assert relation is not None
assert len(self.file.by_type("IfcRelAssignsToControl")) == 1
assert relation.RelatingControl == control
assert set(relation.RelatedObjects) == set((wall, wall1))
def test_batch_assignment(self):
walls = [self.file.createIfcWall() for _ in range(5)]
control = ifcopenshell.api.cost.add_cost_schedule(self.file)
relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_objects=walls)
assert relation
assert len(self.file.by_type("IfcRelAssignsToControl")) == 1
assert relation.RelatingControl == control
assert set(relation.RelatedObjects) == set(walls)
class TestAssignControlIFC2X3(test.bootstrap.IFC2X3, TestAssignControl):
pass
@@ -27,15 +27,17 @@ class TestUnassignControl(test.bootstrap.IFC4):
control = ifcopenshell.api.cost.add_cost_schedule(self.file)
# assign and unassign
relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_object=wall)
ifcopenshell.api.control.unassign_control(self.file, relating_control=control, related_object=wall)
relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_objects=[wall])
assert relation
ifcopenshell.api.control.unassign_control(self.file, relating_control=control, related_objects=[wall])
assert len(self.file.by_type("IfcRelAssignsToControl")) == 0
# 1 control 2 related objects
wall1 = self.file.createIfcWall()
relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_object=wall)
ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_object=wall1)
ifcopenshell.api.control.unassign_control(self.file, relating_control=control, related_object=wall1)
relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_objects=[wall])
assert relation
ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_objects=[wall1])
ifcopenshell.api.control.unassign_control(self.file, relating_control=control, related_objects=[wall1])
assert len(self.file.by_type("IfcRelAssignsToControl")) == 1
assert relation.RelatedObjects == (wall,)
@@ -30,7 +30,7 @@ class TestAddCostItemQuantity(test.bootstrap.IFC4):
schedule = ifcopenshell.api.cost.add_cost_schedule(self.file)
item = ifcopenshell.api.cost.add_cost_item(self.file, cost_schedule=schedule)
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
ifcopenshell.api.control.assign_control(self.file, relating_control=item, related_object=wall)
ifcopenshell.api.control.assign_control(self.file, relating_control=item, related_objects=[wall])
quantities = []
for quantity_type in quantity_types:
@@ -95,7 +95,7 @@ class TestEditTaskTime(test.bootstrap.IFC4):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
calendar = ifcopenshell.api.sequence.add_work_calendar(self.file)
task = self.file.createIfcTask()
ifcopenshell.api.control.assign_control(self.file, relating_control=calendar, related_object=task)
ifcopenshell.api.control.assign_control(self.file, relating_control=calendar, related_objects=[task])
task_time = ifcopenshell.api.sequence.add_task_time(self.file, task=task)
ifcopenshell.api.sequence.edit_task_time(
self.file,
@@ -195,7 +195,7 @@ class TestEditTaskTime(test.bootstrap.IFC4):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
calendar = ifcopenshell.api.sequence.add_work_calendar(self.file)
task = self.file.createIfcTask()
ifcopenshell.api.control.assign_control(self.file, relating_control=calendar, related_object=task)
ifcopenshell.api.control.assign_control(self.file, relating_control=calendar, related_objects=[task])
task_time = ifcopenshell.api.sequence.add_task_time(self.file, task=task)
ifcopenshell.api.sequence.edit_task_time(
self.file,
@@ -38,7 +38,7 @@ class TestRemoveWorkCalendar(test.bootstrap.IFC4):
# Assign tasks.
task = ifcopenshell.api.sequence.add_task(self.file)
ifcopenshell.api.control.assign_control(self.file, work_calendar, task)
ifcopenshell.api.control.assign_control(self.file, work_calendar, [task])
ifcopenshell.api.sequence.remove_work_calendar(self.file, work_calendar)
+22 -2
View File
@@ -16,19 +16,39 @@
# 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.root
import test.bootstrap
import ifcopenshell.api
import ifcopenshell.api.control
import ifcopenshell.api.sequence
import ifcopenshell.util.element
from datetime import datetime
from typing import Union
def deprecation_check(test):
def new_test(self):
assert datetime.now().date() < datetime(2024, 8, 1).date(), "API arguments are completely deprecated"
assert datetime.now().date() < datetime(2026, 1, 9).date(), "API arguments are completely deprecated"
test(self)
return new_test
class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4):
pass
@deprecation_check
def test_assigning_control(self):
model = self.file
element = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
control = ifcopenshell.api.cost.add_cost_schedule(model)
ifcopenshell.api.control.assign_control(model, relating_control=control, related_objects=[element])
assert list(ifcopenshell.util.element.get_controls(element)) == [control]
@deprecation_check
def test_unassigning_control(self):
TestTemporarySupportForDeprecatedAPIArguments.test_assigning_control(self)
model = self.file
element = model.by_type("IfcWall")[0]
control = model.by_type("IfcCostSchedule")[0]
ifcopenshell.api.control.unassign_control(model, relating_control=control, related_objects=[element])
assert list(ifcopenshell.util.element.get_controls(element)) == []
@@ -145,14 +145,20 @@ class TestAssignType(test.bootstrap.IFC4):
This is because the type will have its own PredefinedType, and the element's PredefinedType
will conflict with it. (See #7006)
"""
is_ifc2x3 = self.file.schema == "IFC2X3"
element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
element_type.PredefinedType = "MOVABLE"
element_type.PredefinedType = "POLYGONAL"
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
element.PredefinedType = "USERDEFINED"
if not is_ifc2x3:
# In IFC2X3, there seems to be no example when both type and occurence have PredefinedType.
# So we just ignore it.
element.PredefinedType = "USERDEFINED"
element.ObjectType = "Test"
ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type)
assert element.PredefinedType is None
if not is_ifc2x3:
assert element.PredefinedType is None
assert element.ObjectType is None
def test_keep_predefined_type_if_type_assignment_is_notdefined(self):
@@ -160,16 +166,26 @@ class TestAssignType(test.bootstrap.IFC4):
if an element has a PredefinedType, it will be removed when assigning a type.(See #7006)
This behavior needs to be blocked if the PredefinedType of the typing Entity is set to "NOTDEFINED". (See #7011)
"""
is_ifc2x3 = self.file.schema == "IFC2X3"
element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
element_type.PredefinedType = "NOTDEFINED"
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
element.PredefinedType = "USERDEFINED"
if not is_ifc2x3:
# In IFC2X3, there seems to be no example when both type and occurence have PredefinedType.
# So we just ignore it.
element.PredefinedType = "USERDEFINED"
element.ObjectType = "Test"
ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type)
assert element.PredefinedType == "USERDEFINED"
if not is_ifc2x3:
assert element.PredefinedType == "USERDEFINED"
assert element.ObjectType == "Test"
class TestAssignTypeIFC2X3(test.bootstrap.IFC2X3, TestAssignType):
pass
class TestAssignTypeIFC4X3(test.bootstrap.IFC4X3, TestAssignType):
pass
+8 -3
View File
@@ -47,15 +47,20 @@ class TestPackageSupportedPlatforms:
response = conn.getresponse()
build_html = response.read().decode("utf-8")
def find_make_var(var_name: str) -> str:
line = next(l for l in text.splitlines() if l.startswith(f"{var_name}:="))
return line.partition(":=")[2]
BINARY_VERSION = find_make_var("BINARY_VERSION")
URL_TYPES = ("IOS_URL", "IFCCONVERT_URL")
missing_urls: set[str] = set()
for url_type in URL_TYPES:
line = next(l for l in text.splitlines() if l.startswith(f"{url_type}:="))
_, _, url_template = line.partition(":=")
url_template = find_make_var(url_type)
url_template = url_template.replace("$(", "{").replace(")", "}")
for platform in SUPPORTED_PLATFORMS:
for pyver in SUPPORTED_PY_VERSIONS:
url = url_template.format(PYNUMBER=pyver, PLATFORM=platform)
url = url_template.format(PYNUMBER=pyver, PLATFORM=platform, BINARY_VERSION=BINARY_VERSION)
if url not in build_html:
missing_urls.add(url)
@@ -16,6 +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 ifcopenshell.api.control
import ifcopenshell.api.cost
import ifcopenshell.api.profile
import pytest
import test.bootstrap
@@ -926,6 +928,23 @@ class TestGetGroupsIFC2X3(test.bootstrap.IFC2X3, TestGetGroupsIFC4):
pass
class TestGetControls(test.bootstrap.IFC2X3):
def test_run(self):
model = self.file
element = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
control = ifcopenshell.api.cost.add_cost_schedule(model)
ifcopenshell.api.control.assign_control(model, related_objects=[element], relating_control=control)
assert list(subject.get_controls(element)) == [control]
class TestGetControlsIFC4(test.bootstrap.IFC4, TestGetControls):
pass
class TestGetControlsIFC4X3(test.bootstrap.IFC4X3, TestGetControls):
pass
class TestGetAggregateIFC4(test.bootstrap.IFC4):
def test_getting_the_containing_aggregate_of_a_subelement(self):
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
@@ -468,3 +468,29 @@ class TestAngle2YAxis(test.bootstrap.IFC4):
assert np.allclose(subject.angle2yaxis(45), (-a, a))
assert np.allclose(subject.angle2yaxis(-135), (a, -a))
assert np.allclose(subject.angle2yaxis(135), (-a, -a))
class TestDMS2DDandDD2DMS(test.bootstrap.IFC4):
def test_dms2dd_and_dd2dms(self):
test_cases_3tuple = [
(35.41, (35, 24, 36.0)),
(-116.89, (-116, -53, -24.0)),
]
test_cases_4tuple = [
(40.431389, (40, 25, 53, 400)),
(-4.248056, (-4, -14, -53, -1600)),
(-35.401389, (-35, -24, -5, -400)),
(148.981667, (148, 58, 54, 1200)),
]
for dd, dms in test_cases_3tuple:
d, m, s = subject.dd2dms(dd)
assert (d, m, s) == dms
dd_converted = subject.dms2dd(dms[0], dms[1], dms[2])
assert dd_converted == dd
for dd, dms in test_cases_4tuple:
d, m, s, us = subject.dd2dms(dd, use_us=True)
assert (d, m, s, us) == dms
dd_converted = subject.dms2dd(dms[0], dms[1], dms[2], dms[3])
assert dd_converted == dd