BBIM: improve work schedule visualisation workflow & refactor visualise_work_schedule_date operator

This commit is contained in:
Sigma Dimensions
2023-02-08 08:25:11 +01:00
parent 7fe2634d32
commit ac5ec72acf
4 changed files with 182 additions and 128 deletions
@@ -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"
+11 -1
View File
@@ -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):
+15 -1
View File
@@ -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
+155 -79
View File
@@ -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"))
webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"))