From de3dfcdc436eac770aa9083b41f77adc9083a49b Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Fri, 7 Apr 2023 04:33:56 +0200 Subject: [PATCH] BBIM 4D Features to: - Display nested task resources/task inputs of the active task - Show list of tasks related to the current object selection - Highlight, in the WorkSchedule panel, tasks related to the current object selection --- .../bim/module/sequence/__init__.py | 5 +- .../blenderbim/bim/module/sequence/helper.py | 2 +- .../bim/module/sequence/operator.py | 62 +++++++++++++------ .../blenderbim/bim/module/sequence/prop.py | 18 +++++- .../blenderbim/bim/module/sequence/ui.py | 59 +++++++++++++++--- src/blenderbim/blenderbim/core/sequence.py | 10 +++ src/blenderbim/blenderbim/tool/sequence.py | 32 +++++++++- .../ifcopenshell/util/sequence.py | 41 ++++++++---- 8 files changed, 184 insertions(+), 45 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 3e9f3b7d3a..2f4ec07df1 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -20,7 +20,6 @@ import bpy from . import ui, prop, operator classes = ( - operator.HighlightProductRelatedTask, operator.ExpandAllTasks, operator.ContractAllTasks, operator.AddSummaryTask, @@ -113,6 +112,8 @@ classes = ( operator.VisualiseWorkScheduleDateRange, operator.LoadTaskAnimationColors, operator.DisableEditingTaskAnimationColors, + operator.LoadProductTasks, + operator.HighlightTask, prop.WorkPlan, prop.BIMWorkPlanProperties, prop.Task, @@ -140,6 +141,8 @@ classes = ( ui.BIM_PT_4D_Tools, ui.BIM_PT_Task_Bar_Creator, ui.BIM_UL_animation_colors, + ui.BIM_UL_product_input_tasks, + ui.BIM_UL_product_output_tasks, ) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/helper.py b/src/blenderbim/blenderbim/bim/module/sequence/helper.py index 989cbafd80..56d5a95340 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/helper.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/helper.py @@ -53,7 +53,7 @@ def parse_duration_as_blender_props(dt, simplify=True): seconds = getattr(dt, "seconds", 0) hours, seconds = divmod(seconds, 3600) minutes, seconds = divmod(seconds, 60) - days = getattr(dt, "days",0) + days = getattr(dt, "days", 0) months = int(getattr(dt, "months", 0)) years = int(getattr(dt, "years", 0)) return { diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index e0957f3f69..2067b4e3a0 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -454,11 +454,8 @@ class UnassignProcess(bpy.types.Operator): ) else: core.unassign_input_products( - tool.Ifc, - tool.Sequence, - tool.Spatial, - task=tool.Ifc.get().by_id(self.task) - ) + tool.Ifc, tool.Sequence, tool.Spatial, task=tool.Ifc.get().by_id(self.task) + ) elif self.related_object_type == "CONTROL": pass # TODO return {"FINISHED"} @@ -1006,6 +1003,7 @@ class VisualiseWorkScheduleDate(bpy.types.Operator): core.visualise_work_schedule_date(tool.Sequence, work_schedule=tool.Ifc.get().by_id(self.work_schedule)) return {"FINISHED"} + class GuessDateRange(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.guess_date_range" bl_label = "Guess Work Schedule Date Range" @@ -1024,7 +1022,10 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator): @classmethod def poll(cls, context): - has_start, has_finish = bpy.context.scene.BIMWorkScheduleProperties.visualisation_start, bpy.context.scene.BIMWorkScheduleProperties.visualisation_finish + has_start, has_finish = ( + bpy.context.scene.BIMWorkScheduleProperties.visualisation_start, + bpy.context.scene.BIMWorkScheduleProperties.visualisation_finish, + ) return bool(has_start and has_finish) and not "-" in (has_start, has_finish) def execute(self, context): @@ -1230,17 +1231,6 @@ class CalculateTaskDuration(bpy.types.Operator, tool.Ifc.Operator): core.calculate_task_duration(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) -class HighlightProductRelatedTask(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.highlight_product_related_task" - bl_label = "Highlights the Related Task" - bl_options = {"REGISTER", "UNDO"} - bl_description = "Finds the related Task" - product_type: bpy.props.StringProperty() - - def _execute(self, context): - core.highlight_product_related_task(tool.Sequence, tool.Spatial, product_type=self.product_type) - - class ExpandAllTasks(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.expand_all_tasks" bl_label = "Expands All Tasks" @@ -1293,6 +1283,7 @@ class DisableEditingTaskAnimationColors(bpy.types.Operator): core.disable_editing_task_animation_colors(tool.Sequence) return {"FINISHED"} + class CopyTask(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.duplicate_task" bl_label = "Copy Task" @@ -1300,4 +1291,39 @@ class CopyTask(bpy.types.Operator, tool.Ifc.Operator): task: bpy.props.IntProperty() def _execute(self, context): - core.duplicate_task(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) \ No newline at end of file + core.duplicate_task(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) + + +class LoadProductTasks(bpy.types.Operator): + bl_idname = "bim.load_product_tasks" + bl_label = "Load Product Tasks" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + if not tool.Ifc.get(): + return False + if not context.active_object: + return False + if not context.active_object.BIMObjectProperties.ifc_definition_id: + return False + return True + + def execute(self, context): + core.load_product_tasks( + tool.Sequence, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id) + ) + return {"FINISHED"} + + +class HighlightTask(bpy.types.Operator): + bl_idname = "bim.highlight_task" + bl_label = "Highlight Task" + bl_options = {"REGISTER", "UNDO"} + task: bpy.props.IntProperty() + + def execute(self, context): + r = core.highlight_task(tool.Sequence, task=tool.Ifc.get().by_id(self.task)) + if isinstance(r, str): + self.report({"WARNING"}, r) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index 3e106d3f72..2f97ab598a 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -96,6 +96,14 @@ def update_active_task_outputs(self, context): bpy.ops.bim.load_task_outputs() +def update_active_task_resources(self, context): + bpy.ops.bim.load_task_resources() + + +def update_active_task_inputs(self, context): + bpy.ops.bim.load_task_inputs() + + def updateTaskName(self, context): props = context.scene.BIMWorkScheduleProperties if not props.is_task_update_enabled or self.name == "Unnamed": @@ -389,9 +397,13 @@ class BIMWorkScheduleProperties(PropertyGroup): active_task_input_index: IntProperty(name="Active Task Input Index") task_outputs: CollectionProperty(name="Task Outputs", type=TaskProduct) active_task_output_index: IntProperty(name="Active Task Output Index") - is_nested_task_outputs: BoolProperty( - name="Is Nested Task Outputs", default=False, update=update_active_task_outputs - ) + show_nested_outputs: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_outputs) + show_nested_resources: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_resources) + show_nested_inputs: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_inputs) + product_input_tasks: CollectionProperty(name="Product Task Inputs", type=TaskProduct) + product_output_tasks: CollectionProperty(name="Product Task Outputs", type=TaskProduct) + active_product_output_task_index: IntProperty(name="Active Product Output Task Index") + active_product_input_task_index: IntProperty(name="Active Product Input Task Index") class BIMTaskTreeProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 7356191324..e283b5b6a8 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -433,6 +433,8 @@ class BIM_PT_task_icom(Panel): op = row2.operator("bim.select_task_related_inputs", icon="RESTRICT_SELECT_OFF", text="") op.task = task.ifc_definition_id + row2 = col.row() + row2.prop(self.props, "show_nested_inputs", text="Show Nested") row2 = col.row() row2.template_list("BIM_UL_task_inputs", "", self.props, "task_inputs", self.props, "active_task_input_index") @@ -456,6 +458,9 @@ class BIM_PT_task_icom(Panel): op.related_object_type = "RESOURCE" op.resource = self.props.task_resources[self.props.active_task_resource_index].ifc_definition_id + row2 = col.row() + row2.prop(self.props, "show_nested_resources", text="Show Nested") + row2 = col.row() row2.template_list( "BIM_UL_task_resources", "", self.props, "task_resources", self.props, "active_task_resource_index" @@ -481,7 +486,7 @@ class BIM_PT_task_icom(Panel): op = row2.operator("bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="Select") op.task = task.ifc_definition_id row2 = col.row() - row2.prop(self.props, "is_nested_task_outputs", text="Show Nested") + row2.prop(self.props, "show_nested_outputs", text="Show Nested") row2 = col.row() row2.template_list( "BIM_UL_task_outputs", "", self.props, "task_outputs", self.props, "active_task_output_index" @@ -534,6 +539,26 @@ class BIM_UL_task_outputs(UIList): row.prop(item, "name", emboss=False, text="") +class BIM_UL_product_input_tasks(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + op = row.operator("bim.highlight_task", text="", icon="STYLUS_PRESSURE") + op.task = item.ifc_definition_id + row.split(factor=0.8) + row.label(text=item.name) + + +class BIM_UL_product_output_tasks(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + op = row.operator("bim.highlight_task", text="", icon="STYLUS_PRESSURE") + op.task = item.ifc_definition_id + row.split(factor=0.8) + row.label(text=item.name) + + class BIM_UL_tasks(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: @@ -798,14 +823,32 @@ class BIM_PT_4D_Tools(Panel): bl_category = "4D/5D Toolkit" def draw(self, context): + self.props = context.scene.BIMWorkScheduleProperties row = self.layout.row() - row.operator( - "bim.highlight_product_related_task", text="Find Input-related Task ", icon="STYLUS_PRESSURE" - ).product_type = "Input" - row = self.layout.row() - row.operator( - "bim.highlight_product_related_task", text="Go to Output-related Task ", icon="STYLUS_PRESSURE" - ).product_type = "Output" + row.operator("bim.load_product_tasks", text="Load Tasks", icon="FILE_REFRESH") + + grid = self.layout.grid_flow(columns=2, even_columns=True) + col1 = grid.column() + col1.label(text="Product Input Tasks") + col1.template_list( + "BIM_UL_product_input_tasks", + "", + self.props, + "product_input_tasks", + self.props, + "active_product_input_task_index", + ) + + col2 = grid.column() + col2.label(text="Product Output Tasks") + col2.template_list( + "BIM_UL_product_output_tasks", + "", + self.props, + "product_output_tasks", + self.props, + "active_product_output_task_index", + ) class BIM_PT_Task_Bar_Creator(Panel): diff --git a/src/blenderbim/blenderbim/core/sequence.py b/src/blenderbim/blenderbim/core/sequence.py index 412f99bc31..32f1d9c2ae 100644 --- a/src/blenderbim/blenderbim/core/sequence.py +++ b/src/blenderbim/blenderbim/core/sequence.py @@ -454,6 +454,13 @@ def calculate_task_duration(ifc, sequence, task=None): sequence.load_task_properties() +def highlight_task(sequence, task=None): + work_schedule = sequence.get_work_schedule(task) + is_work_schedule_active = sequence.is_work_schedule_active(work_schedule) + if is_work_schedule_active: + sequence.highlight_task(task) + + def highlight_product_related_task(sequence, spatial, product_type=None): products = spatial.get_selected_products() if products: @@ -514,3 +521,6 @@ def visualise_work_schedule_date(sequence, work_schedule=None): def generate_gantt_chart(sequence, work_schedule): json = sequence.create_tasks_json(work_schedule) sequence.generate_gantt_browser_chart(json) + +def load_product_tasks(sequence, product=None): + sequence.load_product_tasks(product) \ No newline at end of file diff --git a/src/blenderbim/blenderbim/tool/sequence.py b/src/blenderbim/blenderbim/tool/sequence.py index b0d12c6c07..d25eebf80d 100644 --- a/src/blenderbim/blenderbim/tool/sequence.py +++ b/src/blenderbim/blenderbim/tool/sequence.py @@ -422,7 +422,18 @@ class Sequence(blenderbim.core.tool.Sequence): @classmethod def get_task_inputs(cls, task): - return ifcopenshell.util.sequence.get_task_inputs(task) + is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_inputs + return ifcopenshell.util.sequence.get_task_inputs(task, is_deep) + + @classmethod + def get_task_outputs(cls, task): + is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_outputs + return ifcopenshell.util.sequence.get_task_outputs(task, is_deep) + + @classmethod + def get_task_resources(cls, task): + is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_resources + return ifcopenshell.util.sequence.get_task_resource(task, is_deep) @classmethod def load_task_inputs(cls, inputs): @@ -459,7 +470,7 @@ class Sequence(blenderbim.core.tool.Sequence): @classmethod def get_task_outputs(cls, task): - is_deep = bpy.context.scene.BIMWorkScheduleProperties.is_nested_task_outputs + is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_outputs return ifcopenshell.util.sequence.get_task_outputs(task, is_deep) @classmethod @@ -777,7 +788,7 @@ class Sequence(blenderbim.core.tool.Sequence): 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 + task_index = displayed_tasks.index(task.id()) or 0 bpy.context.scene.BIMWorkScheduleProperties.active_task_index = task_index @classmethod @@ -1500,3 +1511,18 @@ class Sequence(blenderbim.core.tool.Sequence): f.write(pystache.render(t.read(), {"json_data": json.dumps(task_json)})) webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html")) + @classmethod + def load_product_tasks(cls, product): + props = bpy.context.scene.BIMWorkScheduleProperties + props.is_task_update_enabled = False + props.product_input_tasks.clear() + props.product_output_tasks.clear() + task_inputs, task_ouputs = ifcopenshell.util.sequence.get_tasks_for_product(product) + for task in task_inputs or []: + new = props.product_input_tasks.add() + new.name = task.Name or "Unnamed" + new.ifc_definition_id = task.id() + for task in task_ouputs or []: + new = props.product_output_tasks.add() + new.name = task.Name or "Unnamed" + new.ifc_definition_id = task.id() diff --git a/src/ifcopenshell-python/ifcopenshell/util/sequence.py b/src/ifcopenshell-python/ifcopenshell/util/sequence.py index e6ca0a9ad0..f03a39e79e 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/sequence.py +++ b/src/ifcopenshell-python/ifcopenshell/util/sequence.py @@ -326,26 +326,45 @@ def get_task_outputs(task, is_deep=False): if not is_deep: return get_direct_task_outputs(task) else: - nested_tasks = get_all_nested_tasks(task) return [ output - for nested_task in nested_tasks + for nested_task in get_all_nested_tasks(task) for output in get_direct_task_outputs(nested_task) ] +def get_task_inputs(task, is_deep=False): + if not is_deep: + return [object for rel in task.OperatesOn if rel.is_a("IfcRelAssignsToProcess") for object in rel.RelatedObjects if object.is_a("IfcProduct")] + else: + return [ + output + for nested_task in get_all_nested_tasks(task) + for output in [object for rel in nested_task.OperatesOn if rel.is_a("IfcRelAssignsToProcess") for object in rel.RelatedObjects if object.is_a("IfcProduct")] + ] -def get_task_inputs(task): - inputs = [] - for rel in task.OperatesOn: - for object in rel.RelatedObjects: - if object.is_a("IfcProduct"): - inputs.append(object) - return inputs - +def get_task_resources(task, is_deep=False): + if not is_deep: + return [object for rel in task.OperatesOn if rel.is_a("IfcRelAssignsToProcess") for object in rel.RelatedObjects if object.is_a("IfcResource")] + else: + return [ + output + for nested_task in get_all_nested_tasks(task) + for output in [object for rel in nested_task.OperatesOn if rel.is_a("IfcRelAssignsToProcess") for object in rel.RelatedObjects if object.is_a("IfcResource")] + ] def has_task_outputs(task): return len(get_task_outputs(task)) > 0 - def has_task_inputs(task): return len(get_task_inputs(task)) > 0 + +def get_tasks_for_product(product): + inputs = [] + outputs = [] + for assignment in product.HasAssignments: + if assignment.is_a("IfcRelAssignsToProcess") and assignment.RelatingProcess.is_a("IfcTask"): + inputs.append(assignment.RelatingProcess) + for reference in product.ReferencedBy: + if reference.is_a("IfcRelAssignsToProduct") and reference.RelatedObjects[0].is_a("IfcTask"): + outputs.extend([obj for obj in reference.RelatedObjects if obj.is_a("IfcTask")]) + return inputs, outputs \ No newline at end of file