classification.remove_reference - support batching #4474

This commit is contained in:
Andrej730
2024-04-15 12:20:19 +05:00
parent d6e69bd6e9
commit 144831f9ee
5 changed files with 155 additions and 64 deletions
@@ -20,6 +20,7 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.classification
import ifcopenshell.util.element
import blenderbim.tool as tool
import blenderbim.bim.helper
@@ -293,6 +294,7 @@ class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
active_reference = tool.Ifc.get().by_id(self.reference)
identification = active_reference[1]
elements_by_references: dict[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]] = []
for obj in objects:
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id)
@@ -301,12 +303,16 @@ class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
if (identification and reference[1] == identification) or (
not identification and reference == active_reference
):
ifcopenshell.api.run(
"classification.remove_reference",
tool.Ifc.get(),
reference=reference,
product=element,
)
elements_by_references.setdefault(reference, []).append(element)
if elements_by_references:
for reference, products in elements_by_references.items():
ifcopenshell.api.run(
"classification.remove_reference",
tool.Ifc.get(),
reference=reference,
products=products,
)
class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
@@ -90,6 +90,9 @@ ARGUMENTS_DEPRECATION = {
"classification.add_reference": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"classification.remove_reference": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
}
@@ -17,12 +17,18 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
class Usecase:
def __init__(self, file, reference=None, product=None):
"""Removes a classification reference from a product
def __init__(
self,
file: ifcopenshell.file,
reference: ifcopenshell.entity_instance,
products: list[ifcopenshell.entity_instance],
):
"""Removes a classification reference from the list of products
If the classification reference is no longer associated to any products,
the classification reference itself is also removed.
@@ -30,9 +36,12 @@ class Usecase:
:param reference: The IfcClassificationReference entity of the
relationship you want to remove.
:type reference: ifcopenshell.entity_instance.entity_instance
:param product: The object entity of the relationship you want to
:param product: The list fo object entities of the relationship you want to
remove.
:type reference: ifcopenshell.entity_instance.entity_instance
:type product: list[ifcopenshell.entity_instance.entity_instance]
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
:return: None
:rtype: None
@@ -47,39 +56,72 @@ class Usecase:
products=[wall_type], classification=classification,
identification="W_01", name="Interior Walls")
ifcopenshell.api.run("classification.remove_reference", model,
reference=reference, product=wall_type)
reference=reference, products=[wall_type])
"""
self.file = file
self.settings = {"reference": reference, "product": product}
self.settings = {"reference": reference, "products": products}
def execute(self):
if self.settings["product"].is_a("IfcRoot"):
for rel in self.file.by_type("IfcRelAssociatesClassification"):
if rel.RelatingClassification == self.settings["reference"] and rel.RelatedObjects:
if self.settings["product"] in rel.RelatedObjects:
related_objects = list(rel.RelatedObjects)
related_objects.remove(self.settings["product"])
if len(related_objects):
rel.RelatedObjects = related_objects
else:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
else:
for rel in self.file.by_type("IfcExternalReferenceRelationship"):
if rel.RelatingReference == self.settings["reference"] and rel.RelatedResourceObjects:
if self.settings["product"] in rel.RelatedResourceObjects:
related_objects = list(rel.RelatedResourceObjects)
related_objects.remove(self.settings["product"])
if len(related_objects):
rel.RelatedResourceObjects = related_objects
else:
self.file.remove(rel)
def execute(self) -> None:
is_ifc2x3 = self.file.schema == "IFC2X3"
products = set(self.settings["products"])
referenced = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
products -= products.difference(referenced)
# all products are already unassigned from a reference
if not products:
return
rooted_products: set[ifcopenshell.entity_instance] = set()
non_rooted_products: set[ifcopenshell.entity_instance] = set()
for product in self.settings["products"]:
if product.is_a("IfcRoot"):
rooted_products.add(product)
else:
non_rooted_products.add(product)
if non_rooted_products and is_ifc2x3:
raise TypeError(f"Cannot add reference to non-IfcRoot element in IFC2X3: {non_rooted_products}.")
if rooted_products:
reference_rels: set[ifcopenshell.entity_instance] = set()
for product in rooted_products:
reference_rels.update(product.HasAssociations)
reference_rels = {
rel
for rel in reference_rels
if rel.is_a("IfcRelAssociatesClassification")
and rel.RelatingClassification == self.settings["reference"]
}
for rel in reference_rels:
related_objects = set(rel.RelatedObjects) - rooted_products
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
if non_rooted_products:
reference_rels: set[ifcopenshell.entity_instance] = set()
for product in non_rooted_products:
rels = getattr(product, "HasExternalReferences", None)
if rels is None:
rels = getattr(product, "HasExternalReference", [])
reference_rels.update(rels)
reference_rels = {rel for rel in reference_rels if rel.RelatingReference == self.settings["reference"]}
for rel in reference_rels:
related_objects = set(rel.RelatedResourceObjects) - non_rooted_products
if related_objects:
rel.RelatedResourceObjects = list(related_objects)
else:
self.file.remove(rel)
# TODO: we only handle lightweight classifications here
if (
not self.settings["reference"].ClassificationRefForObjects
and not self.settings["reference"].ExternalReferenceForResources
):
referenced_elements = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
if not referenced_elements:
self.file.remove(self.settings["reference"])
@@ -16,6 +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 test.bootstrap
import ifcopenshell.api
import ifcopenshell.util.classification
@@ -25,58 +26,80 @@ class TestRemoveReference(test.bootstrap.IFC4):
def test_removing_a_reference(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
reference = ifcopenshell.api.run(
"classification.add_reference",
self.file,
products=[element],
products=[element, element2],
identification="X",
name="Foobar",
classification=result,
)
ifcopenshell.api.run("classification.remove_reference", self.file, product=element, reference=reference)
ifcopenshell.api.run(
"classification.remove_reference", self.file, products=[element, element2], reference=reference
)
assert len(ifcopenshell.util.classification.get_references(element)) == 0
assert len(ifcopenshell.util.classification.get_references(element2)) == 0
assert len(self.file.by_type("IfcClassificationReference")) == 0
def test_removing_a_reference_from_a_resource(self):
def test_removing_a_reference_from_a_resource_and_from_a_root(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = self.file.createIfcMaterial()
element2 = self.file.createIfcCostValue()
element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
reference = ifcopenshell.api.run(
"classification.add_reference",
self.file,
products=[element],
identification="X",
name="Foobar",
classification=result,
if self.file.schema == "IFC2X3":
with pytest.raises(TypeError):
reference = ifcopenshell.api.run(
"classification.add_reference",
self.file,
products=[element, element2, element3],
identification="X",
name="Foobar",
classification=result,
)
return
else:
reference = ifcopenshell.api.run(
"classification.add_reference",
self.file,
products=[element, element2, element3],
identification="X",
name="Foobar",
classification=result,
)
ifcopenshell.api.run(
"classification.remove_reference", self.file, products=[element, element2, element3], reference=reference
)
ifcopenshell.api.run("classification.remove_reference", self.file, product=element, reference=reference)
assert len(ifcopenshell.util.classification.get_references(element)) == 0
assert len(ifcopenshell.util.classification.get_references(element2)) == 0
assert len(ifcopenshell.util.classification.get_references(element3)) == 0
assert len(self.file.by_type("IfcClassificationReference")) == 0
def test_retaining_the_reference_if_still_in_use(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = self.file.createIfcMaterial()
element2 = self.file.createIfcMaterial()
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
reference = ifcopenshell.api.run(
"classification.add_reference",
self.file,
products=[element],
identification="X",
name="Foobar",
classification=result,
)
reference2 = ifcopenshell.api.run(
"classification.add_reference",
self.file,
products=[element2],
products=[element, element2, element3],
identification="X",
name="Foobar",
classification=result,
)
assert len(self.file.by_type("IfcClassificationReference")) == 1
ifcopenshell.api.run("classification.remove_reference", self.file, product=element, reference=reference)
ifcopenshell.api.run(
"classification.remove_reference", self.file, products=[element, element2], reference=reference
)
assert len(self.file.by_type("IfcClassificationReference")) == 1
ifcopenshell.api.run("classification.remove_reference", self.file, product=element2, reference=reference2)
ifcopenshell.api.run("classification.remove_reference", self.file, products=[element3], reference=reference)
assert len(self.file.by_type("IfcClassificationReference")) == 0
class TestRemoveReferenceIFC2X3(test.bootstrap.IFC2X3, TestRemoveReference):
pass
@@ -219,3 +219,20 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4):
assert list(ifcopenshell.util.classification.get_references(element2))[0].Identification == "X"
assert list(ifcopenshell.util.classification.get_references(element2))[0].Name == "Foobar"
assert list(ifcopenshell.util.classification.get_references(element2))[0] == references[0]
@deprecation_check
def test_removing_a_reference(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
reference = ifcopenshell.api.run(
"classification.add_reference",
self.file,
products=[element],
identification="X",
name="Foobar",
classification=result,
)
ifcopenshell.api.run("classification.remove_reference", self.file, product=element, reference=reference)
assert len(ifcopenshell.util.classification.get_references(element)) == 0
assert len(self.file.by_type("IfcClassificationReference")) == 0