diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 8598228e0d..7cef4a3ab3 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -20,6 +20,8 @@ import bpy from . import ui, prop, operator classes = ( + operator.ExpandAllTasks, + operator.ContractAllTasks, operator.AddSummaryTask, operator.AddTask, operator.AddTaskColumn, diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index dee5dc5d35..ead12dae50 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -1507,3 +1507,23 @@ class CalculateTaskDuration(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): core.calculate_task_duration(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) +class ExpandAllTasks(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.expand_all_tasks" + bl_label = "Expands all tasks" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Finds the related Task" + product_type: bpy.props.StringProperty() + + def _execute(self, context): + core.expand_all_tasks(tool.Sequence) + + +class ContractAllTasks(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.contract_all_tasks" + bl_label = "Expands all tasks" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Finds the related Task" + product_type: bpy.props.StringProperty() + + def _execute(self, context): + core.contract_all_tasks(tool.Sequence) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index df58c85ea7..489684a8bf 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -252,6 +252,9 @@ class BIM_PT_work_schedules(Panel): self.props, "active_task_index", ) + row = self.layout.row() + row.operator("bim.expand_all_tasks", text="Expand All Tasks") + row.operator("bim.contract_all_tasks", text="Contract All Tasks") if self.props.active_task_id and self.props.editing_task_type == "ATTRIBUTES": self.draw_editable_task_attributes_ui() elif self.props.active_task_id and self.props.editing_task_type == "CALENDAR": diff --git a/src/blenderbim/blenderbim/core/sequence.py b/src/blenderbim/blenderbim/core/sequence.py index 4bc5babc38..3583ff0673 100644 --- a/src/blenderbim/blenderbim/core/sequence.py +++ b/src/blenderbim/blenderbim/core/sequence.py @@ -90,6 +90,13 @@ def expand_task(sequence, task=None): sequence.load_task_properties() +def expand_all_tasks(sequence): + sequence.expand_all_tasks() + work_schedule = sequence.get_active_work_schedule() + sequence.create_task_tree(work_schedule) + sequence.load_task_properties() + + def contract_task(sequence, task=None): sequence.contract_task(task) work_schedule = sequence.get_active_work_schedule() @@ -97,6 +104,12 @@ def contract_task(sequence, task=None): sequence.load_task_properties() +def contract_all_tasks(sequence): + sequence.contract_all_tasks() + work_schedule = sequence.get_active_work_schedule() + sequence.create_task_tree(work_schedule) + sequence.load_task_properties() + def remove_task(ifc, sequence, task=None): ifc.run("sequence.remove_task", task=task) work_schedule = sequence.get_active_work_schedule() diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 67de3896db..88f207c31f 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -489,8 +489,7 @@ class Sequence: def remove_task_column(cls, name): pass def set_task_sort_column(cls, column): pass def find_related_output_tasks(cls, column): pass - def get_root_task(cls, task): pass - def get_task_work_schedule(cls, task): pass + def get_work_schedule(cls, task): pass def is_work_schedule_active(cls, work_schedule): pass def highlight_task(cls, task): pass diff --git a/src/blenderbim/blenderbim/tool/sequence.py b/src/blenderbim/blenderbim/tool/sequence.py index aaf8508f29..8c0664b3b2 100644 --- a/src/blenderbim/blenderbim/tool/sequence.py +++ b/src/blenderbim/blenderbim/tool/sequence.py @@ -123,7 +123,7 @@ class Sequence(blenderbim.core.tool.Sequence): @classmethod def create_task_tree(cls, work_schedule): - def get_work_schedule_root_tasks(work_schedule): + def get_root_tasks_ids(work_schedule): related_objects_ids = [] if work_schedule.Controls: for rel in work_schedule.Controls: @@ -136,7 +136,7 @@ class Sequence(blenderbim.core.tool.Sequence): props = bpy.context.scene.BIMWorkScheduleProperties cls.contracted_tasks = json.loads(props.contracted_tasks) - related_objects_ids = get_work_schedule_root_tasks(work_schedule) + related_objects_ids = get_root_tasks_ids(work_schedule) if not related_objects_ids: return cls.sort_keys = {i: cls.get_sort_key(tool.Ifc.get().by_id(i)) for i in related_objects_ids} @@ -268,6 +268,19 @@ class Sequence(blenderbim.core.tool.Sequence): contracted_tasks.remove(task.id()) props.contracted_tasks = json.dumps(contracted_tasks) + @classmethod + def expand_all_tasks(cls): + bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks = json.dumps([]) + + @classmethod + def contract_all_tasks(cls): + props = bpy.context.scene.BIMWorkScheduleProperties + contracted_tasks = json.loads(props.contracted_tasks) + for task_item in bpy.context.scene.BIMTaskTreeProperties.tasks: + if task_item.is_expanded: + contracted_tasks.append(task_item.ifc_definition_id) + props.contracted_tasks = json.dumps(contracted_tasks) + @classmethod def contract_task(cls, task): props = bpy.context.scene.BIMWorkScheduleProperties @@ -310,7 +323,7 @@ class Sequence(blenderbim.core.tool.Sequence): @classmethod def get_task_time(cls, task): - return task.TaskTime if task.TaskTime else None + return task.TaskTime if task.TaskTime else None @classmethod def load_task_attributes(cls, task): @@ -342,17 +355,19 @@ class Sequence(blenderbim.core.tool.Sequence): if prop.data_type == "string": if name == "ScheduleDuration" and data[name] and isinstance(data[name], str): time_object = ifcopenshell.util.date.ifc2datetime(data[name]) - minutes = int(round(time_object.seconds/60 % 60)) - hours = (time_object.seconds - minutes*60)/60/60 + minutes = int(round(time_object.seconds / 60 % 60)) + hours = (time_object.seconds - minutes * 60) / 60 / 60 bpy.context.scene.BIMDuration.duration_days = int(time_object.days) bpy.context.scene.BIMDuration.duration_hours = round(hours) - bpy.context.scene.BIMDuration.duration_minutes = minutes # should consider years, months and seconds. + bpy.context.scene.BIMDuration.duration_minutes = ( + minutes # should consider years, months and seconds. + ) if isinstance(data[name], datetime): prop.string_value = "" if prop.is_null else data[name].isoformat() if name == "ScheduleDuration": bpy.context.scene.BIMDuration.duration_days = data[name].days bpy.context.scene.BIMDuration.duration_hours = round(data[name].seconds / 60 / 60) - bpy.context.scene.BIMDuration.duration_minutes = data[name].seconds/60 % 60 + bpy.context.scene.BIMDuration.duration_minutes = data[name].seconds / 60 % 60 return True elif isinstance(data[name], isodate.Duration): prop.string_value = ( @@ -390,7 +405,7 @@ class Sequence(blenderbim.core.tool.Sequence): return True # TODO make this parse PT32 as P4D attributes[prop.name] = helper.parse_duration(prop.string_value) - dprops= bpy.context.scene.BIMDuration + dprops = bpy.context.scene.BIMDuration duration_days = dprops.duration_days if dprops.duration_days else 0 duration_hours = dprops.duration_hours if dprops.duration_hours else 0 duration_minutes = dprops.duration_minutes if dprops.duration_minutes else 0 @@ -638,7 +653,6 @@ class Sequence(blenderbim.core.tool.Sequence): props.active_sequence_id = rel_sequence.id() props.editing_sequence_type = "ATTRIBUTES" - @classmethod def load_lag_time_attributes(cls, lag_time): def callback(name, prop, data): @@ -716,19 +730,12 @@ class Sequence(blenderbim.core.tool.Sequence): return related_tasks @classmethod - def get_root_task(cls, task): - return ( - task - if not task.Nests or not task.Nests[0].RelatingObject.is_a("IfcTask") - else cls.get_root_task(task.Nests[0].RelatingObject) - ) - - @classmethod - def get_task_work_schedule(cls, task): - if task.HasAssignments: - for rel in task.HasAssignments: - if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule"): - return rel.RelatingControl + def get_work_schedule(cls, task): + for rel in task.HasAssignments or []: + if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule"): + return rel.RelatingControl + for rel in task.Nests or []: + return cls.get_work_schedule(rel.RelatingObject) @classmethod def is_work_schedule_active(cls, work_schedule): @@ -736,11 +743,42 @@ class Sequence(blenderbim.core.tool.Sequence): True if work_schedule.id() == bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id else False ) + @classmethod + def get_work_schedule(cls, task): + for rel in task.HasAssignments or []: + if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule"): + return rel.RelatingControl + for rel in task.Nests or []: + return cls.get_work_schedule(rel.RelatingObject) + + @classmethod + def expand_ancestors(cls, task): + for rel in task.Nests or []: + parent_task = rel.RelatingObject if rel.RelatingObject.is_a("IfcTask") else None + print(parent_task) + contracted_tasks = json.loads(bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks) + if parent_task and parent_task.id() in contracted_tasks: + contracted_tasks.remove(parent_task.id()) + bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks = json.dumps(contracted_tasks) + cls.expand_ancestors(parent_task) + @classmethod def highlight_task(cls, task): - props = bpy.context.scene.BIMWorkScheduleProperties + def expand_ancestors(task): + for rel in task.Nests or []: + parent_task = rel.RelatingObject if rel.RelatingObject.is_a("IfcTask") else None + contracted_tasks = json.loads(bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks) + if parent_task and parent_task.id() in contracted_tasks: + contracted_tasks.remove(parent_task.id()) + bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks = json.dumps(contracted_tasks) + expand_ancestors(parent_task) + work_schedule = cls.get_active_work_schedule() + cls.create_task_tree(work_schedule) + cls.load_task_properties() + task_props = bpy.context.scene.BIMTaskTreeProperties - task_id = task.id() - task_index = [task.ifc_definition_id for task in task_props.tasks].index(task_id) - if task_index: - props.active_task_index = task_index + displayed_tasks = [item.ifc_definition_id for item in task_props.tasks] + if not task.id() in displayed_tasks: + expand_ancestors(task) + task_index = [item.ifc_definition_id for item in task_props.tasks].index(task.id()) or 0 + bpy.context.scene.BIMWorkScheduleProperties.active_task_index = task_index diff --git a/src/blenderbim/test/bim/feature/sequence.feature b/src/blenderbim/test/bim/feature/sequence.feature index ee3c42e7ce..afd86dc0db 100644 --- a/src/blenderbim/test/bim/feature/sequence.feature +++ b/src/blenderbim/test/bim/feature/sequence.feature @@ -661,4 +661,56 @@ Scenario: Assign task calendar with no working time And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleStart').string_value" to "2021-01-02" And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleFinish').string_value" to "2021-01-06" And I press "bim.edit_task_time" + Then nothing happens + +Scenario: Contract Task + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + And I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "summary_task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + When I press "bim.add_task(task={summary_task})" + And I press "bim.contract_task(task={summary_task})" + Then nothing happens + +Scenario: Expand Task + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + And I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "summary_task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + When I press "bim.add_task(task={summary_task})" + And I press "bim.contract_task(task={summary_task})" + And I press "bim.expand_task(task={summary_task})" + Then nothing happens + +Scenario: Contract All Tasks + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + And I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "summary_task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + When I press "bim.add_task(task={summary_task})" + And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" + When I press "bim.add_task(task={summary_task})" + And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[1].id()" + When I press "bim.contract_all_tasks()" + Then nothing happens + +Scenario: Expand All Tasks + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + And I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "summary_task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + When I press "bim.add_task(task={summary_task})" + And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" + When I press "bim.add_task(task={summary_task})" + And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[1].id()" + And I press "bim.contract_all_tasks()" + When I press "bim.expand_all_tasks()" Then nothing happens \ No newline at end of file