control.assign_control to support batching

This commit is contained in:
Andrej730
2025-09-09 11:14:57 +05:00
parent fcb263f59a
commit abfb56350d
23 changed files with 104 additions and 50 deletions
@@ -53,6 +53,22 @@ pre_listeners: dict[str, dict] = {}
post_listeners: dict[str, dict] = {}
def batching_argument_deprecation(
usecase_path: str, settings: dict, prev_argument: str, new_argument: str, replace_usecase: Optional[str] = None
) -> tuple[str, dict]:
if replace_usecase is not None:
print(f"WARNING. `{usecase_path}` api method is deprecated and should be replaced with `{replace_usecase}`.")
if prev_argument in settings:
print(
f"WARNING. `{prev_argument}` argument is deprecated for API method "
f'"{usecase_path}" and should be replaced with `{new_argument}`.'
)
settings = settings | {new_argument: [settings[prev_argument]]}
settings.pop(prev_argument)
return (replace_usecase or usecase_path, settings)
def renamed_arguments_deprecation(
usecase_path: str, settings: dict, arguments_remapped: dict[str, str]
) -> tuple[str, dict]:
@@ -71,7 +87,11 @@ def renamed_arguments_deprecation(
# "group.add_group": partial(
# renamed_arguments_deprecation, arguments_remapped={"Name": "name", "Description": "description"}
# ),
ARGUMENTS_DEPRECATION: dict[str, Callable[[str, dict[str, Any]], tuple[str, dict[str, Any]]]] = {}
ARGUMENTS_DEPRECATION: dict[str, Callable[[str, dict[str, Any]], tuple[str, dict[str, Any]]]] = {
"control.assign_control": partial(
batching_argument_deprecation, prev_argument="related_object", new_argument="related_objects"
),
}
CACHED_USECASE_CLASSES: dict[str, Callable] = {}
@@ -25,9 +25,9 @@ from typing import Union
def assign_control(
file: ifcopenshell.file,
relating_control: ifcopenshell.entity_instance,
related_object: ifcopenshell.entity_instance,
related_objects: list[ifcopenshell.entity_instance],
) -> Union[ifcopenshell.entity_instance, None]:
"""Assigns a planning control or constraint to an object
"""Assigns a planning control or constraint to a list of objects.
IFC can describe concepts that control other objects. For example, a
planning calendar controls the availability of working days for
@@ -42,7 +42,7 @@ def assign_control(
:param relating_control: The IfcControl entity that is creating the
control or constraint
:param related_object: The IfcObjectDefinition that is being controlled
:param related_objects: The list of IfcObjectDefinition that is being controlled
:return: The newly created IfcRelAssignsToControl. If relationship already
existed before and wasn't changed then returns None.
@@ -59,7 +59,7 @@ def assign_control(
# All subtasks will inherit this calendar, so assigning a single
# calendar to the root task effectively defines a "default" calendar
ifcopenshell.api.control.assign_control(model,
relating_control=calendar, related_object=task)
relating_control=calendar, related_objects=[task])
# Another common example might be relating a cost item and a product
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
@@ -67,22 +67,33 @@ def assign_control(
cost_item = ifcopenshell.api.cost.add_cost_item(model,
cost_schedule=schedule)
ifcopenshell.api.control.assign_control(model,
relating_control=cost_item, related_object=wall)
relating_control=cost_item, related_objects=[wall])
"""
if related_object.HasAssignments:
for assignment in related_object.HasAssignments:
if assignment.is_a("IfcRelAssignsToControl") and assignment.RelatingControl == relating_control:
return
# Filter out already assigned objects.
related_objects_set = set(related_objects)
objects_to_assign: set[ifcopenshell.entity_instance] = set()
control_assignments = set(relating_control.Controls)
if control_assignments:
for obj in related_objects_set:
existing_assignment = next((a for a in obj.HasAssignments if a in control_assignments), None)
# Skip objects already assigned to this control.
if existing_assignment:
continue
objects_to_assign.add(obj)
else:
objects_to_assign = related_objects_set
if not objects_to_assign:
return None
controls: Union[ifcopenshell.entity_instance, None]
controls = next(iter(relating_control.Controls), None)
if controls:
if related_object in controls.RelatedObjects:
return
related_objects = set(controls.RelatedObjects)
related_objects.add(related_object)
controls.RelatedObjects = list(related_objects)
related_objects_new: list[ifcopenshell.entity_instance] = list(controls.RelatedObjects)
related_objects_new.extend(objects_to_assign)
controls.RelatedObjects = list(related_objects_new)
ifcopenshell.api.owner.update_owner_history(file, element=controls)
else:
controls = file.create_entity(
@@ -90,7 +101,7 @@ def assign_control(
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": [related_object],
"RelatedObjects": list(objects_to_assign),
"RelatingControl": relating_control,
},
)
@@ -45,7 +45,7 @@ def unassign_control(
cost_item = ifcopenshell.api.cost.add_cost_item(model,
cost_schedule=schedule)
ifcopenshell.api.control.assign_control(model,
relating_control=cost_item, related_object=wall)
relating_control=cost_item, related_objects=[wall])
# And now let's change our mind
ifcopenshell.api.control.unassign_control(model,
@@ -59,7 +59,7 @@ def add_cost_item(
cost_item_ = ifcopenshell.api.root.create_entity(file, ifc_class="IfcCostItem")
if cost_schedule:
ifcopenshell.api.control.assign_control(file, cost_schedule, cost_item_)
ifcopenshell.api.control.assign_control(file, cost_schedule, [cost_item_])
elif cost_item:
ifcopenshell.api.nest.assign_object(file, related_objects=[cost_item_], relating_object=cost_item)
return cost_item_
@@ -66,7 +66,7 @@ def add_cost_item_quantity(
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
ifcopenshell.api.control.assign_control(model,
relating_control=item, related_object=chair)
relating_control=item, related_objects=[chair])
# Let's assume we want to count the amount of chairs to calculate our cost item
# Because this is an IfcQuantityCount the count will be automatically set to "1" chair
@@ -122,7 +122,7 @@ class Usecase:
) -> ifcopenshell.entity_instance:
return ifcopenshell.api.control.assign_control(
self.file,
related_object=related_object,
related_objects=[related_object],
relating_control=cost_item,
)
@@ -57,7 +57,7 @@ def calculate_cost_item_resource_value(file: ifcopenshell.file, cost_item: ifcop
concrete = ifcopenshell.api.resource.add_resource(model,
ifc_class="IfcConstructionMaterialResource", parent_resource=crew)
ifcopenshell.api.control.assign_control(model,
relating_control=item, related_object=concrete)
relating_control=item, related_objects=[concrete])
# ... which has a unit price of 42.0 per m3
value = ifcopenshell.api.cost.add_cost_value(model, parent=concrete)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
@@ -72,7 +72,7 @@ def calculate_cost_item_resource_value(file: ifcopenshell.file, cost_item: ifcop
equipment = ifcopenshell.api.resource.add_resource(model,
ifc_class="IfcConstructionEquipmentResource", parent_resource=crew)
ifcopenshell.api.control.assign_control(model,
relating_control=item, related_object=equipment)
relating_control=item, related_objects=[equipment])
# ... with a fixed price of 50,000
value = ifcopenshell.api.cost.add_cost_value(model, parent=concrete)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
@@ -45,5 +45,5 @@ def copy_cost_schedule(
if isinstance(duplicated_cost_item, list):
# All other nested items are not connected to the cost schedule explicitly.
duplicated_cost_item = duplicated_cost_item[0]
ifcopenshell.api.control.assign_control(file, new_schedule, duplicated_cost_item)
ifcopenshell.api.control.assign_control(file, new_schedule, [duplicated_cost_item])
return new_schedule
@@ -138,7 +138,7 @@ def add_task(
task.Identification = identification
task.IsMilestone = False
if work_schedule:
ifcopenshell.api.control.assign_control(file, work_schedule, task)
ifcopenshell.api.control.assign_control(file, work_schedule, [task])
elif parent_task:
rel = ifcopenshell.api.nest.assign_object(
file,
@@ -75,7 +75,7 @@ def add_work_calendar(
# We associate the calendar with the construction root task. All
# subtasks underneath the construction work task will also inherit
# this calendar by default (though you can override them).
ifcopenshell.api.control.assign_control(model, relating_control=calendar, related_object=task)
ifcopenshell.api.control.assign_control(model, relating_control=calendar, related_objects=[task])
"""
work_calendar = ifcopenshell.api.root.create_entity(
file,
@@ -47,5 +47,5 @@ def copy_work_schedule(
duplicated_tasks = ifcopenshell.api.sequence.duplicate_task(file, task)[1]
# All other nested items are not connected to the work schedule explicitly.
duplicated_task = duplicated_tasks[0]
ifcopenshell.api.control.assign_control(file, new_schedule, duplicated_task)
ifcopenshell.api.control.assign_control(file, new_schedule, [duplicated_task])
return new_schedule
@@ -79,7 +79,7 @@ class Usecase:
assert isinstance(res, list)
current, duplicate = res
ifcopenshell.api.control.assign_control(
self.file, relating_control=baseline_work_schedule, related_object=duplicate[0]
self.file, relating_control=baseline_work_schedule, related_objects=[duplicate[0]]
)
for i, task in enumerate(current):
self.create_baseline_reference(task, duplicate[i])