diff --git a/src/ifcopenshell-python/ifcopenshell/api/context/remove_context.py b/src/ifcopenshell-python/ifcopenshell/api/context/remove_context.py index bb4dcff79a..806782b1e4 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/context/remove_context.py +++ b/src/ifcopenshell-python/ifcopenshell/api/context/remove_context.py @@ -51,8 +51,10 @@ def remove_context(file: ifcopenshell.file, context: ifcopenshell.entity_instanc new = context.ParentContext for inverse in file.get_inverse(context): if inverse.is_a("IfcCoordinateOperation"): + # Trick to make sure the coordinate operation is not referenced + # by a context so we can delete it safely inverse.SourceCRS = inverse.TargetCRS - ifcopenshell.util.element.remove_deep(file, inverse) + ifcopenshell.util.element.remove_deep2(file, inverse) else: ifcopenshell.util.element.replace_attribute(inverse, context, new) file.remove(context) diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py index e1be553922..3a9bbe0cde 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py @@ -59,6 +59,6 @@ def edit_cost_value( value["ValueComponent"], ) value = file.create_entity("IfcMeasureWithUnit", value_component, value["UnitComponent"]) - if old_unit_basis and file.get_total_inverses(old_unit_basis) == 0: - ifcopenshell.util.element.remove_deep(file, old_unit_basis) + if old_unit_basis: + ifcopenshell.util.element.remove_deep2(file, old_unit_basis) setattr(cost_value, name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py b/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py index 652fc89f56..f9bcacba8d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py +++ b/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py @@ -42,7 +42,5 @@ def remove_grid_axis(file: ifcopenshell.file, axis: ifcopenshell.entity_instance ifcopenshell.api.grid.remove_grid_axis(model, axis=axis_2) """ axis_curve = axis.AxisCurve - if file.get_total_inverses(axis_curve) == 1: - ifcopenshell.util.element.remove_deep(file, axis_curve) - file.remove(axis_curve) file.remove(axis) + ifcopenshell.util.element.remove_deep2(file, axis_curve) diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py index ca8d5ba55e..2f81494bc7 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py @@ -22,9 +22,9 @@ import ifcopenshell.util.element def remove_prop_template(file: ifcopenshell.file, prop_template: ifcopenshell.entity_instance) -> None: """Removes a property template - Note that a property set template should always have at least one - property template to be valid, so take care when removing property - templates. + Note that a property set template should always have at least one property + template to be valid. So a property set template will not be removed if it + is the only template ina a property ste template. :param prop_template: The IfcSimplePropertyTemplate to remove. :return: None @@ -43,10 +43,8 @@ def remove_prop_template(file: ifcopenshell.file, prop_template: ifcopenshell.en ifcopenshell.api.pset_template.remove_prop_template(model, prop_template=prop2) """ for inverse in file.get_inverse(prop_template): - if len(inverse.HasPropertyTemplates) == 1: - inverse.HasPropertyTemplates = [] - else: + if len(inverse.HasPropertyTemplates) > 1: has_property_templates = list(inverse.HasPropertyTemplates) has_property_templates.remove(prop_template) inverse.HasPropertyTemplates = has_property_templates - ifcopenshell.util.element.remove_deep(file, prop_template) + ifcopenshell.util.element.remove_deep2(file, prop_template) diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py index 07d5cf6daf..5ff3cf6c38 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py @@ -38,4 +38,4 @@ def remove_pset_template(file: ifcopenshell.file, pset_template: ifcopenshell.en # Let's remove the template. ifcopenshell.api.pset_template.remove_pset_template(model, pset_template=template) """ - ifcopenshell.util.element.remove_deep(file, pset_template) + ifcopenshell.util.element.remove_deep2(file, pset_template) diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py index fc67a8d3c7..ceb49681b5 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py @@ -79,5 +79,5 @@ def add_resource_quantity( old_quantity = resource.BaseQuantity resource.BaseQuantity = quantity if old_quantity: - ifcopenshell.util.element.remove_deep(file, old_quantity) + ifcopenshell.util.element.remove_deep2(file, old_quantity) return quantity diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/remove_resource_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/resource/remove_resource_quantity.py index a6b014e1db..afb470565d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/remove_resource_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/remove_resource_quantity.py @@ -47,4 +47,4 @@ def remove_resource_quantity(file: ifcopenshell.file, resource: ifcopenshell.ent old_quantity = resource.BaseQuantity resource.BaseQuantity = None if old_quantity: - ifcopenshell.util.element.remove_deep(file, old_quantity) + ifcopenshell.util.element.remove_deep2(file, old_quantity) diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py index 36ab2f73a2..2611df0b4e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py @@ -47,4 +47,5 @@ def remove_unit(file: ifcopenshell.file, unit: ifcopenshell.entity_instance) -> unit_assignment.Units = units else: file.remove(unit_assignment) - ifcopenshell.util.element.remove_deep(file, unit) + # TODO handle other possible unit inverses + ifcopenshell.util.element.remove_deep2(file, unit) diff --git a/src/ifcopenshell-python/test/api/cost/test_edit_cost_value.py b/src/ifcopenshell-python/test/api/cost/test_edit_cost_value.py new file mode 100644 index 0000000000..c8935c8e9b --- /dev/null +++ b/src/ifcopenshell-python/test/api/cost/test_edit_cost_value.py @@ -0,0 +1,72 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.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 diff --git a/src/ifcopenshell-python/test/api/grid/test_remove_grid_axis.py b/src/ifcopenshell-python/test/api/grid/test_remove_grid_axis.py new file mode 100644 index 0000000000..f59930e0e5 --- /dev/null +++ b/src/ifcopenshell-python/test/api/grid/test_remove_grid_axis.py @@ -0,0 +1,59 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.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 diff --git a/src/ifcopenshell-python/test/api/pset_template/test_remove_prop_template.py b/src/ifcopenshell-python/test/api/pset_template/test_remove_prop_template.py new file mode 100644 index 0000000000..d967a0017a --- /dev/null +++ b/src/ifcopenshell-python/test/api/pset_template/test_remove_prop_template.py @@ -0,0 +1,38 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.api.pset_template +import test.bootstrap + + +class TestRemovePropTemplate(test.bootstrap.IFC4): + def test_removing_a_prop_template(self): + template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors") + prop1 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template) + prop2 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template) + ifcopenshell.api.pset_template.remove_prop_template(self.file, prop_template=prop2) + assert len(self.file.by_type("IfcSimplePropertyTemplate")) == 1 + assert template.HasPropertyTemplates == (prop1,) + + def test_not_removing_the_last_prop_template(self): + template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors") + prop = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template) + ifcopenshell.api.pset_template.remove_prop_template(self.file, prop_template=prop) + # The last prop template should not be removed to keep the pset template valid. + assert len(self.file.by_type("IfcSimplePropertyTemplate")) == 1 + assert template.HasPropertyTemplates == (prop,) diff --git a/src/ifcopenshell-python/test/api/pset_template/test_remove_pset_template.py b/src/ifcopenshell-python/test/api/pset_template/test_remove_pset_template.py new file mode 100644 index 0000000000..67c700a74f --- /dev/null +++ b/src/ifcopenshell-python/test/api/pset_template/test_remove_pset_template.py @@ -0,0 +1,35 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.api.pset_template +import test.bootstrap + + +class TestRemovePsetTemplate(test.bootstrap.IFC4): + def test_removing_a_pset_template(self): + template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors") + ifcopenshell.api.pset_template.remove_pset_template(self.file, pset_template=template) + assert len(self.file.by_type("IfcPropertySetTemplate")) == 0 + + def test_removing_a_pset_template_with_property_templates(self): + template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors") + prop1 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template) + prop2 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template) + ifcopenshell.api.pset_template.remove_pset_template(self.file, pset_template=template) + assert len(self.file.by_type("IfcPropertySetTemplate")) == 0 + assert len(self.file.by_type("IfcSimplePropertyTemplate")) == 0 diff --git a/src/ifcopenshell-python/test/api/resource/test_remove_resource_quantity.py b/src/ifcopenshell-python/test/api/resource/test_remove_resource_quantity.py new file mode 100644 index 0000000000..c91e2f27c6 --- /dev/null +++ b/src/ifcopenshell-python/test/api/resource/test_remove_resource_quantity.py @@ -0,0 +1,44 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.api.resource +import test.bootstrap + + +class TestRemoveResourceQuantity(test.bootstrap.IFC4): + def test_removing_a_resource_quantity(self): + self.file.create_entity("IfcProject") + resource = ifcopenshell.api.resource.add_resource(self.file, ifc_class="IfcLaborResource") + ifcopenshell.api.resource.add_resource_quantity( + self.file, resource=resource, ifc_class="IfcQuantityTime" + ) + assert resource.BaseQuantity is not None + ifcopenshell.api.resource.remove_resource_quantity(self.file, resource=resource) + assert resource.BaseQuantity is None + assert len(self.file.by_type("IfcPhysicalSimpleQuantity")) == 0 + + def test_removing_a_resource_quantity_when_none_exists(self): + self.file.create_entity("IfcProject") + resource = ifcopenshell.api.resource.add_resource(self.file, ifc_class="IfcLaborResource") + # Should not raise. + ifcopenshell.api.resource.remove_resource_quantity(self.file, resource=resource) + assert resource.BaseQuantity is None + + +class TestRemoveResourceQuantityIFC2X3(test.bootstrap.IFC2X3, TestRemoveResourceQuantity): + pass