constraint.assign_constraint - support batching #4474

This commit is contained in:
Andrej730
2024-04-16 11:24:45 +05:00
parent 81608b0cee
commit c808382154
6 changed files with 127 additions and 25 deletions
@@ -179,17 +179,15 @@ class AssignConstraint(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = tool.Ifc.get()
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
for obj in objs: products = [self.file.by_id(obj_id) for obj in objs if (obj_id := obj.BIMObjectProperties.ifc_definition_id)]
obj_id = obj.BIMObjectProperties.ifc_definition_id if products:
if not obj_id:
continue
ifcopenshell.api.run( ifcopenshell.api.run(
"constraint.assign_constraint", "constraint.assign_constraint",
self.file, self.file,
**{ **{
"product": self.file.by_id(obj_id), "products": products,
"constraint": self.file.by_id(self.constraint), "constraint": self.file.by_id(self.constraint),
}, },
) )
+1 -1
View File
@@ -195,7 +195,7 @@ def add_usage_constraint(ifc, resource_tool, resource=None, reference_path=None)
}, },
) )
ifc.run("constraint.add_metric_reference", metric=metric, reference_path=reference_path) ifc.run("constraint.add_metric_reference", metric=metric, reference_path=reference_path)
ifc.run("constraint.assign_constraint", product=resource, constraint=objective) ifc.run("constraint.assign_constraint", products=[resource], constraint=objective)
def remove_usage_constraint(ifc, resource_tool, resource, reference_path): def remove_usage_constraint(ifc, resource_tool, resource, reference_path):
@@ -111,6 +111,9 @@ ARGUMENTS_DEPRECATION = {
"spatial.dereference_structure": partial( "spatial.dereference_structure": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products" batching_argument_deprecation, prev_argument="product", new_argument="products"
), ),
"constraint.assign_constraint": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
} }
@@ -18,11 +18,17 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
from typing import Union
class Usecase: class Usecase:
def __init__(self, file, product=None, constraint=None): def __init__(
"""Assigns a constraint to a product self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
constraint: ifcopenshell.entity_instance,
):
"""Assigns a constraint to a list of products
This assigns a relationship between a product and a constraint, so that This assigns a relationship between a product and a constraint, so that
when a product's properties and quantities do not match the requirements when a product's properties and quantities do not match the requirements
@@ -32,37 +38,58 @@ class Usecase:
constraints are inherited from the type. This way, it is not necessary constraints are inherited from the type. This way, it is not necessary
to create lots of constraint assignments. to create lots of constraint assignments.
:param product: The product the constraint applies to. This is anything :param products: The list of products the constraint applies to. This is anything
which can have properties or quantities. which can have properties or quantities.
:type product: ifcopenshell.entity_instance.entity_instance :type products: list[ifcopenshell.entity_instance.entity_instance]
:param constraint: The IfcObjective constraint :param constraint: The IfcObjective constraint
:type constraint: ifcopenshell.entity_instance.entity_instance :type constraint: ifcopenshell.entity_instance.entity_instance
:return: The new or updated IfcRelAssociatesConstraint relationship :return: The new or updated IfcRelAssociatesConstraint relationship
or `None` if `products` was an empty list.
:rtype: ifcopenshell.entity_instance.entity_instance :rtype: ifcopenshell.entity_instance.entity_instance
""" """
self.file = file self.file = file
self.settings = { self.settings = {
"product": product, "products": products,
"constraint": constraint, "constraint": constraint,
} }
def execute(self): def execute(self) -> Union[ifcopenshell.entity_instance, None]:
rel = self.get_constraint_rel() products = set(self.settings["products"])
related_objects = set(rel.RelatedObjects) if rel.RelatedObjects else set() if not products:
related_objects.add(self.settings["product"]) return
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) self.constraint = self.settings["constraint"]
return rel
rels = self.get_constraint_rels()
related_objects = set()
for rel in rels:
related_objects.update(rel.RelatedObjects)
products_to_assign = products - related_objects
if not products_to_assign:
return rels[0]
rel = next(iter(rels), None)
if rel:
related_objects = set(rel.RelatedObjects) | products_to_assign
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
return rel
def get_constraint_rel(self):
for rel in self.file.by_type("IfcRelAssociatesConstraint"):
if rel.RelatingConstraint == self.settings["constraint"]:
return rel
return self.file.create_entity( return self.file.create_entity(
"IfcRelAssociatesConstraint", "IfcRelAssociatesConstraint",
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatingConstraint": self.settings["constraint"], "RelatingConstraint": self.constraint,
"RelatedObjects": list(products_to_assign),
} }
) )
def get_constraint_rels(self) -> list[ifcopenshell.entity_instance]:
rels = []
for rel in self.file.get_inverse(self.constraint):
if rel.is_a("IfcRelAssociatesConstraint"):
rels.append(rel)
return rels
@@ -0,0 +1,64 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import pytest
import test.bootstrap
import ifcopenshell.api
import ifcopenshell.util.constraint
class TestAssignConstraint(test.bootstrap.IFC4):
def test_assign_a_constraint(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
ifcopenshell.api.run(
"constraint.assign_constraint", self.file, products=[element, element2], constraint=constraint
)
assert ifcopenshell.util.constraint.get_constrained_elements(constraint) == {element, element2}
assert len(self.file.by_type("IfcRelAssociatesConstraint")) == 1
def test_doing_nothing_if_the_constraint_is_already_assigned(self):
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run(
"constraint.assign_constraint", self.file, products=[element, element2], constraint=constraint
)
total_elements = len([e for e in self.file])
ifcopenshell.api.run(
"constraint.assign_constraint", self.file, products=[element, element2], constraint=constraint
)
assert len([e for e in self.file]) == total_elements
def test_that_old_relationships_are_updated_if_they_still_contain_elements(self):
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("constraint.assign_constraint", self.file, products=[element1], constraint=constraint)
rel = self.file.by_type("IfcRelAssociatesConstraint")[0]
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run(
"constraint.assign_constraint", self.file, products=[element2, element3], constraint=constraint
)
assert len(rel.RelatedObjects) == 3
class TestAssignConstraintIFC2X3(test.bootstrap.IFC2X3, TestAssignConstraint):
pass
+11 -1
View File
@@ -19,6 +19,7 @@
import test.bootstrap import test.bootstrap
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.classification import ifcopenshell.util.classification
import ifcopenshell.util.constraint
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.system import ifcopenshell.util.system
from datetime import datetime from datetime import datetime
@@ -285,6 +286,15 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4):
def test_removing_a_container(self): def test_removing_a_container(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.reference_structure", self.file, products=[subelement], relating_structure=element) ifcopenshell.api.run(
"spatial.reference_structure", self.file, products=[subelement], relating_structure=element
)
ifcopenshell.api.run("spatial.dereference_structure", self.file, product=subelement, relating_structure=element) ifcopenshell.api.run("spatial.dereference_structure", self.file, product=subelement, relating_structure=element)
assert ifcopenshell.util.element.get_referenced_structures(subelement) == [] assert ifcopenshell.util.element.get_referenced_structures(subelement) == []
@deprecation_check
def test_assign_a_constraint(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
ifcopenshell.api.run("constraint.assign_constraint", self.file, product=element, constraint=constraint)
assert ifcopenshell.util.constraint.get_constrained_elements(constraint) == {element}