mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
classification.remove_reference - support batching #4474
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user