mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 15:08:51 +00:00
classification.remove_reference - support batching #4474
This commit is contained in:
@@ -20,6 +20,7 @@ import bpy
|
|||||||
import json
|
import json
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api
|
import ifcopenshell.api
|
||||||
|
import ifcopenshell.util.classification
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
import blenderbim.bim.helper
|
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)
|
active_reference = tool.Ifc.get().by_id(self.reference)
|
||||||
identification = active_reference[1]
|
identification = active_reference[1]
|
||||||
|
|
||||||
|
elements_by_references: dict[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]] = []
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
|
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
|
||||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
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 (
|
if (identification and reference[1] == identification) or (
|
||||||
not identification and reference == active_reference
|
not identification and reference == active_reference
|
||||||
):
|
):
|
||||||
ifcopenshell.api.run(
|
elements_by_references.setdefault(reference, []).append(element)
|
||||||
"classification.remove_reference",
|
|
||||||
tool.Ifc.get(),
|
if elements_by_references:
|
||||||
reference=reference,
|
for reference, products in elements_by_references.items():
|
||||||
product=element,
|
ifcopenshell.api.run(
|
||||||
)
|
"classification.remove_reference",
|
||||||
|
tool.Ifc.get(),
|
||||||
|
reference=reference,
|
||||||
|
products=products,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ ARGUMENTS_DEPRECATION = {
|
|||||||
"classification.add_reference": partial(
|
"classification.add_reference": partial(
|
||||||
batching_argument_deprecation, prev_argument="product", new_argument="products"
|
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/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
|
import ifcopenshell.api
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
|
|
||||||
|
|
||||||
class Usecase:
|
class Usecase:
|
||||||
def __init__(self, file, reference=None, product=None):
|
def __init__(
|
||||||
"""Removes a classification reference from a product
|
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,
|
If the classification reference is no longer associated to any products,
|
||||||
the classification reference itself is also removed.
|
the classification reference itself is also removed.
|
||||||
@@ -30,9 +36,12 @@ class Usecase:
|
|||||||
:param reference: The IfcClassificationReference entity of the
|
:param reference: The IfcClassificationReference entity of the
|
||||||
relationship you want to remove.
|
relationship you want to remove.
|
||||||
:type reference: ifcopenshell.entity_instance.entity_instance
|
: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.
|
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
|
:return: None
|
||||||
:rtype: None
|
:rtype: None
|
||||||
|
|
||||||
@@ -47,39 +56,72 @@ class Usecase:
|
|||||||
products=[wall_type], classification=classification,
|
products=[wall_type], classification=classification,
|
||||||
identification="W_01", name="Interior Walls")
|
identification="W_01", name="Interior Walls")
|
||||||
ifcopenshell.api.run("classification.remove_reference", model,
|
ifcopenshell.api.run("classification.remove_reference", model,
|
||||||
reference=reference, product=wall_type)
|
reference=reference, products=[wall_type])
|
||||||
"""
|
"""
|
||||||
self.file = file
|
self.file = file
|
||||||
self.settings = {"reference": reference, "product": product}
|
self.settings = {"reference": reference, "products": products}
|
||||||
|
|
||||||
def execute(self):
|
def execute(self) -> None:
|
||||||
if self.settings["product"].is_a("IfcRoot"):
|
is_ifc2x3 = self.file.schema == "IFC2X3"
|
||||||
for rel in self.file.by_type("IfcRelAssociatesClassification"):
|
products = set(self.settings["products"])
|
||||||
if rel.RelatingClassification == self.settings["reference"] and rel.RelatedObjects:
|
referenced = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
|
||||||
if self.settings["product"] in rel.RelatedObjects:
|
products -= products.difference(referenced)
|
||||||
related_objects = list(rel.RelatedObjects)
|
|
||||||
related_objects.remove(self.settings["product"])
|
# all products are already unassigned from a reference
|
||||||
if len(related_objects):
|
if not products:
|
||||||
rel.RelatedObjects = related_objects
|
return
|
||||||
else:
|
|
||||||
history = rel.OwnerHistory
|
rooted_products: set[ifcopenshell.entity_instance] = set()
|
||||||
self.file.remove(rel)
|
non_rooted_products: set[ifcopenshell.entity_instance] = set()
|
||||||
if history:
|
for product in self.settings["products"]:
|
||||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
if product.is_a("IfcRoot"):
|
||||||
else:
|
rooted_products.add(product)
|
||||||
for rel in self.file.by_type("IfcExternalReferenceRelationship"):
|
else:
|
||||||
if rel.RelatingReference == self.settings["reference"] and rel.RelatedResourceObjects:
|
non_rooted_products.add(product)
|
||||||
if self.settings["product"] in rel.RelatedResourceObjects:
|
|
||||||
related_objects = list(rel.RelatedResourceObjects)
|
if non_rooted_products and is_ifc2x3:
|
||||||
related_objects.remove(self.settings["product"])
|
raise TypeError(f"Cannot add reference to non-IfcRoot element in IFC2X3: {non_rooted_products}.")
|
||||||
if len(related_objects):
|
|
||||||
rel.RelatedResourceObjects = related_objects
|
if rooted_products:
|
||||||
else:
|
reference_rels: set[ifcopenshell.entity_instance] = set()
|
||||||
self.file.remove(rel)
|
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
|
# TODO: we only handle lightweight classifications here
|
||||||
if (
|
referenced_elements = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
|
||||||
not self.settings["reference"].ClassificationRefForObjects
|
if not referenced_elements:
|
||||||
and not self.settings["reference"].ExternalReferenceForResources
|
|
||||||
):
|
|
||||||
self.file.remove(self.settings["reference"])
|
self.file.remove(self.settings["reference"])
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import pytest
|
||||||
import test.bootstrap
|
import test.bootstrap
|
||||||
import ifcopenshell.api
|
import ifcopenshell.api
|
||||||
import ifcopenshell.util.classification
|
import ifcopenshell.util.classification
|
||||||
@@ -25,58 +26,80 @@ class TestRemoveReference(test.bootstrap.IFC4):
|
|||||||
def test_removing_a_reference(self):
|
def test_removing_a_reference(self):
|
||||||
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
||||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
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")
|
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
|
||||||
reference = ifcopenshell.api.run(
|
reference = ifcopenshell.api.run(
|
||||||
"classification.add_reference",
|
"classification.add_reference",
|
||||||
self.file,
|
self.file,
|
||||||
products=[element],
|
products=[element, element2],
|
||||||
identification="X",
|
identification="X",
|
||||||
name="Foobar",
|
name="Foobar",
|
||||||
classification=result,
|
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(element)) == 0
|
||||||
|
assert len(ifcopenshell.util.classification.get_references(element2)) == 0
|
||||||
assert len(self.file.by_type("IfcClassificationReference")) == 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")
|
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
||||||
element = self.file.createIfcMaterial()
|
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")
|
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
|
||||||
reference = ifcopenshell.api.run(
|
if self.file.schema == "IFC2X3":
|
||||||
"classification.add_reference",
|
with pytest.raises(TypeError):
|
||||||
self.file,
|
reference = ifcopenshell.api.run(
|
||||||
products=[element],
|
"classification.add_reference",
|
||||||
identification="X",
|
self.file,
|
||||||
name="Foobar",
|
products=[element, element2, element3],
|
||||||
classification=result,
|
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(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
|
assert len(self.file.by_type("IfcClassificationReference")) == 0
|
||||||
|
|
||||||
def test_retaining_the_reference_if_still_in_use(self):
|
def test_retaining_the_reference_if_still_in_use(self):
|
||||||
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
||||||
element = self.file.createIfcMaterial()
|
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||||
element2 = self.file.createIfcMaterial()
|
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")
|
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
|
||||||
reference = ifcopenshell.api.run(
|
reference = ifcopenshell.api.run(
|
||||||
"classification.add_reference",
|
"classification.add_reference",
|
||||||
self.file,
|
self.file,
|
||||||
products=[element],
|
products=[element, element2, element3],
|
||||||
identification="X",
|
|
||||||
name="Foobar",
|
|
||||||
classification=result,
|
|
||||||
)
|
|
||||||
reference2 = ifcopenshell.api.run(
|
|
||||||
"classification.add_reference",
|
|
||||||
self.file,
|
|
||||||
products=[element2],
|
|
||||||
identification="X",
|
identification="X",
|
||||||
name="Foobar",
|
name="Foobar",
|
||||||
classification=result,
|
classification=result,
|
||||||
)
|
)
|
||||||
assert len(self.file.by_type("IfcClassificationReference")) == 1
|
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
|
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
|
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].Identification == "X"
|
||||||
assert list(ifcopenshell.util.classification.get_references(element2))[0].Name == "Foobar"
|
assert list(ifcopenshell.util.classification.get_references(element2))[0].Name == "Foobar"
|
||||||
assert list(ifcopenshell.util.classification.get_references(element2))[0] == references[0]
|
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