diff --git a/src/bonsai/bonsai/bim/module/sequence/prop.py b/src/bonsai/bonsai/bim/module/sequence/prop.py index 97f274c421..1d6cc521bd 100644 --- a/src/bonsai/bonsai/bim/module/sequence/prop.py +++ b/src/bonsai/bonsai/bim/module/sequence/prop.py @@ -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): diff --git a/src/bonsai/bonsai/bim/module/sequence/ui.py b/src/bonsai/bonsai/bim/module/sequence/ui.py index e393c425c2..c184964790 100644 --- a/src/bonsai/bonsai/bim/module/sequence/ui.py +++ b/src/bonsai/bonsai/bim/module/sequence/ui.py @@ -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() diff --git a/src/bonsai/bonsai/tool/sequence.py b/src/bonsai/bonsai/tool/sequence.py index 3da5290311..6f83975e4b 100644 --- a/src/bonsai/bonsai/tool/sequence.py +++ b/src/bonsai/bonsai/tool/sequence.py @@ -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