diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 881143cf80..68034f6e95 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -969,55 +969,9 @@ class VisualiseWorkScheduleDate(bpy.types.Operator): return bool(bpy.context.scene.BIMWorkScheduleProperties.visualisation_start) 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" + core.visualise_work_schedule_date(tool.Sequence, work_schedule=tool.Ifc.get().by_id(self.work_schedule)) 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 = ifcopenshell.util.sequence.derive_date(task, "ScheduleStart", is_earliest=True) - finish = ifcopenshell.util.sequence.derive_date(task, "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 GuessDateRange(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.guess_date_range" bl_label = "Guess Work Schedule Date Range" diff --git a/src/blenderbim/blenderbim/core/sequence.py b/src/blenderbim/blenderbim/core/sequence.py index be3da6f6ab..e9287f710b 100644 --- a/src/blenderbim/blenderbim/core/sequence.py +++ b/src/blenderbim/blenderbim/core/sequence.py @@ -62,6 +62,7 @@ def assign_work_schedule(ifc, work_plan=None, work_schedule=None): if work_schedule: return ifc.run("aggregate.assign_object", relating_object=work_plan, product=work_schedule) + def unassign_work_schedule(ifc, work_schedule=None): ifc.run("aggregate.unassign_object", product=work_schedule) @@ -499,9 +500,18 @@ def visualise_work_schedule_date_range(sequence, work_schedule=None): if settings: product_frames = sequence.get_animation_product_frames(work_schedule, settings) sequence.load_task_animation_colors() - sequence.animate_objects(settings, product_frames) + sequence.animate_objects(settings, product_frames, "date_range") sequence.add_text_animation_handler(settings) add_task_bars(sequence) + sequence.set_object_shading() + + +def visualise_work_schedule_date(sequence, work_schedule=None): + sequence.clear_objects_animation(include_blender_objects=False) + start_date = sequence.get_start_date() + product_states = sequence.process_construction_state(work_schedule, start_date) + sequence.show_snapshot(product_states) + sequence.set_object_shading() def generate_gantt_chart(sequence, work_schedule): diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 2432dcc572..50bdc50b28 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -484,9 +484,11 @@ class Sequence: def add_task_column(cls, column_type, name, data_type): pass def contract_all_tasks(cls): pass def contract_task(cls, task): pass - def create_task_tree(cls, work_schedule): pass + def create_bars(cls, tasks): pass def create_bars(cls, tasks):pass + def create_task_tree(cls, work_schedule): pass def disable_editing_rel_sequence(cls): pass + def disable_editing_task_animation_colors(cls): pass def disable_editing_task_time(cls): pass def disable_editing_task(cls): pass def disable_editing_work_calendar(cls): pass @@ -497,6 +499,7 @@ class Sequence: def disable_work_schedule(cls): pass def enable_editing_rel_sequence_attributes(cls, rel_sequence): pass def enable_editing_sequence_lag_time(cls, rel_sequence): pass + def enable_editing_task_animation_colors(cls): pass def enable_editing_task_calendar(cls, task): pass def enable_editing_task_sequence(cls, task): pass def enable_editing_task_time(cls, task): pass @@ -510,12 +513,16 @@ class Sequence: def enable_editing_work_time(cls, work_time): pass def expand_all_tasks(cls): pass def expand_task(cls, task): pass + def find_related_input_tasks(cls, product): pass def find_related_output_tasks(cls, column): pass + def find_related_output_tasks(cls, product): pass def get_active_task(cls): pass def get_active_work_schedule(cls): pass + def get_animation_bar_tasks(cls): pass def get_checked_tasks(cls): pass def get_direct_nested_tasks(cls, task):pass def get_direct_task_outputs(cls, task): pass + def get_finish_date(cls): pass def get_highlighted_task(cls): pass def get_lag_time_attributes(cls): pass def get_recurrence_pattern_attributes(cls, recurrence_pattern): pass @@ -523,6 +530,7 @@ class Sequence: def get_rel_sequence_attributes(cls): pass def get_selected_products(cls): pass def get_selected_resource(cls): pass + def get_start_date(cls): pass def get_task_attribute_value(cls, attribute_name): pass def get_task_attributes(cls): pass def get_task_inputs(cls, task): pass @@ -541,6 +549,7 @@ class Sequence: def load_lag_time_attributes(cls, lag_time): pass def load_rel_sequence_attributes(cls, rel_sequence): pass def load_resources(cls): pass + def load_task_animation_colors(cls): pass def load_task_attributes(cls, task): pass def load_task_inputs(cls, inputs): pass def load_task_outputs(cls, outputs): pass @@ -551,11 +560,16 @@ class Sequence: def load_work_plan_attributes(cls, work_plan): pass def load_work_schedule_attributes(cls, work_schedule): pass def load_work_time_attributes(cls, work_time): pass + def override_end_date(cls): pass + def process_construction_state(cls, work_schedule, date): pass + def process_task_status(cls, task, date): pass def remove_task_column(cls, name): pass def reset_time_period(cls): pass def select_products(cls, products): pass + def set_object_shading(cls): pass def set_task_sort_column(cls, column): pass def setup_default_task_columns(cls): pass + def show_snapshot(cls, product_states): pass def update_visualisation_date(cls, start_date, finish_date): pass diff --git a/src/blenderbim/blenderbim/tool/sequence.py b/src/blenderbim/blenderbim/tool/sequence.py index 0c5de7c1a0..30c1ce4982 100644 --- a/src/blenderbim/blenderbim/tool/sequence.py +++ b/src/blenderbim/blenderbim/tool/sequence.py @@ -35,6 +35,7 @@ from datetime import datetime import mathutils import pystache import webbrowser +from datetime import timedelta class Sequence(blenderbim.core.tool.Sequence): @@ -429,12 +430,7 @@ class Sequence(blenderbim.core.tool.Sequence): @classmethod def get_task_inputs(cls, task): - inputs = [] - for rel in task.OperatesOn: - for object in rel.RelatedObjects: - if object.is_a("IfcProduct"): - inputs.append(object) - return inputs + return ifcopenshell.util.sequence.get_task_inputs(task) @classmethod def load_task_inputs(cls, inputs): @@ -742,17 +738,17 @@ class Sequence(blenderbim.core.tool.Sequence): props.sort_column = column @classmethod - def find_related_input_tasks(cls, object): + def find_related_input_tasks(cls, product): related_tasks = [] - for assignment in object.HasAssignments: + for assignment in product.HasAssignments: if assignment.is_a("IfcRelAssignsToProcess") and assignment.RelatingProcess.is_a("IfcTask"): related_tasks.append(assignment.RelatingProcess) return related_tasks @classmethod - def find_related_output_tasks(cls, object): + def find_related_output_tasks(cls, product): related_tasks = [] - for reference in object.ReferencedBy: + for reference in product.ReferencedBy: if reference.is_a("IfcRelAssignsToProduct") and reference.RelatedObjects[0].is_a("IfcTask"): related_tasks.append(reference.RelatedObjects[0]) return related_tasks @@ -1104,6 +1100,110 @@ class Sequence(blenderbim.core.tool.Sequence): def disable_editing_task_animation_colors(cls): bpy.context.scene.BIMAnimationProperties.is_editing = False + @classmethod + def get_start_date(cls): + start = parser.parse(bpy.context.scene.BIMWorkScheduleProperties.visualisation_start, dayfirst=True, fuzzy=True) + return start if start else None + + @classmethod + def get_finish_date(cls): + finish = parser.parse( + bpy.context.scene.BIMWorkScheduleProperties.visualisation_finish, dayfirst=True, fuzzy=True + ) + return finish if finish else None + + @classmethod + def process_construction_state(cls, work_schedule, date): + cls.to_build = set() + cls.in_construction = set() + cls.completed = set() + cls.to_demolish = set() + cls.in_demolition = set() + cls.demolished = set() + for rel in work_schedule.Controls or []: + for related_object in rel.RelatedObjects: + if related_object.is_a("IfcTask"): + cls.process_task_status(related_object, date) + return { + "TO_BUILD": cls.to_build, + "IN_CONSTRUCTION": cls.in_construction, + "COMPLETED": cls.completed, + "TO_DEMOLISH": cls.to_demolish, + "IN_DEMOLITION": cls.in_demolition, + "DEMOLISHED": cls.demolished, + } + + @classmethod + def process_task_status(cls, task, date): + for rel in task.IsNestedBy or []: + [cls.process_task_status(related_object, date) for related_object in rel.RelatedObjects] + start = ifcopenshell.util.sequence.derive_date(task, "ScheduleStart", is_earliest=True) + finish = ifcopenshell.util.sequence.derive_date(task, "ScheduleFinish", is_latest=True) + if not start or not finish: + return + outputs = ifcopenshell.util.sequence.get_task_outputs(task) or [] + inputs = cls.get_task_inputs(task) or [] + if date < start: + [cls.to_build.add(tool.Ifc.get_object(output)) for output in outputs] + [cls.to_demolish.add(tool.Ifc.get_object(input)) for input in inputs] + elif date < finish: + [cls.in_construction.add(tool.Ifc.get_object(output)) for output in outputs] + [cls.in_demolition.add(tool.Ifc.get_object(input)) for input in inputs] + else: + [cls.completed.add(tool.Ifc.get_object(output)) for output in outputs] + [cls.demolished.add(tool.Ifc.get_object(input)) for input in inputs] + + @classmethod + def show_snapshot(cls, product_states): + print(product_states) + bpy.context.scene.frame_start = 1 + bpy.context.scene.frame_end = 2 + 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_viewport = False + obj.hide_render = False + + for obj in cls.to_build: + obj.hide_viewport = True + obj.hide_render = True + obj.keyframe_insert(data_path="hide_viewport", frame=1) + obj.keyframe_insert(data_path="hide_render", frame=1) + + for obj in cls.in_construction: + obj.color = (1.0, 0.7, 0.0, 1) + obj.keyframe_insert(data_path="color", frame=1) + + for obj in cls.completed: + obj.color = (0.0, 1.0, 0.0, 1) + obj.keyframe_insert(data_path="color", frame=1) + + for obj in cls.to_demolish: + if obj in cls.to_build: + continue + elif obj.animation_data: + obj.animation_data_clear() + obj.hide_viewport = False + obj.hide_render = False + obj.keyframe_insert(data_path="hide_viewport", frame=1) + obj.keyframe_insert(data_path="hide_render", frame=1) + for obj in cls.in_demolition: + obj.color = (1.0, 0.0, 0.0, 1) + obj.keyframe_insert(data_path="color", frame=1) + for obj in cls.demolished: + if obj.animation_data: + obj.animation_data_clear() + obj.hide_viewport = True + obj.hide_render = True + obj.keyframe_insert(data_path="hide_viewport", frame=1) + obj.keyframe_insert(data_path="hide_render", frame=1) + + @classmethod + def set_object_shading(cls): + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.color_type = "OBJECT" + @classmethod def get_animation_settings(cls): def calculate_total_frames(fps): @@ -1143,8 +1243,9 @@ class Sequence(blenderbim.core.tool.Sequence): props = bpy.context.scene.BIMWorkScheduleProperties if not (props.visualisation_start and props.visualisation_finish): return - start = parser.parse(props.visualisation_start, dayfirst=True, fuzzy=True) - finish = parser.parse(props.visualisation_finish, dayfirst=True, fuzzy=True) + + start = cls.get_start_date() + finish = cls.get_finish_date() duration = finish - start start_frame = 1 total_frames = calculate_total_frames(bpy.context.scene.render.fps) @@ -1192,34 +1293,45 @@ class Sequence(blenderbim.core.tool.Sequence): return product_frames @classmethod - def animate_objects(cls, settings, frames): + def clear_object_animation(cls, obj): + if obj.animation_data: + obj.animation_data_clear() + print("Cleared animation for", obj.name) + + @classmethod + def clear_objects_animation(cls, include_blender_objects=True): + for obj in bpy.data.objects: + if not include_blender_objects and not obj.BIMObjectProperties.ifc_definition_id: + continue + cls.clear_object_animation(obj) + if not obj.visible_get(): + obj.hide_viewport = False + obj.hide_render = False + + @classmethod + def animate_objects(cls, settings, frames, clear_previous=True, animation_type=""): for obj in bpy.data.objects: if not obj.BIMObjectProperties.ifc_definition_id: continue - if obj.animation_data: - obj.animation_data_clear() + if clear_previous: + cls.clear_object_animation(obj) cls.earliest_frame = None product_frames = frames.get(obj.BIMObjectProperties.ifc_definition_id, []) for product_frame in product_frames: if product_frame["relationship"] == "input": - cls.animate_input(obj, settings["start_frame"], product_frame) + cls.animate_input(obj, settings["start_frame"], product_frame, animation_type) elif product_frame["relationship"] == "output": cls.animate_output(obj, settings["start_frame"], product_frame) area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") area.spaces[0].shading.color_type = "OBJECT" bpy.context.scene.frame_start = settings["start_frame"] - bpy.context.scene.frame_end = int(settings["start_frame"] + settings["total_frames"]) + bpy.context.scene.frame_end = int(settings["start_frame"] + settings["total_frames"] + 1) @classmethod - def animate_input(cls, obj, start_frame, product_frame): + def animate_input(cls, obj, start_frame, product_frame, animation_type): props = bpy.context.scene.BIMAnimationProperties color = props.task_colors_components_inputs[product_frame["type"]].color - if product_frame["type"] in ["LOGISTIC", "MOVE"]: - cls.animate_movement_from(obj, start_frame, product_frame, color) - elif product_frame["type"] in ["DEMOLITION", "DISMANTLE", "DISPOSAL", "REMOVAL"]: - cls.animate_destruction(obj, start_frame, product_frame, color) - else: - cls.animate_consumption(obj, start_frame, product_frame, color) + cls.animate_destruction(obj, start_frame, product_frame, color, animation_type) @classmethod def animate_output(cls, obj, start_frame, product_frame): @@ -1235,7 +1347,7 @@ class Sequence(blenderbim.core.tool.Sequence): cls.animate_operation(obj, start_frame, product_frame, color) @classmethod - def animate_destruction(cls, obj, start_frame, product_frame, color): + def animate_destruction(cls, obj, start_frame, product_frame, color, animation_type): if cls.earliest_frame is None or product_frame["STARTED"] < cls.earliest_frame: obj.color = (1.0, 1.0, 1.0, 1) obj.hide_viewport = False @@ -1244,9 +1356,15 @@ class Sequence(blenderbim.core.tool.Sequence): obj.keyframe_insert(data_path="hide_viewport", frame=start_frame) obj.keyframe_insert(data_path="hide_render", frame=start_frame) cls.earliest_frame = product_frame["STARTED"] - obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1) + if animation_type == "snapshot": + start = product_frame["STARTED"] + else: + start = product_frame["STARTED"] - 1 + obj.keyframe_insert(data_path="color", frame=start) + obj.keyframe_insert(data_path="hide_viewport", frame=start) + obj.keyframe_insert(data_path="hide_render", frame=start) obj.color = (color.r, color.g, color.b, 1) - obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"]) + obj.keyframe_insert(data_path="color", frame=start + 1) obj.hide_viewport = True obj.hide_render = True obj.color = (0.0, 0.0, 0.0, 1) @@ -1255,44 +1373,12 @@ class Sequence(blenderbim.core.tool.Sequence): obj.keyframe_insert(data_path="hide_render", frame=product_frame["COMPLETED"]) @classmethod - def animate_movement_from(cls, obj, start_frame, product_frame, color): - if cls.earliest_frame is None or product_frame["STARTED"] < cls.earliest_frame: - obj.color = (1.0, 1.0, 1.0, 1) - obj.keyframe_insert(data_path="color", frame=start_frame) - cls.earliest_frame = product_frame["STARTED"] - obj.hide_viewport = False - obj.hide_render = False - obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1) - obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"] - 1) - obj.keyframe_insert(data_path="hide_render", frame=product_frame["STARTED"] - 1) - obj.color = (color.r, color.g, color.b, 1) - obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"]) - obj.hide_viewport = True - obj.hide_render = True - obj.color = (0.0, 0.0, 0.0, 1) - obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"]) - obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"]) - obj.keyframe_insert(data_path="hide_render", frame=product_frame["COMPLETED"]) + def animate_movement_from(cls, obj, start_frame, product_frame, color, animation_type): + cls.animate_destruction(obj, start_frame, product_frame, color, animation_type) @classmethod - def animate_consumption(cls, obj, start_frame, product_frame, color): - if cls.earliest_frame is None or product_frame["STARTED"] < cls.earliest_frame: - obj.color = (1.0, 1.0, 1.0, 1) - obj.keyframe_insert(data_path="color", frame=start_frame) - cls.earliest_frame = product_frame["STARTED"] - obj.hide_viewport = False - obj.hide_render = False - obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1) - obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"] - 1) - obj.keyframe_insert(data_path="hide_render", frame=product_frame["STARTED"] - 1) - obj.color = (color.r, color.g, color.b, 1) - obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"]) - obj.hide_viewport = True - obj.hide_render = True - obj.color = (0.0, 0.0, 0.0, 1) - obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"]) - obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"]) - obj.keyframe_insert(data_path="hide_render", frame=product_frame["COMPLETED"]) + def animate_consumption(cls, obj, start_frame, product_frame, color, animation_type): + cls.animate_destruction(obj, start_frame, product_frame, color, animation_type) @classmethod def animate_creation(cls, obj, start_frame, product_frame, color): @@ -1325,20 +1411,7 @@ class Sequence(blenderbim.core.tool.Sequence): @classmethod def animate_movement_to(cls, obj, start_frame, product_frame, color): - if cls.earliest_frame is None or product_frame["STARTED"] < cls.earliest_frame: - obj.hide_viewport = True - obj.hide_render = True - obj.keyframe_insert(data_path="hide_viewport", frame=start_frame) - obj.keyframe_insert(data_path="hide_render", frame=start_frame) - cls.earliest_frame = product_frame["STARTED"] - obj.hide_viewport = False - obj.hide_render = False - obj.color = (color.r, color.g, color.b, 1) - obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"]) - obj.keyframe_insert(data_path="hide_render", frame=product_frame["STARTED"]) - obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"]) - obj.color = (1.0, 1.0, 1.0, 1) - obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"]) + cls.animate_creation(obj, start_frame, product_frame, color) @classmethod def add_text_animation_handler(cls, settings): @@ -1421,7 +1494,9 @@ class Sequence(blenderbim.core.tool.Sequence): else: data["pClass"] = "gtaskblue" - data["pDepend"] = ",".join([f"{rel.RelatingProcess.id()}{type_map[rel.SequenceType]}" for rel in task.IsSuccessorFrom or []]) + data["pDepend"] = ",".join( + [f"{rel.RelatingProcess.id()}{type_map[rel.SequenceType]}" for rel in task.IsSuccessorFrom or []] + ) json.append(data) for nested_task in ifcopenshell.util.sequence.get_nested_tasks(task): cls.create_new_task_json(nested_task, json, type_map) @@ -1431,4 +1506,5 @@ class Sequence(blenderbim.core.tool.Sequence): with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f: with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t: 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")) \ No newline at end of file + webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html")) +