From a430d356c079d6bd135be9e3a9bdfa66e210aede Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 25 Aug 2021 16:27:23 +1000 Subject: [PATCH] You can now parametrically derive task durations from resource utilisation --- .../bim/module/sequence/__init__.py | 1 + .../bim/module/sequence/operator.py | 21 ++++++- .../blenderbim/bim/module/sequence/ui.py | 19 +++--- .../api/sequence/calculate_task_duration.py | 62 +++++++++++++++++++ 4 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/sequence/calculate_task_duration.py diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 10d973b167..1997e93671 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -98,6 +98,7 @@ classes = ( operator.LoadTaskInputs, operator.LoadTaskResources, operator.LoadTaskOutputs, + operator.DeriveTaskDuration, prop.WorkPlan, prop.BIMWorkPlanProperties, prop.Task, diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 0af442fffe..00c0e8bfd3 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -443,7 +443,7 @@ class AddTask(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMWorkScheduleProperties self.file = IfcStore.get_file() - ifcopenshell.api.run("sequence.add_task", self.file, **{"parent_task": self.file.by_id(self.task)}) + ifcopenshell.api.run("sequence.add_task", self.file, parent_task=self.file.by_id(self.task)) Data.load(self.file) bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) return {"FINISHED"} @@ -461,7 +461,7 @@ class AddSummaryTask(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMWorkScheduleProperties self.file = IfcStore.get_file() - ifcopenshell.api.run("sequence.add_task", self.file, **{"work_schedule": self.file.by_id(self.work_schedule)}) + ifcopenshell.api.run("sequence.add_task", self.file, work_schedule=self.file.by_id(self.work_schedule)) Data.load(self.file) bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) return {"FINISHED"} @@ -2098,9 +2098,24 @@ class LoadTaskOutputs(bpy.types.Operator): self.tprops = context.scene.BIMTaskTreeProperties ifc_definition_id = self.tprops.tasks[self.props.active_task_index].ifc_definition_id self.props.task_outputs.clear() - for output_id in Data.tasks[ifc_definition_id]["outputs"]: + for output_id in Data.tasks[ifc_definition_id]["Outputs"]: product = self.file.by_id(output_id) new = self.props.task_outputs.add() new.ifc_definition_id = output_id new.name = product.Name or "Unnamed" return {"FINISHED"} + + +class DeriveTaskDuration(bpy.types.Operator): + bl_idname = "bim.derive_task_duration" + bl_label = "Derive Task Duration" + bl_options = {"REGISTER", "UNDO"} + task: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMWorkScheduleProperties + self.file = IfcStore.get_file() + ifcopenshell.api.run("sequence.calculate_task_duration", self.file, task=self.file.by_id(self.task)) + Data.load(self.file) + bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 7586690242..42dea09f5e 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -363,9 +363,7 @@ class BIM_PT_task_icom(Panel): op.related_object = "" row2 = col.row() - row2.template_list( - "BIM_UL_task_inputs", "", self.props, "task_inputs", self.props, "active_task_input_index" - ) + row2.template_list("BIM_UL_task_inputs", "", self.props, "task_inputs", self.props, "active_task_input_index") # Column2 col = grid.column() @@ -373,17 +371,24 @@ class BIM_PT_task_icom(Panel): row2 = col.row(align=True) row2.label(text="Resources") + op = row2.operator("bim.derive_task_duration", text="", icon="TIME") + op.task = task.ifc_definition_id + total_resources = len(context.scene.BIMResourceTreeProperties.resources) if total_resources and context.scene.BIMResourceProperties.active_resource_index < total_resources: - resource_id = context.scene.BIMResourceTreeProperties.resources[context.scene.BIMResourceProperties.active_resource_index].ifc_definition_id op = row2.operator("bim.assign_process", icon="ADD", text="") op.task = task.ifc_definition_id op.related_object_type = "RESOURCE" - op.resource = resource_id + op.resource = context.scene.BIMResourceTreeProperties.resources[ + context.scene.BIMResourceProperties.active_resource_index + ].ifc_definition_id + + total_task_resources = len(self.props.task_resources) + if total_task_resources and self.props.active_task_resource_index < total_task_resources: op = row2.operator("bim.unassign_process", icon="REMOVE", text="") op.task = task.ifc_definition_id op.related_object_type = "RESOURCE" - op.resource = resource_id + op.resource = self.props.task_resources[self.props.active_task_resource_index].ifc_definition_id row2 = col.row() row2.template_list( @@ -430,7 +435,7 @@ class BIM_UL_task_inputs(UIList): if item: row = layout.row(align=True) row.prop(item, "name", emboss=False, text="") - #row.operator("bim.remove_task_column", text="", icon="X").name = item.name + # row.operator("bim.remove_task_column", text="", icon="X").name = item.name class BIM_UL_task_resources(UIList): diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/calculate_task_duration.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/calculate_task_duration.py new file mode 100644 index 0000000000..93520337ff --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/calculate_task_duration.py @@ -0,0 +1,62 @@ +import math +import ifcopenshell.api +import ifcopenshell.util.date +import ifcopenshell.util.element + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"task": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + self.seconds_per_day = self.calculate_seconds_per_day() + duration = self.calculate_max_resource_usage_duration() + if duration: + self.set_task_duration(duration) + + def calculate_max_resource_usage_duration(self): + max_duration = 0 + for rel in self.settings["task"].OperatesOn or []: + for related_object in rel.RelatedObjects: + if related_object.is_a("IfcConstructionResource"): + duration = self.calculate_duration_in_days(related_object) + if duration and duration > max_duration: + max_duration = duration + return max_duration + + def set_task_duration(self, duration): + if not self.settings["task"].TaskTime: + ifcopenshell.api.run("sequence.add_task_time", self.file, task=self.settings["task"]) + self.settings["task"].TaskTime.ScheduleDuration = f"P{duration}D" + + def calculate_seconds_per_day(self): + default_seconds_per_day = 8 * 60 * 60 + work_schedule = self.get_work_schedule() + if not work_schedule: + return default_seconds_per_day + psets = ifcopenshell.util.element.get_psets(work_schedule) + if not psets or "Pset_WorkControlCommon" not in psets or "WorkDayDuration" not in psets["Pset_WorkControlCommon"]: + return default_seconds_per_day + work_day_duration = ifcopenshell.util.date.ifc2datetime(psets["Pset_WorkControlCommon"]["WorkDayDuration"]) + return work_day_duration.seconds + + def get_work_schedule(self): + for rel in self.settings["task"].HasAssignments or []: + if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule"): + return rel.RelatingControl + for rel in self.settings["task"].Nests or []: + return self.get_work_schedule(rel.RelatingObject) + + def calculate_duration_in_days(self, resource): + if not resource.Usage or not resource.Usage.ScheduleWork: + return + schedule_usage = resource.Usage.ScheduleUsage or 1 + schedule_duration = ifcopenshell.util.date.ifc2datetime(resource.Usage.ScheduleWork) + schedule_seconds = 0 + if schedule_duration.days: + schedule_seconds += schedule_duration.days * self.seconds_per_day + schedule_seconds += schedule_duration.seconds + return math.ceil((schedule_seconds / self.seconds_per_day) / schedule_usage)