From 6b3393c0032c92fbff9d41afab5196745b4abc3c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 5 May 2021 18:36:28 +1000 Subject: [PATCH] You can now generate construction sequence animations to visualise within a date range --- .../bim/module/sequence/operator.py | 86 ++++++++++++++++++- .../blenderbim/bim/module/sequence/prop.py | 40 ++++++--- .../blenderbim/bim/module/sequence/ui.py | 12 +++ 3 files changed, 123 insertions(+), 15 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 08bf9acd08..20dbf6a7a5 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -1462,8 +1462,86 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator): 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) + self.props = context.scene.BIMWorkScheduleProperties + self.start = parser.parse(self.props.visualisation_start, dayfirst=True, fuzzy=True) + self.finish = parser.parse(self.props.visualisation_finish, dayfirst=True, fuzzy=True) + self.duration = self.finish - self.start + self.start_frame = 1 + self.total_frames = self.calculate_total_frames() + self.preprocess_tasks() + for obj in bpy.data.objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + product_frames = self.product_frames.get(obj.BIMObjectProperties.ifc_definition_id) + if not product_frames: + continue + obj.hide_viewport = True + obj.keyframe_insert(data_path="hide_viewport", frame=self.start_frame) + obj.hide_viewport = False + obj.color = (0.0, 1.0, 0.0, 1) + obj.keyframe_insert(data_path="hide_viewport", frame=product_frames["STARTED"]) + obj.keyframe_insert(data_path="color", frame=product_frames["STARTED"]) + obj.color = (1.0, 1.0, 1.0, 1) + obj.keyframe_insert(data_path="color", frame=product_frames["COMPLETED"]) + area = next(area for area in context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.color_type = "OBJECT" + context.scene.frame_start = self.start_frame + context.scene.frame_end = self.start_frame + self.total_frames return {"FINISHED"} + + def calculate_total_frames(self): + if self.props.speed_types == "FRAME_SPEED": + return self.calculate_using_frames( + self.start, + self.finish, + self.props.speed_animation_frames, + isodate.parse_duration(self.props.speed_real_duration), + ) + elif self.props.speed_types == "DURATION_SPEED": + return self.calculate_using_duration( + self.start, + self.finish, + bpy.context.scene.render.fps, + isodate.parse_duration(self.props.speed_animation_duration), + isodate.parse_duration(self.props.speed_real_duration), + ) + elif self.props.speed_types == "MULTIPLIER_SPEED": + return self.calculate_using_multiplier( + self.start, + self.finish, + bpy.context.scene.render.fps, + isodate.parse_duration(self.props.speed_animation_duration), + self.props.speed_multiplier, + ) + + def calculate_using_multiplier(self, start, finish, fps, multiplier): + animation_time = (finish - start) / multiplier + return animation_time.total_seconds() * fps + + def calculate_using_duration(self, start, finish, fps, animation_duration, real_duration): + return self.calculate_using_multiplier(start, finish, fps, real_duration / animation_duration) + + def calculate_using_frames(self, start, finish, animation_frames, real_duration): + return ((finish - start) / real_duration) * animation_frames + + def preprocess_tasks(self): + self.product_frames = {} + 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 + product_ids = [r.RelatingProduct.id() for r in task.HasAssignments or [] if r.is_a("IfcRelAssignsToProduct")] + for product_id in product_ids: + self.product_frames[product_id] = { + "STARTED": round(self.start_frame + (((start - self.start) / self.duration) * self.total_frames)), + "COMPLETED": round(self.start_frame + (((finish - self.start) / self.duration) * self.total_frames)), + } diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index 9e0cbadeda..2490736e8b 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -110,9 +110,11 @@ def updateTaskTimeDateTime(self, context, startfinish): 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: @@ -197,6 +199,19 @@ class BIMWorkScheduleProperties(PropertyGroup): 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) + speed_multiplier: FloatProperty(name="Speed Multiplier") + speed_animation_duration: StringProperty(name="Speed Animation Duration", default="PT1S") + speed_animation_frames: IntProperty(name="Speed Animation Frames", default=24) + speed_real_duration: StringProperty(name="Speed Real Duration", default="P1W") + speed_types: EnumProperty( + items=[ + ("FRAME_SPEED", "Frame-based", "e.g. 25 frames = 1 real week"), + ("DURATION_SPEED", "Duration-based", "e.g. 1 video second = 1 real week"), + ("MULTIPLIER_SPEED", "Multiplier", "e.g. 1000 x real life speed"), + ], + name="Speed Type", + default="FRAME_SPEED", + ) class BIMTaskTreeProperties(PropertyGroup): @@ -227,16 +242,19 @@ class BIMWorkCalendarProperties(PropertyGroup): position: IntProperty(name="Position") interval: IntProperty(name="Recurrence Interval") occurrences: IntProperty(name="Occurs N Times") - recurrence_types: EnumProperty(items=[ - ("DAILY", "Daily", "e.g. Every day"), - ("WEEKLY", "Weekly", "e.g. Every Friday"), - ("MONTHLY_BY_DAY_OF_MONTH", "Monthly on Specified Date", "e.g. Every 2nd of each Month"), - ("MONTHLY_BY_POSITION", "Monthly on Specified Weekday", "e.g. Every 1st Friday of each Month"), - # https://forums.buildingsmart.org/t/what-does-by-day-count-and-by-weekday-count-mean-in-ifcrecurrencetypeenum/3571 - # ("BY_DAY_COUNT", "", ""), - # ("BY_WEEKDAY_COUNT", "", ""), - ("YEARLY_BY_DAY_OF_MONTH", "Yearly on Specified Date", "e.g. Every 2nd of October"), - ("YEARLY_BY_POSITION", "Yearly on Specified Weekday", "e.g. Every 1st Friday of October"), - ], name="Recurrence Types") + recurrence_types: EnumProperty( + items=[ + ("DAILY", "Daily", "e.g. Every day"), + ("WEEKLY", "Weekly", "e.g. Every Friday"), + ("MONTHLY_BY_DAY_OF_MONTH", "Monthly on Specified Date", "e.g. Every 2nd of each Month"), + ("MONTHLY_BY_POSITION", "Monthly on Specified Weekday", "e.g. Every 1st Friday of each Month"), + # https://forums.buildingsmart.org/t/what-does-by-day-count-and-by-weekday-count-mean-in-ifcrecurrencetypeenum/3571 + # ("BY_DAY_COUNT", "", ""), + # ("BY_WEEKDAY_COUNT", "", ""), + ("YEARLY_BY_DAY_OF_MONTH", "Yearly on Specified Date", "e.g. Every 2nd of October"), + ("YEARLY_BY_POSITION", "Yearly on Specified Weekday", "e.g. Every 1st Friday of October"), + ], + name="Recurrence Types", + ) start_time: StringProperty(name="Start Time") end_time: StringProperty(name="End Time") diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index e1f57714f4..ea5aab4a13 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -137,6 +137,18 @@ class BIM_PT_work_schedules(Panel): 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") + op.work_schedule = self.props.active_work_schedule_id + + row = self.layout.row(align=True) + row.prop(self.props, "speed_types", text="") + if self.props.speed_types == "FRAME_SPEED": + row.prop(self.props, "speed_animation_frames", text="") + row.prop(self.props, "speed_real_duration", text="") + elif self.props.speed_types == "DURATION_SPEED": + row.prop(self.props, "speed_animation_duration", text="") + row.prop(self.props, "speed_real_duration", text="") + elif self.props.speed_types == "MULTIPLIER_SPEED": + row.prop(self.props, "speed_multiplier", text="") def draw_editable_work_schedule_ui(self): for attribute in self.props.work_schedule_attributes: