Bonsai: allow 4D animation at parent task granularity

For large schedules the reporter of #2381 wants to visualise the
construction sequence at a higher level, without the detailed order of
subtasks. Add an Aggregate Collapsed Tasks option to the animation
settings. When enabled, any task collapsed in the task tree animates as
a single unit: products assigned to its nested tasks all use the
collapsed task's derived date range. The task tree's expand and
collapse state therefore selects the granularity per branch, and the
default (everything expanded, option off) keeps existing behaviour.

Each product keeps the PredefinedType of its own task, so demolition
subtasks still animate as demolition within the aggregated window.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Petru Conduraru
2026-07-19 22:01:38 +03:00
parent 89523999b3
commit 4d9785e3ce
3 changed files with 31 additions and 0 deletions
@@ -542,6 +542,12 @@ class BIMWorkScheduleProperties(PropertyGroup):
should_auto_select: BoolProperty(
name="Auto Select", description="Auto select elements when task is selected", default=False
)
should_aggregate_contracted_tasks: BoolProperty(
name="Aggregate Collapsed Tasks",
description="Animate tasks collapsed in the task tree as single units, "
"using the collapsed task's derived date range for all products of its nested tasks",
default=False,
)
if TYPE_CHECKING:
work_schedule_predefined_types: str
@@ -602,6 +608,8 @@ class BIMWorkScheduleProperties(PropertyGroup):
show_task_operators: bool
should_show_schedule_baseline_ui: bool
filter_by_active_schedule: bool
should_auto_select: bool
should_aggregate_contracted_tasks: bool
class BIMTaskTreeProperties(PropertyGroup):
@@ -585,6 +585,9 @@ class BIM_PT_animation_tools(Panel):
row.label(text="Display Settings")
row = self.layout.row()
row.alignment = "RIGHT"
row.prop(self.props, "should_aggregate_contracted_tasks", icon="COLLAPSEMENU")
row = self.layout.row()
row.alignment = "RIGHT"
row.prop(self.animation_props, "should_show_task_bar_options", text="Task Bars", icon="NLA_PUSHDOWN")
if self.animation_props.should_show_task_bar_options:
row = self.layout.row()
+20
View File
@@ -1335,6 +1335,9 @@ class Sequence(bonsai.core.tool.Sequence):
@classmethod
def get_animation_product_frames(cls, work_schedule: ifcopenshell.entity_instance, settings: dict[str, Any]):
def preprocess_task(task):
if task.id() in contracted_tasks and ifcopenshell.util.sequence.get_nested_tasks(task):
preprocess_aggregated_task(task)
return
for subtask in ifcopenshell.util.sequence.get_nested_tasks(task):
preprocess_task(subtask)
start = ifcopenshell.util.sequence.derive_date(task, "ScheduleStart", is_earliest=True)
@@ -1346,6 +1349,19 @@ class Sequence(bonsai.core.tool.Sequence):
for input in cls.get_task_inputs(task):
add_product_frame(input.id(), task.PredefinedType, start, finish, "input")
def preprocess_aggregated_task(task):
# A collapsed task animates as a single unit: all products of its
# nested tasks use the collapsed task's derived date range.
start = ifcopenshell.util.sequence.derive_date(task, "ScheduleStart", is_earliest=True)
finish = ifcopenshell.util.sequence.derive_date(task, "ScheduleFinish", is_latest=True)
if not start or not finish:
return
for subtask in [task, *ifcopenshell.util.sequence.get_all_nested_tasks(task)]:
for output in ifcopenshell.util.sequence.get_task_outputs(subtask):
add_product_frame(output.id(), subtask.PredefinedType, start, finish, "output")
for input in ifcopenshell.util.sequence.get_task_inputs(subtask):
add_product_frame(input.id(), subtask.PredefinedType, start, finish, "input")
def add_product_frame(product_id, type, product_start, product_finish, relationship):
product_frames.setdefault(product_id, []).append(
{
@@ -1363,6 +1379,10 @@ class Sequence(bonsai.core.tool.Sequence):
)
product_frames = {}
contracted_tasks: set[int] = set()
props = cls.get_work_schedule_props()
if props.should_aggregate_contracted_tasks:
contracted_tasks = set(json.loads(props.contracted_tasks))
for root_task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
preprocess_task(root_task)
return product_frames