From da5a2625a997bfd7b6e0c52cf6fce6f4154bfd8c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 5 May 2021 17:07:17 +1000 Subject: [PATCH] New feature to visualise construction progress at a particular date --- .../bim/module/sequence/__init__.py | 2 + .../blenderbim/bim/module/sequence/helper.py | 20 ++++ .../bim/module/sequence/operator.py | 98 ++++++++++++++----- .../blenderbim/bim/module/sequence/prop.py | 29 ++++++ .../blenderbim/bim/module/sequence/ui.py | 11 +++ 5 files changed, 134 insertions(+), 26 deletions(-) create mode 100644 src/blenderbim/blenderbim/bim/module/sequence/helper.py diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 7782163b5c..379532fa0c 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -63,6 +63,8 @@ classes = ( operator.ImportP6, operator.LoadTaskProperties, operator.SelectTaskRelatedProducts, + operator.VisualiseWorkScheduleDate, + operator.VisualiseWorkScheduleDateRange, prop.WorkPlan, prop.BIMWorkPlanProperties, prop.Task, diff --git a/src/blenderbim/blenderbim/bim/module/sequence/helper.py b/src/blenderbim/blenderbim/bim/module/sequence/helper.py new file mode 100644 index 0000000000..f7144628cd --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/sequence/helper.py @@ -0,0 +1,20 @@ +from ifcopenshell.api.sequence.data import Data + + +def derive_date(ifc_definition_id, attribute_name, date=None, is_earliest=False, is_latest=False): + task = Data.tasks[ifc_definition_id] + if task["TaskTime"]: + current_date = Data.task_times[task["TaskTime"]][attribute_name] + if current_date: + return current_date + for subtask in task["RelatedObjects"]: + current_date = derive_date( + subtask, attribute_name, date=date, is_earliest=is_earliest, is_latest=is_latest + ) + if is_earliest: + if current_date and (date is None or current_date < date): + date = current_date + if is_latest: + if current_date and (date is None or current_date > date): + date = current_date + return date diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index c5f97035ac..08bf9acd08 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -7,6 +7,7 @@ import pystache import webbrowser import ifcopenshell.api import ifcopenshell.util.date +import blenderbim.bim.module.sequence.helper as helper from datetime import datetime from datetime import timedelta from dateutil import parser @@ -311,8 +312,8 @@ class LoadTaskProperties(bpy.types.Operator): isodate.duration_isoformat(task_time["ScheduleDuration"]) if task_time["ScheduleDuration"] else "-" ) else: - derived_start = self.derive_start(item.ifc_definition_id) - derived_finish = self.derive_finish(item.ifc_definition_id) + derived_start = helper.derive_date(item.ifc_definition_id, "ScheduleStart", is_earliest=True) + derived_finish = helper.derive_date(item.ifc_definition_id, "ScheduleFinish", is_latest=True) item.derived_start = self.canonicalise_time(derived_start) if derived_start else "" item.derived_finish = self.canonicalise_time(derived_finish) if derived_finish else "" if derived_start and derived_finish: @@ -324,30 +325,6 @@ class LoadTaskProperties(bpy.types.Operator): self.props.is_task_update_enabled = True return {"FINISHED"} - def derive_start(self, ifc_definition_id, start=None): - task = Data.tasks[ifc_definition_id] - if task["TaskTime"]: - schedule_start = Data.task_times[task["TaskTime"]]["ScheduleStart"] - if schedule_start: - return schedule_start - for subtask in task["RelatedObjects"]: - schedule_start = self.derive_start(subtask, start) - if schedule_start and (start is None or schedule_start < start): - start = schedule_start - return start - - def derive_finish(self, ifc_definition_id, finish=None): - task = Data.tasks[ifc_definition_id] - if task["TaskTime"]: - schedule_finish = Data.task_times[task["TaskTime"]]["ScheduleFinish"] - if schedule_finish: - return schedule_finish - for subtask in task["RelatedObjects"]: - schedule_finish = self.derive_finish(subtask, finish) - if schedule_finish and (finish is None or schedule_finish > finish): - finish = schedule_finish - return finish - def canonicalise_time(self, time): if not time: return "-" @@ -1421,3 +1398,72 @@ class SelectTaskRelatedProducts(bpy.types.Operator): if obj.BIMObjectProperties.ifc_definition_id in related_products: obj.select_set(True) return {"FINISHED"} + + +class VisualiseWorkScheduleDate(bpy.types.Operator): + bl_idname = "bim.visualise_work_schedule_date" + bl_label = "Visualise Work Schedule Date" + work_schedule: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMWorkScheduleProperties + self.file = IfcStore.get_file() + self.date = parser.parse(props.visualisation_start, dayfirst=True, fuzzy=True) + self.preprocess_tasks() + for obj in bpy.data.objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + obj.color = (1.0, 1.0, 1.0, 1) + obj.hide_set(False) + if obj.BIMObjectProperties.ifc_definition_id in self.not_yet_started: + obj.hide_set(True) + elif obj.BIMObjectProperties.ifc_definition_id in self.started: + obj.color = (0.0, 1.0, 0.0, 1) + elif obj.BIMObjectProperties.ifc_definition_id in self.completed: + pass + else: + obj.color = (1.0, 0.0, 0.0, 1) + area = next(area for area in context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.color_type = "OBJECT" + return {"FINISHED"} + + def preprocess_tasks(self): + self.not_yet_started = set() + self.started = set() + self.completed = set() + for rel in self.file.by_id(self.work_schedule).Controls or []: + for related_object in rel.RelatedObjects: + if related_object.is_a("IfcTask"): + self.preprocess_task(related_object) + + def preprocess_task(self, task): + for rel in task.IsNestedBy or []: + for related_object in rel.RelatedObjects: + self.preprocess_task(related_object) + start = helper.derive_date(task.id(), "ScheduleStart", is_earliest=True) + finish = helper.derive_date(task.id(), "ScheduleFinish", is_latest=True) + if not start or not finish: + return + products = [r.RelatingProduct.id() for r in task.HasAssignments or [] if r.is_a("IfcRelAssignsToProduct")] + if not products: + return + if self.date < start: + self.not_yet_started.update(products) + elif self.date < finish: + self.started.update(products) + else: + self.completed.update(products) + + +class VisualiseWorkScheduleDateRange(bpy.types.Operator): + bl_idname = "bim.visualise_work_schedule_date_range" + bl_label = "Visualise Work Schedule Date Range" + work_schedule: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + # for obj in bpy.context.visible_objects: + # obj.select_set(False) + # if obj.BIMObjectProperties.ifc_definition_id in related_products: + # obj.select_set(True) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index ae6e48fe69..9e0cbadeda 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -107,6 +107,32 @@ def updateTaskTimeDateTime(self, context, startfinish): setattr(self, startfinish, canonicalise_time(startfinish_datetime)) +def updateVisualisationStart(self, context): + updateVisualisationStartFinish(self, context, "visualisation_start") + +def updateVisualisationFinish(self, context): + updateVisualisationStartFinish(self, context, "visualisation_finish") + +def updateVisualisationStartFinish(self, context, startfinish): + def canonicalise_time(time): + if not time: + return "-" + return time.strftime("%d/%m/%y") + + startfinish_value = getattr(self, startfinish) + try: + startfinish_datetime = parser.isoparse(startfinish_value) + except: + try: + startfinish_datetime = parser.parse(startfinish_value, dayfirst=True, fuzzy=True) + except: + setattr(self, startfinish, "-") + return + canonical_value = canonicalise_time(startfinish_datetime) + if startfinish_value != canonical_value: + setattr(self, startfinish, canonical_value) + + workschedule_enum = [] @@ -159,6 +185,7 @@ class BIMWorkScheduleProperties(PropertyGroup): active_task_index: IntProperty(name="Active Task Index") active_task_id: IntProperty(name="Active Task Id") task_attributes: CollectionProperty(name="Task Attributes", type=Attribute) + should_show_visualisation_ui: BoolProperty(name="Should Show Visualisation UI", default=False) should_show_times: BoolProperty(name="Should Show Times", default=False) active_task_time_id: IntProperty(name="Active Task Id") task_time_attributes: CollectionProperty(name="Task Time Attributes", type=Attribute) @@ -168,6 +195,8 @@ class BIMWorkScheduleProperties(PropertyGroup): active_sequence_id: IntProperty(name="Active Sequence Id") sequence_attributes: CollectionProperty(name="Sequence Attributes", type=Attribute) time_lag_attributes: CollectionProperty(name="Time Lag Attributes", type=Attribute) + visualisation_start: StringProperty(name="Visualisation Start", update=updateVisualisationStart) + visualisation_finish: StringProperty(name="Visualisation Finish", update=updateVisualisationFinish) class BIMTaskTreeProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 2ec7542127..e1f57714f4 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -109,6 +109,7 @@ class BIM_PT_work_schedules(Panel): row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK") elif self.props.editing_type == "TASKS": row.prop(self.props, "should_show_times", text="", icon="TIME") + row.prop(self.props, "should_show_visualisation_ui", text="", icon="CAMERA_STEREO") row.operator("bim.generate_gantt_chart", text="", icon="NLA").work_schedule = work_schedule_id row.operator("bim.add_summary_task", text="", icon="ADD").work_schedule = work_schedule_id row.operator("bim.disable_editing_work_schedule", text="", icon="CANCEL") @@ -122,11 +123,21 @@ class BIM_PT_work_schedules(Panel): row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id if self.props.active_work_schedule_id == work_schedule_id: + if self.props.should_show_visualisation_ui: + self.draw_visualisation_ui() if self.props.editing_type == "WORK_SCHEDULE": self.draw_editable_work_schedule_ui() elif self.props.editing_type == "TASKS": self.draw_editable_task_ui(work_schedule_id) + def draw_visualisation_ui(self): + row = self.layout.row(align=True) + row.prop(self.props, "visualisation_start", text="", icon="REW") + row.prop(self.props, "visualisation_finish", text="", icon="FF") + op = row.operator("bim.visualise_work_schedule_date", text="", icon="RESTRICT_RENDER_OFF") + op.work_schedule = self.props.active_work_schedule_id + op = row.operator("bim.visualise_work_schedule_date_range", text="", icon="OUTLINER_OB_CAMERA") + def draw_editable_work_schedule_ui(self): for attribute in self.props.work_schedule_attributes: row = self.layout.row(align=True)